Skip to content

Commit

Permalink
Improvements (#220)
Browse files Browse the repository at this point in the history
  • Loading branch information
alan2207 authored Oct 30, 2024
1 parent e6acf0a commit 9010f0d
Show file tree
Hide file tree
Showing 42 changed files with 359 additions and 175 deletions.
2 changes: 1 addition & 1 deletion apps/nextjs-app/e2e/tests/auth.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ setup('authenticate', async ({ page }) => {
// log out:
await page.getByRole('button', { name: 'Open user menu' }).click();
await page.getByRole('menuitem', { name: 'Sign Out' }).click();
await page.waitForURL('/auth/login?redirectTo=%2Fapp');
await page.waitForURL('/auth/login?redirectTo=%252Fapp');

// log in:
await page.getByLabel('Email Address').click();
Expand Down
18 changes: 16 additions & 2 deletions apps/nextjs-app/src/app/auth/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import { ReactNode } from 'react';
import { ReactNode, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

import { AuthLayout as AuthLayoutComponent } from '@/components/layouts/auth-layout';
import { Spinner } from '@/components/ui/spinner';

export const metadata = {
title: 'Bulletproof React',
description: 'Welcome to Bulletproof React',
};

const AuthLayout = ({ children }: { children: ReactNode }) => {
return <AuthLayoutComponent>{children}</AuthLayoutComponent>;
return (
<Suspense
fallback={
<div className="flex size-full items-center justify-center">
<Spinner size="xl" />
</div>
}
>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<AuthLayoutComponent>{children}</AuthLayoutComponent>
</ErrorBoundary>
</Suspense>
);
};

export default AuthLayout;
5 changes: 4 additions & 1 deletion apps/nextjs-app/src/app/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useRouter, useSearchParams } from 'next/navigation';

import { paths } from '@/config/paths';
import { LoginForm } from '@/features/auth/components/login-form';

// export const metadata = {
Expand All @@ -17,7 +18,9 @@ const LoginPage = () => {
return (
<LoginForm
onSuccess={() =>
router.replace(`${redirectTo ? `${redirectTo}` : '/app'}`)
router.replace(
`${redirectTo ? `${decodeURIComponent(redirectTo)}` : paths.app.dashboard.getHref()}`,
)
}
/>
);
Expand Down
5 changes: 4 additions & 1 deletion apps/nextjs-app/src/app/auth/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useRouter, useSearchParams } from 'next/navigation';
import { useState } from 'react';

import { paths } from '@/config/paths';
import { RegisterForm } from '@/features/auth/components/register-form';
import { useTeams } from '@/features/teams/api/get-teams';

Expand All @@ -28,7 +29,9 @@ const RegisterPage = () => {
return (
<RegisterForm
onSuccess={() =>
router.replace(`${redirectTo ? `${redirectTo}` : '/app'}`)
router.replace(
`${redirectTo ? `${decodeURIComponent(redirectTo)}` : paths.app.dashboard.getHref()}`,
)
}
chooseTeam={chooseTeam}
setChooseTeam={() => setChooseTeam(!chooseTeam)}
Expand Down
3 changes: 2 additions & 1 deletion apps/nextjs-app/src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Link } from '@/components/ui/link';
import { paths } from '@/config/paths';

const NotFoundPage = () => {
return (
<div className="mt-52 flex flex-col items-center font-semibold">
<h1>404 - Not Found</h1>
<p>Sorry, the page you are looking for does not exist.</p>
<Link href="/" replace>
<Link href={paths.home.getHref()} replace>
Go to Home
</Link>
</div>
Expand Down
9 changes: 8 additions & 1 deletion apps/nextjs-app/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Button } from '@/components/ui/button';
import { Link } from '@/components/ui/link';
import { paths } from '@/config/paths';
import { checkLoggedIn } from '@/utils/auth';

const HomePage = () => {
Expand All @@ -15,7 +16,13 @@ const HomePage = () => {
<p>Showcasing Best Practices For Building React Applications</p>
<div className="mt-8 flex justify-center">
<div className="inline-flex rounded-md shadow">
<Link href={isLoggedIn ? '/app' : '/auth/login'}>
<Link
href={
isLoggedIn
? paths.app.root.getHref()
: paths.auth.login.getHref()
}
>
<Button
icon={
<svg
Expand Down
65 changes: 31 additions & 34 deletions apps/nextjs-app/src/components/layouts/auth-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
'use client';

import { useRouter, usePathname } from 'next/navigation';
import { ReactNode, useEffect, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
import { ReactNode, useEffect } from 'react';

import { Link } from '@/components/ui/link';
import { Spinner } from '@/components/ui/spinner';
import { paths } from '@/config/paths';
import { useUser } from '@/lib/auth';

type LayoutProps = {
Expand All @@ -16,46 +15,44 @@ export const AuthLayout = ({ children }: LayoutProps) => {
const user = useUser();
const router = useRouter();
const pathname = usePathname();
const isLoginPage = pathname === '/auth/login';
const isLoginPage = pathname === paths.auth.login.getHref();
const title = isLoginPage
? 'Log in to your account'
: 'Register your account';

const searchParams = useSearchParams();
const redirectTo = searchParams?.get('redirectTo');

useEffect(() => {
if (user.data) {
router.replace('/app');
router.replace(
`${redirectTo ? `${decodeURIComponent(redirectTo)}` : paths.app.dashboard.getHref()}`,
);
}
}, [user.data, router]);
}, [user.data, router, redirectTo]);

return (
<Suspense
fallback={
<div className="flex size-full items-center justify-center">
<Spinner size="xl" />
<div className="flex min-h-screen flex-col justify-center bg-gray-50 py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<div className="flex justify-center">
<Link
className="flex items-center text-white"
href={paths.home.getHref()}
>
<img className="h-24 w-auto" src="/logo.svg" alt="Workflow" />
</Link>
</div>
}
>
<ErrorBoundary key={pathname} fallback={<div>Something went wrong!</div>}>
<div className="flex min-h-screen flex-col justify-center bg-gray-50 py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<div className="flex justify-center">
<Link className="flex items-center text-white" href="/">
<img className="h-24 w-auto" src="/logo.svg" alt="Workflow" />
</Link>
</div>

<h2 className="mt-3 text-center text-3xl font-extrabold text-gray-900">
{title}
</h2>
</div>

<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white px-4 py-8 shadow sm:rounded-lg sm:px-10">
{children}
</div>
</div>

<h2 className="mt-3 text-center text-3xl font-extrabold text-gray-900">
{title}
</h2>
</div>

<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white px-4 py-8 shadow sm:rounded-lg sm:px-10">
{children}
</div>
</ErrorBoundary>
</Suspense>
</div>
</div>
);
};
11 changes: 6 additions & 5 deletions apps/nextjs-app/src/components/layouts/dashboard-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ErrorBoundary } from 'react-error-boundary';
import { Button } from '@/components/ui/button';
import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer';
import { Spinner } from '@/components/ui/spinner';
import { paths } from '@/config/paths';
import { AuthLoader, useLogout } from '@/lib/auth';
import { ROLES, useAuthorization } from '@/lib/authorization';
import { cn } from '@/utils/cn';
Expand All @@ -30,7 +31,7 @@ type SideNavigationItem = {

const Logo = () => {
return (
<Link className="flex items-center text-white" href="/">
<Link className="flex items-center text-white" href={paths.home.getHref()}>
<img className="h-8 w-auto" src="/logo.svg" alt="Workflow" />
<span className="text-sm font-semibold text-white">
Bulletproof React
Expand All @@ -45,11 +46,11 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
const pathname = usePathname();
const router = useRouter();
const navigation = [
{ name: 'Dashboard', to: '/app', icon: Home },
{ name: 'Discussions', to: '/app/discussions', icon: Folder },
{ name: 'Dashboard', to: paths.app.root.getHref(), icon: Home },
{ name: 'Discussions', to: paths.app.discussions.getHref(), icon: Folder },
checkAccess({ allowedRoles: [ROLES.ADMIN] }) && {
name: 'Users',
to: '/app/users',
to: paths.app.users.getHref(),
icon: Users,
},
].filter(Boolean) as SideNavigationItem[];
Expand Down Expand Up @@ -143,7 +144,7 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => router.push('/app/profile')}
onClick={() => router.push(paths.app.profile.getHref())}
className={cn('block px-4 py-2 text-sm text-gray-700')}
>
Your Profile
Expand Down
42 changes: 42 additions & 0 deletions apps/nextjs-app/src/config/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export const paths = {
home: {
getHref: () => '/',
},

auth: {
register: {
getHref: (redirectTo?: string | null | undefined) =>
`/auth/register${redirectTo ? `?redirectTo=${encodeURIComponent(redirectTo)}` : ''}`,
},
login: {
getHref: (redirectTo?: string | null | undefined) =>
`/auth/login${redirectTo ? `?redirectTo=${encodeURIComponent(redirectTo)}` : ''}`,
},
},

app: {
root: {
getHref: () => '/app',
},
dashboard: {
getHref: () => '/app',
},
discussions: {
getHref: () => '/app/discussions',
},
discussion: {
getHref: (id: string) => `/app/discussions/${id}`,
},
users: {
getHref: () => '/app/users',
},
profile: {
getHref: () => '/app/profile',
},
},
public: {
discussion: {
getHref: (id: string) => `/public/discussions/${id}`,
},
},
} as const;
3 changes: 2 additions & 1 deletion apps/nextjs-app/src/features/auth/components/login-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useSearchParams } from 'next/navigation';

import { Button } from '@/components/ui/button';
import { Form, Input } from '@/components/ui/form';
import { paths } from '@/config/paths';
import { useLogin, loginInputSchema } from '@/lib/auth';

type LoginFormProps = {
Expand Down Expand Up @@ -55,7 +56,7 @@ export const LoginForm = ({ onSuccess }: LoginFormProps) => {
<div className="mt-2 flex items-center justify-end">
<div className="text-sm">
<NextLink
href={`/auth/register${redirectTo ? `?redirectTo=${encodeURIComponent(redirectTo)}` : ''}`}
href={paths.auth.register.getHref(redirectTo)}
className="font-medium text-blue-600 hover:text-blue-500"
>
Register
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as React from 'react';

import { Button } from '@/components/ui/button';
import { Form, Input, Select, Label, Switch } from '@/components/ui/form';
import { paths } from '@/config/paths';
import { useRegister, registerInputSchema } from '@/lib/auth';
import { Team } from '@/types/api';

Expand Down Expand Up @@ -109,7 +110,7 @@ export const RegisterForm = ({
<div className="mt-2 flex items-center justify-end">
<div className="text-sm">
<NextLink
href={`/auth/login${redirectTo ? `?redirectTo=${encodeURIComponent(redirectTo)}` : ''}`}
href={paths.auth.login.getHref(redirectTo)}
className="font-medium text-blue-600 hover:text-blue-500"
>
Log In
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { usePathname } from 'next/navigation';
import { Link } from '@/components/ui/link';
import { MDPreview } from '@/components/ui/md-preview';
import { Spinner } from '@/components/ui/spinner';
import { paths } from '@/config/paths';
import { formatDate } from '@/utils/format';

import { useDiscussion } from '../api/get-discussion';
Expand Down Expand Up @@ -47,7 +48,7 @@ export const DiscussionView = ({ discussionId }: { discussionId: string }) => {
{!isPublicView && discussion.public && (
<Link
className="ml-2 flex items-center gap-2 text-sm font-bold"
href={`/public/discussions/${discussionId}`}
href={paths.public.discussion.getHref(discussionId)}
target="_blank"
>
View Public Version <LinkIcon size={16} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useSearchParams } from 'next/navigation';
import { Link } from '@/components/ui/link';
import { Spinner } from '@/components/ui/spinner';
import { Table } from '@/components/ui/table';
import { paths } from '@/config/paths';
import { formatDate } from '@/utils/format';

import { getDiscussionQueryOptions } from '../api/get-discussion';
Expand Down Expand Up @@ -67,7 +68,7 @@ export const DiscussionsList = ({
queryClient.prefetchQuery(getDiscussionQueryOptions(id));
onDiscussionPrefetch?.(id);
}}
href={`/app/discussions/${id}`}
href={paths.app.discussion.getHref(id)}
>
View
</Link>
Expand Down
9 changes: 2 additions & 7 deletions apps/nextjs-app/src/lib/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useEffect } from 'react';
import { configureAuth } from 'react-query-auth';
import { z } from 'zod';

import { paths } from '@/config/paths';
import { AuthResponse, User } from '@/types/api';

import { api } from './api-client';
Expand Down Expand Up @@ -84,13 +85,7 @@ export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {

useEffect(() => {
if (!user.data) {
if (pathname) {
router.replace(
`/auth/login?redirectTo=${encodeURIComponent(pathname)}`,
);
} else {
router.replace('/auth/login');
}
router.replace(paths.auth.login.getHref(pathname));
}
}, [user.data, router, pathname]);

Expand Down
3 changes: 2 additions & 1 deletion apps/nextjs-app/src/lib/authorization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { usePathname, useRouter } from 'next/navigation';
import * as React from 'react';

import { paths } from '@/config/paths';
import { Comment, User } from '@/types/api';

import { useUser } from './auth';
Expand Down Expand Up @@ -35,7 +36,7 @@ export const useAuthorization = () => {

if (!user.data && !user.isLoading) {
const redirectTo = encodeURIComponent(pathname);
router.push(`/auth/login?redirectTo=${redirectTo}`);
router.push(paths.auth.login.getHref(redirectTo));
}

const checkAccess = React.useCallback(
Expand Down
Loading

0 comments on commit 9010f0d

Please sign in to comment.