From f1c18efe3553abd7c5157685c05e284c2103f991 Mon Sep 17 00:00:00 2001 From: Ash Monty Date: Wed, 6 Jul 2022 11:12:31 +0200 Subject: [PATCH 01/11] feat: add user popout to navbar --- public/assets/css/aprilfools.css | 21 -- public/assets/css/dropdown.css | 45 +--- public/assets/css/main.css | 131 +++++++++--- public/assets/js/header-handler.js | 85 ++++++++ public/assets/js/locale-dropdown-handler.js | 64 ------ src/routers/account.js | 11 + src/routers/aprilfools.js | 20 +- src/routers/blog.js | 50 +++-- src/routers/docs.js | 54 +++-- src/routers/home.js | 32 +-- src/routers/localization.js | 21 +- src/routers/progress.js | 27 ++- src/util.js | 43 +++- views/layouts/blog-opengraph.handlebars | 1 - views/layouts/main.handlebars | 2 - views/partials/header.handlebars | 225 ++++++++++++-------- 16 files changed, 498 insertions(+), 334 deletions(-) create mode 100644 public/assets/js/header-handler.js delete mode 100644 public/assets/js/locale-dropdown-handler.js diff --git a/public/assets/css/aprilfools.css b/public/assets/css/aprilfools.css index 4da6b07..4dbb2db 100644 --- a/public/assets/css/aprilfools.css +++ b/public/assets/css/aprilfools.css @@ -79,27 +79,6 @@ color: var(--text-secondary); } -@media screen and (max-width: 1064px) { - .selected-locale .locale-names { - display: none; - } - - .selected-locale { - width: 80px; - margin-left: auto; - margin-right: 12px; - } - - .locale-dropdown { - width: fit-content; - } - - .select-box .options-container { - width: 150px; - right: 12px; - } -} - @media screen and (max-width: 946px) { header nav a:not(.keep-on-mobile) { display: none; diff --git a/public/assets/css/dropdown.css b/public/assets/css/dropdown.css index 1b0860d..3da4a25 100644 --- a/public/assets/css/dropdown.css +++ b/public/assets/css/dropdown.css @@ -1,6 +1,5 @@ .select-box { display: flex; - width: 188px; flex-direction: column; position: relative; user-select: none; @@ -12,7 +11,7 @@ .select-box .options-container { max-height: 0; - width: 100%; + width: fit-content; opacity: 0; transition: all 0.4s; overflow: hidden; @@ -20,34 +19,8 @@ background-color: var(--btn-secondary); order: 1; position: absolute; - top: 50px; -} - -.selected-locale { - margin-bottom: 8px; - position: relative; - width: 188px; - height: 45px; - border-radius: 5px; - display: flex; - align-items: center; - background-color: var(--btn-secondary); - color: white; - order: 0; -} - -.selected-locale::after { - content: ""; - width: 1.2rem; - height: 1.2rem; - background: url("/assets/images/down-arrow.svg"); - position: absolute; - right: 15px; - top: 50%; - transition: transform 150ms; - transform: translateY(-50%); - background-size: contain; - background-position: center; + top: 48px; + right: 0; } .select-box .option .item { @@ -67,25 +40,21 @@ overflow-y: auto; } -.select-box .options-container.active + .selected-locale::after { +.select-box .options-container.active + .locale-dropdown-toggle::after { transform: translateY(-50%) rotateX(180deg); } .select-box .options-container::-webkit-scrollbar { width: 8px; - background: #0d141f; - background: #81878f; - background: #f1f2f3; + background: var(--btn-secondary); border-radius: 0 5px 5px 0; } .select-box .options-container::-webkit-scrollbar-thumb { - background: #525861; - background: #81878f; + background: var(--text-secondary); border-radius: 0 5px 5px 0; } -.select-box .option, -.selected-locale { +.select-box .option { padding: 12px 15px; cursor: pointer; } diff --git a/public/assets/css/main.css b/public/assets/css/main.css index f65167b..e9ce83c 100644 --- a/public/assets/css/main.css +++ b/public/assets/css/main.css @@ -94,10 +94,108 @@ header nav a:hover { transition: color 50ms ease-in-out; } -.locale-dropdown { +header .right-section { + display: grid; + grid-auto-flow: column; + grid-gap: 24px; margin-left: auto; z-index: 2; - height: 45px; + color: var(--text-secondary); +} + +header .locale-dropdown-toggle { + width: fit-content; + height: 24px; + padding: 0; + margin: auto; + transition: color 150ms; + cursor: pointer; +} +header .locale-dropdown-toggle:hover, +header .locale-dropdown-toggle.active { + color: var(--text); +} + +header .user-widget-wrapper { + height: 24px; +} + +header .user-widget-wrapper a.login-link { + color: var(--text-secondary); + text-decoration: none; + transition: color 150ms; +} +header .user-widget-wrapper a.login-link:hover { + color: var(--text); +} + +header .user-widget-wrapper.logged-in { + position: relative; + width: 32px; + height: 32px; +} +header .user-widget-wrapper.logged-in .user-widget-toggle { + width: 32px; + height: 32px; + background: var(--text-secondary-2); + border-radius: 50%; + overflow: hidden; + cursor: pointer; +} +header .user-widget-wrapper .user-widget-toggle img, +header .user-widget .user-avatar img { + width: 100%; + height: 100%; +} +header .user-widget { + max-height: 0; + overflow: hidden; + + box-sizing: border-box; + transition: max-height 300ms, padding 200ms, opacity 150ms; + + position: absolute; + right: 0; + top: 48px; + padding: 0; + background: #2a2f50; + border-radius: 8px; + text-align: center; + opacity: 0; + + box-shadow: 0 0 10px -2px #111531; +} +header .user-widget.active { + max-height: 100vh; + padding: 36px; + opacity: 1; +} +header .user-widget .user-avatar { + width: 128px; + height: 128px; + margin: auto; + background: var(--text-secondary-2); + border-radius: 50%; + overflow: hidden; +} +header .user-widget .user-info { + margin-top: 12px; +} +header .user-widget .user-info .mii-name { + color: var(--text); +} +header .user-widget .buttons { + margin-top: 12px; +} +header .user-widget .button { + margin-top: 12px; + width: 100%; + padding: 8px 60px; + cursor: pointer; +} +header .user-widget .button.logout { + background: #383f6b; + color: var(--text); } /* Misc */ @@ -1118,25 +1216,6 @@ footer div.discord-server-card svg { margin: auto !important; } - .selected-locale .locale-names { - display: none; - } - - .selected-locale { - width: 80px; - margin-left: auto; - margin-right: 12px; - } - - .locale-dropdown { - width: fit-content; - } - - .select-box .options-container { - width: 150px; - right: 12px; - } - footer { grid-template-columns: 1fr; grid-template-rows: repeat(4, fit-content(100%)); @@ -1168,13 +1247,3 @@ footer div.discord-server-card svg { margin: 0 10px; } } - -@media screen and (max-width: 330px) { - .locale-dropdown .selected-locale { - width: 50px; - } - .locale-dropdown .selected-locale::after { - display: none; - } - -} diff --git a/public/assets/js/header-handler.js b/public/assets/js/header-handler.js new file mode 100644 index 0000000..263de63 --- /dev/null +++ b/public/assets/js/header-handler.js @@ -0,0 +1,85 @@ +/* eslint-disable no-undef, no-unused-vars */ + +// Account widget handler +const userWidgetToggle = document.querySelector('.user-widget-toggle') ; +const userWidget = document.querySelector('.user-widget'); + +// Open widget on click, close locale dropdown +userWidgetToggle?.addEventListener('click', () => { + userWidget.classList.toggle('active'); + localeOptionsContainer.classList.toggle('active'); + localeDropdownToggle.classList.toggle('active'); +}); + +// Locale dropdown handler +function localeDropdownHandler(selectedLocale) { + document.cookie = `preferredLocale=${selectedLocale};max-age=31536000`; + window.location.reload(); +} + +const localeDropdown = document.querySelector( + '.locale-dropdown[data-dropdown]' +); +const localeDropdownOptions = document.querySelectorAll( + '.locale-dropdown[data-dropdown] .options-container' +); +const localeDropdownToggle = document.querySelector('.locale-dropdown-toggle'); + +const localeOptionsContainer = localeDropdown.querySelector('.options-container'); +const localeOptionsList = localeDropdown.querySelectorAll('.option'); + +// click dropdown element will open dropdown +localeDropdownToggle.addEventListener('click', () => { + localeOptionsContainer.classList.toggle('active'); + localeDropdownToggle.classList.toggle('active'); +}); + +// clicking on any option will close dropdown and change value +localeOptionsList.forEach((option) => { + option.addEventListener('click', () => { + localeDropdownToggle.classList.remove('active'); + localeOptionsContainer.classList.remove('active'); + const selectedLocale = option.querySelector('label').getAttribute('for'); + localeDropdownHandler(selectedLocale); + }); +}); + +// close all dropdowns on scroll +document.addEventListener('scroll', () => { + localeDropdownOptions.forEach((el) => el.classList.remove('active')); + localeDropdownToggle.classList.remove('active'); + + userWidget.classList.remove('active'); +}); + +// click outside of dropdown will close all dropdowns +document.addEventListener('click', (e) => { + const targetElement = e.target; + + let found = false; + if ( + localeDropdown == targetElement || + localeDropdown.contains(targetElement) + ) { + found = true; + userWidget.classList.remove('active'); + } + + if ( + userWidget == targetElement || + userWidget.contains(targetElement) || + userWidgetToggle == targetElement || + userWidgetToggle.contains(targetElement) + ) { + found = true; + localeDropdownToggle.classList.remove('active'); + localeOptionsContainer.classList.remove('active'); + } + + if (found) return; + + // click outside of dropdowns + userWidget.classList.remove('active'); + localeDropdownToggle.classList.remove('active'); + localeOptionsContainer.classList.remove('active'); +}); diff --git a/public/assets/js/locale-dropdown-handler.js b/public/assets/js/locale-dropdown-handler.js deleted file mode 100644 index bebec0a..0000000 --- a/public/assets/js/locale-dropdown-handler.js +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable no-undef, no-unused-vars */ -function setDefaultDropdownLocale(localeString) { - try { - const selected = document.querySelector('.selected-locale'); - let item = document.querySelector(`label[for=${localeString}`); - if (!item) { // if locale can't be found, default to en-US - localeString = 'en-US'; - item = document.querySelector(`label[for=${localeString}`); - } - selected.innerHTML = item.innerHTML; - } catch (e) {} // If it errors it's probably because there isn't a navbar in the view -} - -function localeDropdownHandler(selectedLocale) { - document.cookie = `preferredLocale=${selectedLocale};max-age=31536000`; - window.location.reload(); -} - -const dropdowns = document.querySelectorAll('*[data-dropdown]'); -const dropdownOptions = document.querySelectorAll( - '*[data-dropdown] .options-container' -); - -dropdowns.forEach((el) => { - const selected = el.querySelector('.selected-locale'); - const optionsContainer = el.querySelector('.options-container'); - const optionsList = el.querySelectorAll('.option'); - - // click dropdown element will open dropdown - selected.addEventListener('click', () => { - optionsContainer.classList.toggle('active'); - }); - - // clicking on any option will close dropdown and change value - optionsList.forEach((option) => { - option.addEventListener('click', () => { - selected.innerHTML = option.querySelector('label').innerHTML; - optionsContainer.classList.remove('active'); - const selectedLocale = option.querySelector('label').getAttribute('for'); - localeDropdownHandler(selectedLocale); - }); - }); -}); - -// close all dropdowns on scroll -document.addEventListener('scroll', () => { - dropdownOptions.forEach((el) => el.classList.remove('active')); -}); - -// click outside of dropdown will close all dropdowns -document.addEventListener('click', (e) => { - const targetElement = e.target; - - // check if target is from a dropdown - let found = false; - dropdowns.forEach((v) => { - if (v == targetElement || v.contains(targetElement)) found = true; - }); - - if (found) return; - - // click outside of dropdowns - dropdownOptions.forEach((el) => el.classList.remove('active')); -}); diff --git a/src/routers/account.js b/src/routers/account.js index e6c6873..08fdf4d 100644 --- a/src/routers/account.js +++ b/src/routers/account.js @@ -193,6 +193,8 @@ router.get('/', async (request, response) => { renderData.discordAuthURL = discordAuthURL; } + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + response.render('account/account', renderData); }); @@ -297,6 +299,15 @@ router.post('/register', async (request, response) => { response.redirect('/account'); }); +router.get('/logout', async(_request, response) => { + response.clearCookie('refresh_token', { domain: '.pretendo.network' }); + response.clearCookie('access_token', { domain: '.pretendo.network' }); + response.clearCookie('token_type', { domain: '.pretendo.network' }); + response.clearCookie('ph', { domain: '.pretendo.network' }); + + response.redirect('/'); +}); + router.get('/connect/discord', async (request, response) => { let tokens; try { diff --git a/src/routers/aprilfools.js b/src/routers/aprilfools.js index c44c745..818070f 100644 --- a/src/routers/aprilfools.js +++ b/src/routers/aprilfools.js @@ -4,14 +4,20 @@ const router = new Router(); router.get('/', async (request, response) => { - const reqLocale = request.locale; - const locale = util.getLocale(reqLocale.region, reqLocale.language); - - response.render('aprilfools', { + const renderData = { layout: 'main', - locale, - localeString: reqLocale.toString(), - }); + locale: util.getLocale(request.locale.region, request.locale.language), + localeString: request.locale.toString(), + }; + + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + + if (renderData.isLoggedIn) { + const account = await util.getAccount(request, response); + renderData.account = account; + } + + response.render('aprilfools', renderData); }); module.exports = router; diff --git a/src/routers/blog.js b/src/routers/blog.js index c35afc8..cf1528a 100644 --- a/src/routers/blog.js +++ b/src/routers/blog.js @@ -36,18 +36,21 @@ const postList = () => { }; router.get('/', async (request, response) => { - - const reqLocale = request.locale; - const locale = util.getLocale(reqLocale.region, reqLocale.language); - - const localeString = reqLocale.toString(); - - response.render('blog/blog', { + const renderData = { layout: 'main', - locale, - localeString, - postList - }); + locale: util.getLocale(request.locale.region, request.locale.language), + localeString: request.locale.toString(), + postList, + }; + + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + + if (renderData.isLoggedIn) { + const account = await util.getAccount(request, response); + renderData.account = account; + } + + response.render('blog/blog', renderData); }); // RSS feed @@ -69,10 +72,19 @@ router.get('/feed.xml', async (request, response) => { router.get('/:slug', async (request, response, next) => { - const reqLocale = request.locale; - const locale = util.getLocale(reqLocale.region, reqLocale.language); + const renderData = { + layout: 'blog-opengraph', + locale: util.getLocale(request.locale.region, request.locale.language), + localeString: request.locale.toString(), + postList, + }; - const localeString = reqLocale.toString(); + renderData.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token && request.cookies.ph; + + if (renderData.isLoggedIn) { + const account = await util.getAccount(request, response); + renderData.account = account; + } // Get the name of the post from the URL const postName = request.params.slug; @@ -89,6 +101,7 @@ router.get('/:slug', async (request, response, next) => { // Convert the post info into JSON and separate it and the content // eslint-disable-next-line prefer-const let { data: postInfo, content } = matter(rawPost); + renderData.postInfo = postInfo; // Replace [yt-iframe](videoID) with the full