Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements #220

Merged
merged 4 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading