Skip to content

Commit

Permalink
Merge pull request #8 from nitic-pbl-p8/7/add-header-for-auth-guarded…
Browse files Browse the repository at this point in the history
…-page
  • Loading branch information
ReoHakase authored Mar 5, 2024
2 parents 5f95be7 + 3fced9a commit 419c5b4
Show file tree
Hide file tree
Showing 19 changed files with 401 additions and 66 deletions.
3 changes: 0 additions & 3 deletions apps/website/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
/** @type {import('next').NextConfig} */
const config = {
experimental: {
typedRoutes: true,
},
images: {
remotePatterns: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import type { NextPage } from 'next';
import { cookies } from 'next/headers';
import { InAppHeaderRouteIndicatorDivider, InAppHeaderRouteIndicatorIcon, InAppHeaderRouteIndicatorLabel } from '#website/layout/global/header';
import { findUserUseCase } from '#website/use-case/find-user';
import { findUserLostItemsUseCase } from '~website/src/use-case/find-user-lost-items';

const RouteIndicator: NextPage = async () => {
const cookieStore = cookies();

const supabase = createServerComponentClient({ cookies: () => cookieStore });
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return null;
}

const foundUser = await findUserUseCase(user.id);
if (!foundUser) {
return null;
}

const userLostItem = await findUserLostItemsUseCase(foundUser.authId);
if (!userLostItem) {
return null;
}

return userLostItem.currentTargetLostItem && foundUser.lostAndFoundState !== 'NONE' ? (
<>
<InAppHeaderRouteIndicatorDivider />
{!!userLostItem.currentTargetLostItem.lostItem.imageUrls[0] && (
<InAppHeaderRouteIndicatorIcon
src={userLostItem.currentTargetLostItem.lostItem.imageUrls[0]}
alt={`An image of ${userLostItem.currentTargetLostItem.lostItem.title}`}
/>
)}
<InAppHeaderRouteIndicatorLabel>{userLostItem.currentTargetLostItem.lostItem.title}</InAppHeaderRouteIndicatorLabel>
</>
) : (
<>
<InAppHeaderRouteIndicatorDivider />
<InAppHeaderRouteIndicatorLabel>Overview</InAppHeaderRouteIndicatorLabel>
</>
);
};

export default RouteIndicator;
5 changes: 5 additions & 0 deletions apps/website/src/app/(with-auth)/@routeIndicator/default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ReactNode } from 'react';

const RouteIndicatorPage = (): ReactNode => null;

export default RouteIndicatorPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ReactNode } from 'react';

const RouteIndicatorPage = (): ReactNode => null;

export default RouteIndicatorPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ReactNode } from 'react';

const RouteIndicatorPage = (): ReactNode => null;

export default RouteIndicatorPage;
31 changes: 25 additions & 6 deletions apps/website/src/app/(with-auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import type { NextPage } from 'next';
import { cookies } from 'next/headers';
import type { ReactNode } from 'react';
import { InAppHeader } from '#website/layout/global/header';
import { AuthGuardProvider } from '#website/layout/with-auth/auth-guard-provider';
import { findUserUseCase } from '#website/use-case/find-user';

type WithAuthLayoutProps = {
children: ReactNode;
routeIndicator: ReactNode;
};

const WithAuthLayout: NextPage<WithAuthLayoutProps> = ({ children }) => (
<>
<AuthGuardProvider />
{children}
</>
);
const WithAuthLayout: NextPage<WithAuthLayoutProps> = async ({ children, routeIndicator }) => {
// HACK: To avoid next build errors, functions that depend on async contexts need to be called outside the function that creates the new execution context.
// ref: https://nextjs.org/docs/messages/dynamic-server-error
const cookieStore = cookies();

const supabase = createServerComponentClient({ cookies: () => cookieStore });
const {
data: { user },
} = await supabase.auth.getUser();

const foundUser = user && (await findUserUseCase(user.id));

return (
<>
<InAppHeader user={foundUser}>{routeIndicator}</InAppHeader>
<AuthGuardProvider />
{children}
</>
);
};

export default WithAuthLayout;
File renamed without changes.
32 changes: 32 additions & 0 deletions apps/website/src/app/(with-no-auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import type { NextPage } from 'next';
import { cookies } from 'next/headers';
import type { ReactNode } from 'react';
import { Header } from '#website/layout/global/header';
import { findUserUseCase } from '#website/use-case/find-user';

type WithNoAuthLayoutProps = {
children: ReactNode;
};

const WithNoAuthLayout: NextPage<WithNoAuthLayoutProps> = async ({ children }) => {
// HACK: To avoid next build errors, functions that depend on async contexts need to be called outside the function that creates the new execution context.
// ref: https://nextjs.org/docs/messages/dynamic-server-error
const cookieStore = cookies();

const supabase = createServerComponentClient({ cookies: () => cookieStore });
const {
data: { user },
} = await supabase.auth.getUser();

const foundUser = user && (await findUserUseCase(user.id));

return (
<>
<Header user={foundUser} />
{children}
</>
);
};

export default WithNoAuthLayout;
File renamed without changes.
60 changes: 21 additions & 39 deletions apps/website/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,37 @@ import { firaCode, getFontVariables, notoSans } from '@lockerai/core/font/family
import { getBaseUrl } from '@lockerai/core/util/get-base-url';
import { colors } from '@lockerai/design-token';
import { cn } from '@lockerai/tailwind';
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import type { Metadata, NextPage, Viewport } from 'next';
import { cookies } from 'next/headers';
import type { ReactNode } from 'react';
import { UrqlProvider } from '#website/infra/urql/ssr';
import { Footer } from '#website/layout/global/footer';
import { Header } from '#website/layout/global/header';
import { findUserUseCase } from '#website/use-case/find-user';
import '#website/style/global.css';

type RootLayoutProps = {
children: ReactNode;
};

const RootLayout: NextPage<RootLayoutProps> = async ({ children }) => {
// HACK: To avoid next build errors, functions that depend on async contexts need to be called outside the function that creates the new execution context.
// ref: https://nextjs.org/docs/messages/dynamic-server-error
const cookieStore = cookies();

const supabase = createServerComponentClient({ cookies: () => cookieStore });
const {
data: { user },
} = await supabase.auth.getUser();

const foundUser = user && (await findUserUseCase(user.id));

return (
<html lang="en" suppressHydrationWarning>
<head />
<body className={cn(getFontVariables([firaCode, notoSans]), 'relative bg-sage-1 font-sans')}>
<div
aria-hidden
className={cn(
'absolute -z-20 h-full w-full bg-grid-light-green-7/50 dark:bg-grid-dark-green-7/50',
'from-pure to-[70%] [-webkit-mask-image:linear-gradient(to_bottom,var(--tw-gradient-stops))] [mask-image:linear-gradient(to_bottom,var(--tw-gradient-stops))]',
)}
/>
<UrqlProvider>
<ThemeProvider attribute="data-theme" enableSystem defaultTheme="system">
<Header user={foundUser} />
{children}
<Footer />
<Sonner />
</ThemeProvider>
</UrqlProvider>
</body>
</html>
);
};
const RootLayout: NextPage<RootLayoutProps> = async ({ children }) => (
<html lang="en" suppressHydrationWarning>
<head />
<body className={cn(getFontVariables([firaCode, notoSans]), 'relative bg-sage-1 font-sans')}>
<div
aria-hidden
className={cn(
'absolute -z-20 h-full w-full bg-grid-light-green-7/50 dark:bg-grid-dark-green-7/50',
'from-pure to-[70%] [-webkit-mask-image:linear-gradient(to_bottom,var(--tw-gradient-stops))] [mask-image:linear-gradient(to_bottom,var(--tw-gradient-stops))]',
)}
/>
<UrqlProvider>
<ThemeProvider attribute="data-theme" enableSystem defaultTheme="system">
{children}
<Footer />
<Sonner />
</ThemeProvider>
</UrqlProvider>
</body>
</html>
);

export default RootLayout;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Meta, StoryObj } from '@storybook/react';
import { HeaderLink } from './header-link';

type Story = StoryObj<typeof HeaderLink>;

const meta = {
component: HeaderLink,
args: {
href: '/dashboard',
children: 'Overview',
},
} satisfies Meta<typeof HeaderLink>;

export default meta;

export const Default: Story = {};

export const Selected: Story = {
parameters: {
nextjs: {
appDirectory: true,
navigation: {
pathname: '/dashboard',
},
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Link } from '#core/component/link';
import { type VariantProps, cn, tv } from '@lockerai/tailwind';
import { usePathname } from 'next/navigation';
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
import { useMemo } from 'react';

const headerLinkVariant = tv({
base: 'flex items-center justify-center rounded-t-lg p-3 font-bold hover:bg-sage-3',
variants: {
selected: {
true: 'border-b-2 border-sage-12 text-sage-12',
false: 'text-sage-11',
},
},
defaultVariants: {
selected: false,
},
});

type HeaderLinkProps = ComponentPropsWithoutRef<typeof Link> & VariantProps<typeof headerLinkVariant>;

export const HeaderLink = ({ children, className, ...props }: HeaderLinkProps): ReactNode => {
// Retrieve the current path starting with /.
// Refer: https://nextjs.org/docs/app/api-reference/functions/use-pathname
const currentPath = usePathname(); // e.g. `/docs/works/shelfree`
// Check if the current path is the same as the href.
const isBeingOpened = useMemo(() => !!props.href && currentPath === props.href.toString(), [currentPath, props.href]);

return (
<Link className={cn(headerLinkVariant({ selected: isBeingOpened }), className)} {...props}>
{children}
</Link>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { HeaderLink } from './header-link';
29 changes: 29 additions & 0 deletions apps/website/src/layout/global/header/in-app-header.container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client';

import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { type ComponentPropsWithoutRef, type ReactNode, useEffect, useState } from 'react';
import { InAppHeader as InAppHeaderPresenter } from './in-app-header.presenter';

export type HeaderProps = Omit<ComponentPropsWithoutRef<typeof InAppHeaderPresenter>, 'className'>;

export const InAppHeader = ({ user: initialUser, ...props }: HeaderProps): ReactNode => {
const [user, setUser] = useState(initialUser);

const supabase = createClientComponentClient();

useEffect(() => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange(async (_, session) => {
if (!session) {
setUser(null);
}
});

return () => {
subscription.unsubscribe();
};
}, [supabase.auth]);

return <InAppHeaderPresenter user={user} {...props} />;
};
Loading

0 comments on commit 419c5b4

Please sign in to comment.