feat: refine navigation and account access flow
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
export function mountAppBar({ onManageAccess } = {}) {
|
||||
const header = document.querySelector('#site-header');
|
||||
if (!header) throw new Error('Global app bar mount point is missing.');
|
||||
|
||||
header.innerHTML = `
|
||||
<a class="brand" href="/" aria-label="Our Recipe Atlas 홈">
|
||||
<span class="brand-mark brand-mark-small" aria-hidden="true">ORA</span>
|
||||
<span>Our Recipe Atlas</span>
|
||||
</a>
|
||||
<div class="profile">
|
||||
<img id="profile-image" class="avatar" alt="" hidden>
|
||||
<span id="profile-name"></span>
|
||||
<button id="access-menu-button" class="menu-button" type="button"
|
||||
aria-label="메뉴 열기" aria-controls="access-drawer"
|
||||
aria-expanded="false">
|
||||
<span></span><span></span><span></span>
|
||||
</button>
|
||||
</div>`;
|
||||
|
||||
header.insertAdjacentHTML('afterend', `
|
||||
<button id="drawer-backdrop" class="drawer-backdrop" type="button"
|
||||
aria-label="메뉴 닫기" hidden></button>
|
||||
<aside id="access-drawer" class="access-drawer" role="dialog" aria-modal="true"
|
||||
aria-labelledby="drawer-title" aria-hidden="true" hidden>
|
||||
<div class="drawer-heading">
|
||||
<div>
|
||||
<p class="section-number">MENU</p>
|
||||
<h2 id="drawer-title">메뉴</h2>
|
||||
</div>
|
||||
<button id="close-access-drawer" class="drawer-close" type="button"
|
||||
aria-label="메뉴 닫기">×</button>
|
||||
</div>
|
||||
<div class="drawer-account">
|
||||
<strong id="drawer-user-name"></strong>
|
||||
<span id="drawer-user-email"></span>
|
||||
</div>
|
||||
<div class="drawer-actions">
|
||||
<button id="open-access-overlay" class="drawer-action" type="button" hidden>
|
||||
<span>로그인 허용 계정 관리</span><small>관리자</small>
|
||||
</button>
|
||||
<button id="drawer-logout" class="drawer-action danger" type="button" data-logout>
|
||||
로그아웃
|
||||
</button>
|
||||
</div>
|
||||
</aside>`);
|
||||
|
||||
const elements = {
|
||||
menuButton: document.querySelector('#access-menu-button'),
|
||||
profileImage: document.querySelector('#profile-image'),
|
||||
profileName: document.querySelector('#profile-name'),
|
||||
drawer: document.querySelector('#access-drawer'),
|
||||
closeButton: document.querySelector('#close-access-drawer'),
|
||||
backdrop: document.querySelector('#drawer-backdrop'),
|
||||
manageAccessButton: document.querySelector('#open-access-overlay'),
|
||||
drawerUserName: document.querySelector('#drawer-user-name'),
|
||||
drawerUserEmail: document.querySelector('#drawer-user-email'),
|
||||
};
|
||||
|
||||
function setOpen(open) {
|
||||
elements.menuButton.setAttribute('aria-expanded', String(open));
|
||||
elements.drawer.setAttribute('aria-hidden', String(!open));
|
||||
elements.drawer.hidden = !open;
|
||||
elements.backdrop.hidden = !open;
|
||||
document.body.classList.toggle('drawer-open', open);
|
||||
if (open) elements.closeButton.focus();
|
||||
else elements.menuButton.focus();
|
||||
}
|
||||
|
||||
elements.menuButton.addEventListener('click', () => setOpen(true));
|
||||
elements.closeButton.addEventListener('click', () => setOpen(false));
|
||||
elements.backdrop.addEventListener('click', () => setOpen(false));
|
||||
elements.manageAccessButton.addEventListener('click', () => {
|
||||
setOpen(false);
|
||||
if (onManageAccess) onManageAccess();
|
||||
else window.location.assign('/?manage-access=1');
|
||||
});
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && !elements.drawer.hidden) setOpen(false);
|
||||
});
|
||||
|
||||
return {
|
||||
focusMenuButton() {
|
||||
elements.menuButton.focus();
|
||||
},
|
||||
setUser(user) {
|
||||
elements.profileName.textContent = user.name || user.email;
|
||||
if (user.picture) {
|
||||
elements.profileImage.src = user.picture;
|
||||
elements.profileImage.alt = `${user.name || user.email} 프로필`;
|
||||
elements.profileImage.hidden = false;
|
||||
}
|
||||
elements.drawerUserName.textContent = user.name || user.email;
|
||||
elements.drawerUserEmail.textContent = user.email;
|
||||
elements.manageAccessButton.hidden = !user.canManageAccess;
|
||||
},
|
||||
};
|
||||
}
|
||||
+20
-43
@@ -1,8 +1,11 @@
|
||||
import { apiRequest } from './api.js';
|
||||
import { mountAppBar } from './app-bar.js';
|
||||
import { bindLogout, loadSession } from './auth.js';
|
||||
import { searchRecipes } from './recipe-search.js';
|
||||
import { bindScrollToTop } from './scroll-to-top.js';
|
||||
|
||||
const appBar = mountAppBar({ onManageAccess: openAccessManager });
|
||||
|
||||
const elements = {
|
||||
importForm: document.querySelector('#import-form'),
|
||||
importStatus: document.querySelector('#import-status'),
|
||||
@@ -17,13 +20,6 @@ const elements = {
|
||||
tagFilterOptions: document.querySelector('#tag-filter-options'),
|
||||
toggleTagFilter: document.querySelector('#toggle-tag-filter'),
|
||||
clearTagFilter: document.querySelector('#clear-tag-filter'),
|
||||
accessMenuButton: document.querySelector('#access-menu-button'),
|
||||
accessDrawer: document.querySelector('#access-drawer'),
|
||||
closeAccessDrawer: document.querySelector('#close-access-drawer'),
|
||||
drawerBackdrop: document.querySelector('#drawer-backdrop'),
|
||||
drawerUserName: document.querySelector('#drawer-user-name'),
|
||||
drawerUserEmail: document.querySelector('#drawer-user-email'),
|
||||
openAccessOverlay: document.querySelector('#open-access-overlay'),
|
||||
accessOverlay: document.querySelector('#access-overlay'),
|
||||
closeAccessOverlay: document.querySelector('#close-access-overlay'),
|
||||
accessOverlayBackdrop: document.querySelector('#access-overlay-backdrop'),
|
||||
@@ -199,20 +195,7 @@ async function loadAllowedEmails() {
|
||||
}
|
||||
|
||||
function updatePageLock() {
|
||||
document.body.classList.toggle(
|
||||
'drawer-open',
|
||||
!elements.accessDrawer.hidden || !elements.accessOverlay.hidden,
|
||||
);
|
||||
}
|
||||
|
||||
function setAccessDrawerOpen(open) {
|
||||
elements.accessMenuButton.setAttribute('aria-expanded', String(open));
|
||||
elements.accessDrawer.setAttribute('aria-hidden', String(!open));
|
||||
elements.accessDrawer.hidden = !open;
|
||||
elements.drawerBackdrop.hidden = !open;
|
||||
updatePageLock();
|
||||
if (open) elements.closeAccessDrawer.focus();
|
||||
else elements.accessMenuButton.focus();
|
||||
document.body.classList.toggle('drawer-open', !elements.accessOverlay.hidden);
|
||||
}
|
||||
|
||||
function setAccessOverlayOpen(open) {
|
||||
@@ -221,7 +204,16 @@ function setAccessOverlayOpen(open) {
|
||||
elements.accessOverlayBackdrop.hidden = !open;
|
||||
updatePageLock();
|
||||
if (open) elements.accessEmail.focus();
|
||||
else elements.accessMenuButton.focus();
|
||||
else appBar.focusMenuButton();
|
||||
}
|
||||
|
||||
async function openAccessManager() {
|
||||
setAccessOverlayOpen(true);
|
||||
try {
|
||||
await loadAllowedEmails();
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function ingredientGroupTemplate(group, groupIndex) {
|
||||
@@ -444,28 +436,11 @@ elements.clearRecipeSearch.addEventListener('click', () => {
|
||||
renderRecipes();
|
||||
});
|
||||
|
||||
elements.accessMenuButton.addEventListener('click', () => {
|
||||
setAccessDrawerOpen(true);
|
||||
});
|
||||
|
||||
elements.openAccessOverlay.addEventListener('click', async () => {
|
||||
setAccessDrawerOpen(false);
|
||||
setAccessOverlayOpen(true);
|
||||
try {
|
||||
await loadAllowedEmails();
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
elements.closeAccessDrawer.addEventListener('click', () => setAccessDrawerOpen(false));
|
||||
elements.drawerBackdrop.addEventListener('click', () => setAccessDrawerOpen(false));
|
||||
elements.closeAccessOverlay.addEventListener('click', () => setAccessOverlayOpen(false));
|
||||
elements.accessOverlayBackdrop.addEventListener('click', () => setAccessOverlayOpen(false));
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key !== 'Escape') return;
|
||||
if (!elements.accessOverlay.hidden) setAccessOverlayOpen(false);
|
||||
else if (!elements.accessDrawer.hidden) setAccessDrawerOpen(false);
|
||||
});
|
||||
|
||||
elements.accessForm.addEventListener('submit', async (event) => {
|
||||
@@ -507,9 +482,11 @@ elements.allowedEmailList.addEventListener('click', async (event) => {
|
||||
bindLogout();
|
||||
bindScrollToTop(elements.scrollToTop);
|
||||
currentUser = await loadSession();
|
||||
elements.drawerUserName.textContent = currentUser.name || currentUser.email;
|
||||
elements.drawerUserEmail.textContent = currentUser.email;
|
||||
appBar.setUser(currentUser);
|
||||
await loadRecipes();
|
||||
if (currentUser.canManageAccess) {
|
||||
elements.openAccessOverlay.hidden = false;
|
||||
if (
|
||||
currentUser.canManageAccess
|
||||
&& new URLSearchParams(window.location.search).get('manage-access') === '1'
|
||||
) {
|
||||
await openAccessManager();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
const error = new URLSearchParams(window.location.search).get('error');
|
||||
|
||||
if (error === 'account-not-allowed') {
|
||||
document.querySelector('#login-alert').hidden = false;
|
||||
document.querySelector('#google-login').href = '/auth/google/select-account';
|
||||
document.querySelector('#google-login-label').textContent = '다른 Google 계정으로 다시 시도';
|
||||
}
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
import { apiRequest } from './api.js';
|
||||
import { mountAppBar } from './app-bar.js';
|
||||
import { bindLogout, loadSession } from './auth.js';
|
||||
import { bindScrollToTop } from './scroll-to-top.js';
|
||||
|
||||
const appBar = mountAppBar();
|
||||
const detail = document.querySelector('#recipe-detail');
|
||||
const toast = document.querySelector('#toast');
|
||||
const scrollToTop = document.querySelector('#scroll-to-top');
|
||||
@@ -221,7 +223,7 @@ function renderRecipe(recipe) {
|
||||
}
|
||||
|
||||
bindLogout();
|
||||
await loadSession();
|
||||
appBar.setUser(await loadSession());
|
||||
|
||||
try {
|
||||
const recipeId = decodeURIComponent(window.location.pathname.split('/').filter(Boolean).pop());
|
||||
|
||||
Reference in New Issue
Block a user