hmn/src/templates/src/include/header.html

193 lines
8.9 KiB
HTML
Raw Normal View History

2021-10-21 02:21:24 +00:00
<header id="site-header" class="mb3 bb bw1 b--theme-dark">
2023-01-17 03:22:23 +00:00
<a href="#content-start" class="sr-focusable" id="content-jump">Jump to Content</a>
2021-10-26 04:42:55 +00:00
<div class="user-options flex justify-center justify-end-ns relative">
2021-03-26 03:33:00 +00:00
{{ if .User }}
2021-07-22 02:16:10 +00:00
{{ if .User.IsStaff }}
2021-08-28 17:07:45 +00:00
<a class="admin-panel pa2" href="{{ .Header.AdminUrl }}"><span class="icon-settings"> Admin</span></a>
2021-03-26 03:33:00 +00:00
{{ end }}
2021-08-28 17:07:45 +00:00
<div>
<a class="dib pv2 pl2" href="{{ .Header.UserProfileUrl }}">{{ .User.Username }}</a>
<a class="dib pv2 pr2" href="{{ .Header.UserSettingsUrl }}">(settings)</a>
</div>
<a class="logout pa2" href="{{ .Header.LogoutActionUrl }}"><span class="icon-logout"></span> Log Out</a>
2021-03-26 03:33:00 +00:00
{{ else }}
2021-08-28 17:07:45 +00:00
<a class="register pa2" id="register-link" href="{{ .Header.RegisterUrl }}">Register</a>
<a class="login pa2" id="login-link" href="{{ .LoginPageUrl }}">Log in</a>
Add Discord login (#106) This leverages our existing Discord OAuth implementation. Any users with a linked Discord account will be able to log in immediately. When logging in, we request the `email` scope in addition to `identity`, so existing users will be prompted one time to accept the new permissions. On subsequent logins, Discord will skip the prompt. When linking your Discord account to an existing HMN account, we continue to only request the `identity` scope, so we do not receive the user's Discord email. Both login and linking go through the same Discord OAuth callback. All flows through the callback try to achieve the same end goal: a logged-in HMN user with a linked Discord account. Linking works the same as it ever has. Login, however, is different because we do not have a session ID to use as the OAuth state. To account for this, I have added a `pending_login` table that stores a secure unique ID and the eventual destination URL. These pending logins expire after 10 minutes. When we receive the OAuth callback, we look up the pending login by the OAuth `state` and immediately delete it. The destination URL will be used to redirect the user to the right place. If we have a `discord_user` entry for the OAuth'd Discord user, we immediately log the user into the associated HMN account. This is the typical login case. If we do not have a `discord_user`, but there is exactly one HMN user with the same email address as the Discord user, we will link the two accounts and log into the HMN account. (It is possible for multiple HMN accounts to have the same email, because we don't have a uniqueness constraint there. We fail the login in this case rather than link to the wrong account.) Finally, if no associated HMN user exists, a new one will be created. It will use the Discord user's username, email, and avatar. This user will have no password, but they can set or reset a password through the usual flows. Co-authored-by: Ben Visness <bvisness@gmail.com> Reviewed-on: https://git.handmade.network/hmn/hmn/pulls/106
2023-05-06 19:38:50 +00:00
<div id="login-popup" class="pa3 bt bb ba-ns bw1 b--theme-dark">
<form action="{{ .Header.LoginActionUrl }}" method="post" class="ma0 flex flex-column">
2021-08-28 13:41:09 +00:00
<input type="text" name="username" class="w-100" value="" placeholder="Username" />
<input type="password" name="password" class="w-100 mt1" value="" placeholder="Password" />
Add Discord login (#106) This leverages our existing Discord OAuth implementation. Any users with a linked Discord account will be able to log in immediately. When logging in, we request the `email` scope in addition to `identity`, so existing users will be prompted one time to accept the new permissions. On subsequent logins, Discord will skip the prompt. When linking your Discord account to an existing HMN account, we continue to only request the `identity` scope, so we do not receive the user's Discord email. Both login and linking go through the same Discord OAuth callback. All flows through the callback try to achieve the same end goal: a logged-in HMN user with a linked Discord account. Linking works the same as it ever has. Login, however, is different because we do not have a session ID to use as the OAuth state. To account for this, I have added a `pending_login` table that stores a secure unique ID and the eventual destination URL. These pending logins expire after 10 minutes. When we receive the OAuth callback, we look up the pending login by the OAuth `state` and immediately delete it. The destination URL will be used to redirect the user to the right place. If we have a `discord_user` entry for the OAuth'd Discord user, we immediately log the user into the associated HMN account. This is the typical login case. If we do not have a `discord_user`, but there is exactly one HMN user with the same email address as the Discord user, we will link the two accounts and log into the HMN account. (It is possible for multiple HMN accounts to have the same email, because we don't have a uniqueness constraint there. We fail the login in this case rather than link to the wrong account.) Finally, if no associated HMN user exists, a new one will be created. It will use the Discord user's username, email, and avatar. This user will have no password, but they can set or reset a password through the usual flows. Co-authored-by: Ben Visness <bvisness@gmail.com> Reviewed-on: https://git.handmade.network/hmn/hmn/pulls/106
2023-05-06 19:38:50 +00:00
<a class="db mt1" href="{{ .Header.ForgotPasswordUrl }}">Forgot your password?</a>
<input type="hidden" name="redirect" value="{{ $.CurrentUrl }}">
Add Discord login (#106) This leverages our existing Discord OAuth implementation. Any users with a linked Discord account will be able to log in immediately. When logging in, we request the `email` scope in addition to `identity`, so existing users will be prompted one time to accept the new permissions. On subsequent logins, Discord will skip the prompt. When linking your Discord account to an existing HMN account, we continue to only request the `identity` scope, so we do not receive the user's Discord email. Both login and linking go through the same Discord OAuth callback. All flows through the callback try to achieve the same end goal: a logged-in HMN user with a linked Discord account. Linking works the same as it ever has. Login, however, is different because we do not have a session ID to use as the OAuth state. To account for this, I have added a `pending_login` table that stores a secure unique ID and the eventual destination URL. These pending logins expire after 10 minutes. When we receive the OAuth callback, we look up the pending login by the OAuth `state` and immediately delete it. The destination URL will be used to redirect the user to the right place. If we have a `discord_user` entry for the OAuth'd Discord user, we immediately log the user into the associated HMN account. This is the typical login case. If we do not have a `discord_user`, but there is exactly one HMN user with the same email address as the Discord user, we will link the two accounts and log into the HMN account. (It is possible for multiple HMN accounts to have the same email, because we don't have a uniqueness constraint there. We fail the login in this case rather than link to the wrong account.) Finally, if no associated HMN user exists, a new one will be created. It will use the Discord user's username, email, and avatar. This user will have no password, but they can set or reset a password through the usual flows. Co-authored-by: Ben Visness <bvisness@gmail.com> Reviewed-on: https://git.handmade.network/hmn/hmn/pulls/106
2023-05-06 19:38:50 +00:00
<div class="mt2">
<input type="submit" value="Log In" class="w-100" />
</div>
<div class="mt3 tc">
<div class="b mb1">Third-party login</div>
<div class="flex flex-column g1">
<a href="{{ .Header.LoginWithDiscordUrl }}" class="db br2 overflow-hidden flex" title="Log in with Discord">
<img
src="{{ static "discord-login.svg" }}"
alt="Log in with Discord"
>
</a>
</div>
2021-03-18 01:25:06 +00:00
</div>
</form>
</div>
2021-03-26 03:33:00 +00:00
{{ end }}
</div>
2021-12-04 14:55:45 +00:00
<div class="menu-bar flex flex-column flex-row-ns justify-between w-100 {{ if .IsProjectPage }}project{{ end }}">
<div class="flex flex-column flex-row-ns items-center w-100">
2021-10-28 03:05:31 +00:00
{{ $itemsClass := "items self-stretch flex items-center justify-center justify-start-ns ml2-ns ml3-l" }}
2021-10-28 01:35:53 +00:00
{{ if .Header.Project }}
2021-12-04 14:55:45 +00:00
<a href="{{ .Header.HMNHomepageUrl }}" class="hmn-logo small bg-theme-dark flex-shrink-0">
2022-10-20 10:45:57 +00:00
Hand
made
2021-10-28 01:35:53 +00:00
</a>
2021-12-04 14:55:45 +00:00
<a href="{{ .Project.Url }}" class="flex-shrink-0">
2021-10-28 02:54:20 +00:00
<h2 class="mb0 mt2 mt0-ns mh3 mr0-ns tc tl-ns">{{ .Project.Name }}</h2>
2021-10-28 01:35:53 +00:00
</a>
{{ with .Header.Project }}
2021-12-04 14:55:45 +00:00
<div class="{{ $itemsClass }} w-100">
2021-10-28 01:35:53 +00:00
{{ if .HasBlog }}
2021-12-04 14:55:45 +00:00
<div class="root-item flex-shrink-0">
2021-10-28 01:35:53 +00:00
<a href="{{ .BlogUrl }}">Blog</a>
</div>
{{ end }}
{{ if .HasForums }}
2021-12-04 14:55:45 +00:00
<div class="root-item flex-shrink-0">
2021-10-28 01:35:53 +00:00
<a href="{{ .ForumsUrl }}">Forums</a>
</div>
{{ end }}
{{ if .HasEpisodeGuide }}
2021-12-04 14:55:45 +00:00
<div class="root-item flex-shrink-0">
2021-10-28 01:35:53 +00:00
<a href="{{ .EpisodeGuideUrl }}">Episode Guide</a>
</div>
{{ end }}
2021-12-04 14:55:45 +00:00
{{ if .CanEdit }}
<div class="flex-grow-1 dn db-ns"></div>
2021-12-04 14:55:45 +00:00
<div class="root-item flex-shrink-0">
<a href="{{ .EditUrl }}">Edit Project</a>
</div>
{{ end }}
2021-10-21 02:21:24 +00:00
</div>
2021-10-28 01:35:53 +00:00
{{ end }}
{{ else }}
<a href="{{ .Header.HMNHomepageUrl }}" class="hmn-logo big bg-theme-dark">
Handmade
</a>
<div class="{{ $itemsClass }}">
<div class="root-item">
<a href="{{ .Header.ProjectIndexUrl }}">Projects</a>
</div>
<div class="root-item">
2023-04-22 16:26:07 +00:00
<a aria-expanded="false" aria-controls="events-submenu" class="menu-dropdown-js" href="#">
Events <div class="dib svgicon ml1">{{ svg "chevron-down-thick" }}</div>
</a>
2023-04-22 16:26:07 +00:00
<div class="submenu b--theme-dark" id="events-submenu">
<a href="{{ .Header.JamsUrl }}">Jams</a>
2022-07-26 16:34:05 +00:00
<a href="{{ .Header.ConferencesUrl }}">Conferences</a>
<a href="{{ .Header.FishbowlUrl }}">Fishbowls</a>
2023-04-22 16:26:07 +00:00
<a href="{{ .Header.PodcastUrl }}">Podcast</a>
2022-12-16 04:59:57 +00:00
<a href="https://guide.handmade-seattle.com/s" target="_blank">Handmade Dev Show</a>
2021-10-28 01:35:53 +00:00
</div>
</div>
<div class="root-item">
<a href="{{ .Header.ForumsUrl }}">Forums</a>
</div>
<div class="root-item">
<a aria-expanded="false" aria-controls="resource-submenu" class="menu-dropdown-js" href="#">
Resources <div class="dib svgicon ml1">{{ svg "chevron-down-thick" }}</div>
</a>
<div class="submenu b--theme-dark" id="resource-submenu">
2022-11-05 21:23:12 +00:00
<a href="{{ .Header.EducationUrl }}">Education</a>
2021-10-28 01:35:53 +00:00
</div>
2021-10-21 02:21:24 +00:00
</div>
</div>
2021-10-28 01:35:53 +00:00
{{ end }}
2021-03-18 01:25:06 +00:00
</div>
<div class="dn flex-ns items-center f3">
<a class="svgicon svgicon-nofix" href="https://twitter.com/handmade_net/" target="_blank">{{ svg "twitter" }}</a>
<a class="svgicon svgicon-nofix ml2" href="https://discord.gg/hmn" target="_blank">{{ svg "discord" }}</a>
</div>
2021-03-18 01:25:06 +00:00
</div>
</header>
2023-01-17 03:22:23 +00:00
<div id="content-start"></div>
2021-03-18 01:25:06 +00:00
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
2021-10-21 02:21:24 +00:00
const header = document.querySelector('#site-header');
// set up dropdown stuff for mobile / touch
{
const rootItems = header.querySelectorAll('.root-item');
2021-03-18 01:25:06 +00:00
2021-10-21 02:21:24 +00:00
function clearDropdowns() {
for (const item of rootItems) {
item.classList.remove('clicked');
}
}
function clickDropdown(el) {
header.classList.add('clicked');
2021-10-21 02:21:24 +00:00
if (el.classList.contains('clicked')) {
clearDropdowns();
} else {
clearDropdowns();
el.classList.add('clicked');
}
}
2021-03-18 01:25:06 +00:00
2021-10-21 02:21:24 +00:00
for (const item of rootItems) {
if (item.querySelector('.submenu')) {
item.addEventListener('click', e => {
clickDropdown(item);
e.stopPropagation();
});
}
2021-03-18 01:25:06 +00:00
}
}
// dropdown accessiblity
{
const dropdowns = document.querySelectorAll('.menu-dropdown-js');
for(let i = 0; i < dropdowns.length; i++) {
let dropdown = dropdowns[i];
dropdown.addEventListener('click', e => {
e.preventDefault();
for(let j = 0; j < dropdowns.length; j++){
let each = dropdowns[j];
if(each != dropdown){
each.setAttribute("aria-expanded", false);
}
}
// getAttribute returns a string so we have to do it this way
var toSetTo = dropdown.getAttribute("aria-expanded") == "false" ? "true" : "false";
dropdown.setAttribute("aria-expanded", toSetTo);
console.log(dropdown);
});
}
}
2021-10-21 02:21:24 +00:00
// set up login form
{
const loginPopup = document.getElementById("login-popup");
const loginLink = document.getElementById("login-link");
if (loginPopup !== null) {
loginLink.onclick = (e) => {
e.preventDefault();
2021-10-21 02:21:24 +00:00
loginPopup.classList.toggle("open");
}
}
for (const time of document.querySelectorAll('time')) {
const d = new Date(Date.parse(time.dateTime));
time.title = d.toLocaleString();
}
}
2021-03-18 01:25:06 +00:00
});
</script>