Skip to content

Commit

Permalink
refactor(frontend): menu and profile refactor (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dario-Au authored Jan 21, 2025
1 parent 070ae67 commit dc7cccd
Show file tree
Hide file tree
Showing 20 changed files with 269 additions and 56 deletions.
5 changes: 4 additions & 1 deletion frontend/app/.server/locales/gcweb-en.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"language": "English",
"app": {
"account": "Account",
"form": "Form",
"logout": "Logout",
"menu": "Menu",
"profile": "Profile"
"profile": "Profile",
"title": "Future SIR"
},
"nav": {
"skip-to-content": "Skip to main content",
Expand Down
5 changes: 4 additions & 1 deletion frontend/app/.server/locales/gcweb-fr.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"language": "Français",
"app": {
"account": "Compte",
"form": "Forme",
"logout": "Déconnexion",
"menu": "Menu",
"profile": "Profil"
"profile": "Profil",
"title": "Future SIR"
},
"nav": {
"skip-to-content": "Passer au contenu principal",
Expand Down
12 changes: 12 additions & 0 deletions frontend/app/.server/locales/protected-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,20 @@
"admin": {
"page-title": "Admin dashboard"
},
"dashboard": {
"sin": "SIN",
"sin-system": "<span>SIN</span> Dashboard System",
"system": "System",
"assigned-cases": "Affaires Attribuées",
"get-started": "Get Started"
},
"in-person": {
"title": "In-person SIN request",
"description": "Process an application or amendment"
},
"index": {
"admin-dashboard": "Admin dashboard",
"dashboard": "Dashboard",
"home": "Home",
"in-person": "Navigate to 'In person case' page",
"page-title": "Welcome to Future SIR (protected)",
Expand Down
12 changes: 12 additions & 0 deletions frontend/app/.server/locales/protected-fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,20 @@
"admin": {
"page-title": "Interface d'administration"
},
"dashboard": {
"sin": "SIN",
"sin-system": "<span>SIN</span> Interface Système",
"system": "Système",
"assigned-cases": "Affaires Attribuées",
"get-started": "Commencer Ici"
},
"in-person": {
"title": "Demande SIN en personne",
"description": "Traiter une demande ou une modification"
},
"index": {
"admin-dashboard": "Interface d'administration",
"dashboard": "Interface",
"home": "Accueil",
"in-person": "Accédez à la page «\u00a0Cas en personne\u00a0»",
"page-title": "Bienvenue sur Future SIR (protégée)",
Expand Down
29 changes: 29 additions & 0 deletions frontend/app/components/app-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useTranslation } from 'react-i18next';

import { LanguageSwitcher } from '~/components/language-switcher';
import { Menu } from '~/components/menu';
import { UserButton } from '~/components/user-button';

interface AppBarProps {
children: React.ReactNode;
name?: string;
profileItems?: React.ReactNode;
}

export function AppBar({ children, name, profileItems }: AppBarProps) {
const { t } = useTranslation(['gcweb']);

return (
<div className="bg-slate-700">
<div className="align-center container mx-auto flex flex-wrap justify-between">
<div className="align-center flex">
<Menu>{children}</Menu>
</div>
<div className="flex items-center space-x-4 text-right">
{name && <UserButton name={name}>{profileItems}</UserButton>}
<LanguageSwitcher>{t('gcweb:language-switcher.alt-lang')}</LanguageSwitcher>
</div>
</div>
</div>
);
}
9 changes: 8 additions & 1 deletion frontend/app/components/language-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { InlineLink } from '~/components/inline-link';
import { useLanguage } from '~/hooks/use-language';
import { useRoute } from '~/hooks/use-route';
import type { I18nRouteFile } from '~/i18n-routes';
import { cn } from '~/utils/tailwind-utils';

type LanguageSwitcherProps = Omit<ComponentProps<typeof InlineLink>, 'file' | 'lang' | 'reloadDocument' | 'to'>;

Expand All @@ -12,7 +13,13 @@ export function LanguageSwitcher({ className, children, ...props }: LanguageSwit
const { file } = useRoute();

return (
<InlineLink file={file as I18nRouteFile} lang={altLanguage} reloadDocument={true} {...props}>
<InlineLink
className={cn('text-white hover:text-blue-100 focus:text-blue-200 sm:text-lg', className)}
file={file as I18nRouteFile}
lang={altLanguage}
reloadDocument={true}
{...props}
>
{children}
</InlineLink>
);
Expand Down
8 changes: 5 additions & 3 deletions frontend/app/components/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ export function Menu({ className, children }: MenuProps) {
<DropdownMenu>
<DropdownMenuTrigger
className={cn(
'flex flex-nowrap space-x-2 rounded-b-md border-x border-b border-slate-700 bg-slate-700 px-4 py-2 text-lg text-white hover:bg-slate-800 hover:underline focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 aria-expanded:bg-slate-900 aria-expanded:text-white',
'flex h-full flex-nowrap space-x-2 bg-slate-700 px-2 text-white hover:bg-slate-600 hover:underline focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 aria-expanded:bg-slate-900 aria-expanded:text-white sm:space-x-3 sm:px-4',
className,
)}
>
<span id="menu-label">{t('gcweb:app.menu')}</span>
<FontAwesomeIcon icon={faChevronDown} className="my-auto size-5 fill-current" />
<span id="menu-label" className="my-auto py-2 sm:text-2xl">
{t('gcweb:app.title')}
</span>
<FontAwesomeIcon icon={faChevronDown} className="my-auto size-4" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-64 bg-slate-700">
{children}
Expand Down
43 changes: 22 additions & 21 deletions frontend/app/components/user-button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { useState } from 'react';

import { faUser } from '@fortawesome/free-solid-svg-icons';
import { faChevronDown, faRightFromBracket, faUser } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useTranslation } from 'react-i18next';

Expand All @@ -15,31 +13,34 @@ interface UserButtonProps {
}

export function UserButton({ className, children, name }: UserButtonProps) {
const [open, setOpen] = useState(false);
const { t } = useTranslation(['gcweb']);
const baseClassName = cn(
'my-0 flex h-10 w-10 items-center justify-center rounded-full text-white focus:outline-none focus:ring-2 focus:ring-slate-300',
open ? 'bg-slate-900 hover:bg-slate-800' : 'bg-slate-700 hover:bg-slate-600',
);

return (
<DropdownMenu onOpenChange={(open) => setOpen(open)}>
<DropdownMenu>
<DropdownMenuTrigger
asChild
title={t('gcweb:app.profile')}
aria-label={t('gcweb:app.profile')}
className={cn(baseClassName, className)}
aria-haspopup={true}
aria-expanded={open}
className={cn(
'flex h-full flex-nowrap space-x-2 bg-slate-600 px-2 text-sm text-white hover:bg-slate-500 hover:underline focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2 aria-expanded:bg-slate-800 aria-expanded:text-white sm:space-x-4 sm:px-4',
className,
)}
>
<button>
<FontAwesomeIcon icon={faUser} className="size-6" />
</button>
<div className="text-md my-auto flex flex-nowrap items-center space-x-2 py-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-white">
<FontAwesomeIcon icon={faUser} className="size-5 text-slate-700" />
</div>
<span id="menu-label" className="text-md hidden py-2 font-bold sm:block">
{name}
</span>
</div>
<FontAwesomeIcon icon={faChevronDown} className="my-auto size-4" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-64 !bg-slate-700">
<DropdownMenuContent align="end" className="w-64 bg-slate-700">
<UserName name={name} />
<MenuItem file="routes/protected/index.tsx">{t('gcweb:app.profile')}</MenuItem>
{children}
<MenuItem to="/auth/logout">{t('gcweb:app.logout')}</MenuItem>
<MenuItem to="/auth/logout" className="text-md flex justify-between">
{t('gcweb:app.logout')}
<FontAwesomeIcon icon={faRightFromBracket} className="my-auto size-8" />
</MenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
Expand All @@ -49,7 +50,7 @@ function UserName({ name }: { name?: string }) {
return (
<>
{name !== undefined && (
<DropdownMenuLabel className="text-md flex items-center border-b-2 border-slate-600 px-3 py-2 text-gray-300">
<DropdownMenuLabel className="text-md flex items-center border-b-2 border-slate-600 px-3 py-2 text-gray-300 sm:hidden">
<FontAwesomeIcon icon={faUser} className="mr-2 size-4" />
{name}
</DropdownMenuLabel>
Expand Down
5 changes: 5 additions & 0 deletions frontend/app/i18n-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ export const i18nRoutes = [
},
{
id: 'PROT-0003',
file: 'routes/protected/request.tsx',
paths: { en: '/en/protected/request', fr: '/fr/protege/requete' },
},
{
id: 'PROT-0004',
file: 'routes/protected/person-case/first-name.tsx',
paths: { en: '/en/protected/person-case/first-name', fr: '/fr/protege/person-case/first-name' },
},
Expand Down
30 changes: 23 additions & 7 deletions frontend/app/routes/protected/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { RouteHandle } from 'react-router';

import { faChevronRight } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Trans, useTranslation } from 'react-i18next';

import type { Route } from './+types/index';

import { requireAuth } from '~/.server/utils/auth-utils';
import { Menu, MenuItem } from '~/components/menu';
import { ButtonLink } from '~/components/button-link';
import { getFixedT } from '~/i18n-config.server';
import { handle as parentHandle } from '~/routes/protected/layout';

Expand All @@ -28,12 +30,26 @@ export default function Index() {

return (
<div className="mb-8">
<Menu>
<MenuItem to="/">{t('protected:index.home')}</MenuItem>
<MenuItem file="routes/protected/admin.tsx">{t('protected:index.admin-dashboard')}</MenuItem>
<MenuItem file="routes/public/index.tsx">{t('protected:index.public')}</MenuItem>
<MenuItem file="routes/protected/person-case/first-name.tsx">{t('protected:index.in-person')}</MenuItem>
</Menu>
<h1 className="mt-8 text-2xl font-bold text-slate-700">
<Trans
i18nKey="protected:dashboard.sin-system"
components={{ span: <span className="underline decoration-red-800 underline-offset-8" /> }}
/>
</h1>
<h2 className="mb-2 mt-10 text-lg font-bold text-slate-700">{t('protected:dashboard.assigned-cases')}</h2>
<ButtonLink className="w-72" file="routes/protected/request.tsx">
{t('gcweb:app.form')}
</ButtonLink>
<h2 className="mb-2 mt-10 text-2xl font-bold text-slate-700">{t('protected:dashboard.get-started')}</h2>
<ButtonLink className="flex w-80 items-center justify-between rounded-none" file="routes/protected/request.tsx">
<span className="text-bold flex flex-col text-slate-700">
<span className="text-xl">{t('protected:in-person.title')}</span>
<span>{t('protected:in-person.description')}</span>
</span>
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-slate-700">
<FontAwesomeIcon icon={faChevronRight} className="my-auto size-4 text-white" />
</div>
</ButtonLink>
<p className="mt-8 text-lg">
<Trans
i18nKey="protected:index.resources"
Expand Down
20 changes: 12 additions & 8 deletions frontend/app/routes/protected/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { useTranslation } from 'react-i18next';
import type { Route } from './+types/layout';

import { requireAuth } from '~/.server/utils/auth-utils';
import { AppBar } from '~/components/app-bar';
import { AppLink } from '~/components/app-link';
import { LanguageSwitcher } from '~/components/language-switcher';
import { MenuItem } from '~/components/menu';
import { PageDetails } from '~/components/page-details';
import { UserButton } from '~/components/user-button';
import { useLanguage } from '~/hooks/use-language';
import { useRoute } from '~/hooks/use-route';

Expand All @@ -24,14 +24,14 @@ export function loader({ context, request }: Route.LoaderArgs) {

export default function Layout({ loaderData }: Route.ComponentProps) {
const { currentLanguage } = useLanguage();
const { t } = useTranslation(['gcweb']);
const { t } = useTranslation(['gcweb', 'protected']);
const { id: pageId } = useRoute();

const { BUILD_DATE, BUILD_VERSION } = globalThis.__appEnvironment;

return (
<>
<header className="border-b-[3px] border-slate-700 print:hidden">
<header className="print:hidden">
<div id="wb-bnr">
<div className="container flex items-center justify-between gap-6 py-2.5 sm:py-3.5">
<AppLink to="https://canada.ca/">
Expand All @@ -44,12 +44,16 @@ export default function Layout({ loaderData }: Route.ComponentProps) {
decoding="async"
/>
</AppLink>
<div className="flex items-center space-x-4 text-right">
<LanguageSwitcher>{t('gcweb:language-switcher.alt-lang')}</LanguageSwitcher>
<UserButton name={loaderData.name?.toString()} />
</div>
</div>
</div>
<AppBar
name={loaderData.name?.toString()}
profileItems={<MenuItem file="routes/protected/index.tsx">{t('protected:index.dashboard')}</MenuItem>}
>
<MenuItem to="/">{t('protected:index.home')}</MenuItem>
<MenuItem file="routes/protected/admin.tsx">{t('protected:index.admin-dashboard')}</MenuItem>
<MenuItem file="routes/public/index.tsx">{t('protected:index.public')}</MenuItem>
</AppBar>
</header>
<main className="container">
<Outlet />
Expand Down
34 changes: 34 additions & 0 deletions frontend/app/routes/protected/request.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { RouteHandle } from 'react-router';

import { useTranslation } from 'react-i18next';

import type { Route } from './+types/request';

import { requireAuth } from '~/.server/utils/auth-utils';
import { getFixedT } from '~/i18n-config.server';
import { handle as parentHandle } from '~/routes/protected/layout';

export const handle = {
i18nNamespace: [...parentHandle.i18nNamespace, 'protected'],
} as const satisfies RouteHandle;

export async function loader({ context, request }: Route.LoaderArgs) {
requireAuth(context.session, new URL(request.url), ['user']);
const t = await getFixedT(request, handle.i18nNamespace);
return { documentTitle: t('protected:index.page-title') };
}

export function meta({ data }: Route.MetaArgs) {
return [{ title: data.documentTitle }];
}

export default function Request() {
const { t } = useTranslation(handle.i18nNamespace);

return (
<div className="mb-8">
<h2 className="mt-8 text-xl">{t('gcweb:app.form')}</h2>
<hr className="my-4 text-slate-700" />
</div>
);
}
4 changes: 0 additions & 4 deletions frontend/app/routes/public/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next';

import type { Route } from './+types/index';

import { Menu, MenuItem } from '~/components/menu';
import { PageTitle } from '~/components/page-title';
import { getFixedT } from '~/i18n-config.server';
import { handle as parentHandle } from '~/routes/public/layout';
Expand All @@ -27,9 +26,6 @@ export default function Index() {

return (
<>
<Menu>
<MenuItem file="routes/protected/index.tsx">{t('public:index.navigate')}</MenuItem>
</Menu>
<PageTitle>{t('public:index.page-title')}</PageTitle>
<p className="mt-8">{t('public:index.about')}</p>
</>
Expand Down
Loading

0 comments on commit dc7cccd

Please sign in to comment.