diff --git a/apps/nextjs-app/src/app/app/discussions/[discussionId]/page.tsx b/apps/nextjs-app/src/app/app/discussions/[discussionId]/page.tsx index ff1704c2..d843e967 100644 --- a/apps/nextjs-app/src/app/app/discussions/[discussionId]/page.tsx +++ b/apps/nextjs-app/src/app/app/discussions/[discussionId]/page.tsx @@ -3,7 +3,7 @@ import { useParams } from 'next/navigation'; import { ErrorBoundary } from 'react-error-boundary'; -import { ContentLayout } from '@/components/layouts'; +import { ContentLayout } from '@/components/layouts/content-layout'; import { Spinner } from '@/components/ui/spinner'; import { Comments } from '@/features/comments/components/comments'; import { useDiscussion } from '@/features/discussions/api/get-discussion'; diff --git a/apps/nextjs-app/src/app/app/discussions/page.tsx b/apps/nextjs-app/src/app/app/discussions/page.tsx index a9831757..bc002009 100644 --- a/apps/nextjs-app/src/app/app/discussions/page.tsx +++ b/apps/nextjs-app/src/app/app/discussions/page.tsx @@ -2,7 +2,7 @@ import { useQueryClient } from '@tanstack/react-query'; -import { ContentLayout } from '@/components/layouts'; +import { ContentLayout } from '@/components/layouts/content-layout'; import { getInfiniteCommentsQueryOptions } from '@/features/comments/api/get-comments'; import { CreateDiscussion } from '@/features/discussions/components/create-discussion'; import { DiscussionsList } from '@/features/discussions/components/discussions-list'; diff --git a/apps/nextjs-app/src/app/app/layout.tsx b/apps/nextjs-app/src/app/app/layout.tsx index c4c1eb24..4c96e848 100644 --- a/apps/nextjs-app/src/app/app/layout.tsx +++ b/apps/nextjs-app/src/app/app/layout.tsx @@ -1,6 +1,11 @@ import { ReactNode } from 'react'; -import { DashboardLayout } from '@/components/layouts'; +import { DashboardLayout } from '@/components/layouts/dashboard-layout'; + +export const metadata = { + title: 'Dashboard', + description: 'Dashboard', +}; const AppLayout = ({ children }: { children: ReactNode }) => { return {children}; diff --git a/apps/nextjs-app/src/app/app/users/page.tsx b/apps/nextjs-app/src/app/app/users/page.tsx index 14bb2eba..c6693e06 100644 --- a/apps/nextjs-app/src/app/app/users/page.tsx +++ b/apps/nextjs-app/src/app/app/users/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ContentLayout } from '@/components/layouts'; +import { ContentLayout } from '@/components/layouts/content-layout'; import { UsersList } from '@/features/users/components/users-list'; import { Authorization, ROLES } from '@/lib/authorization'; diff --git a/apps/nextjs-app/src/app/auth/layout.tsx b/apps/nextjs-app/src/app/auth/layout.tsx index 896f83f1..a8310152 100644 --- a/apps/nextjs-app/src/app/auth/layout.tsx +++ b/apps/nextjs-app/src/app/auth/layout.tsx @@ -1,18 +1,17 @@ -'use client'; - -import { usePathname } from 'next/navigation'; import { ReactNode } from 'react'; import { AuthLayout as AuthLayoutComponent } from '@/components/layouts/auth-layout'; -const AuthLayout = ({ children }: { children: ReactNode }) => { - const pathname = usePathname(); - const isLoginPage = pathname === '/auth/login'; - const title = isLoginPage - ? 'Log in to your account' - : 'Register your account'; +export const generateMetadata = (...args: any[]) => { + console.log(args); + return { + title: 'Bulletproof React', + description: 'Welcome to Bulletproof React', + }; +}; - return {children}; +const AuthLayout = ({ children }: { children: ReactNode }) => { + return {children}; }; export default AuthLayout; diff --git a/apps/nextjs-app/src/app/layout.tsx b/apps/nextjs-app/src/app/layout.tsx index 3ec2d211..4b9bc9eb 100644 --- a/apps/nextjs-app/src/app/layout.tsx +++ b/apps/nextjs-app/src/app/layout.tsx @@ -4,6 +4,11 @@ import { AppProvider } from '@/app/provider'; import '@/styles/globals.css'; +export const metadata = { + title: 'Bulletproof React', + description: 'Showcasing Best Practices For Building React Applications', +}; + const RootLayout = ({ children }: { children: ReactNode }) => { return ( diff --git a/apps/nextjs-app/src/app/page.tsx b/apps/nextjs-app/src/app/page.tsx index d322cbd0..3276a41b 100644 --- a/apps/nextjs-app/src/app/page.tsx +++ b/apps/nextjs-app/src/app/page.tsx @@ -1,25 +1,9 @@ -'use client'; -import { useRouter } from 'next/navigation'; - import { Button } from '@/components/ui/button'; -import { useUser } from '@/lib/auth'; - -// export const metadata = { -// title: 'Bulletproof React', -// description: 'Welcome to bulletproof react', -// }; +import { Link } from '@/components/ui/link'; +import { checkLoggedIn } from '@/utils/auth'; const HomePage = () => { - const router = useRouter(); - const user = useUser(); - - const handleStart = () => { - if (user.data) { - router.push('/app'); - } else { - router.push('/auth/login'); - } - }; + const isLoggedIn = checkLoggedIn(); return (
@@ -31,27 +15,28 @@ const HomePage = () => {

Showcasing Best Practices For Building React Applications

- + + +
{ - const queryClient = new QueryClient(); +export const generateMetadata = async ({ + params, +}: { + params: { discussionId: string }; +}) => { + const discussion = await getDiscussion({ + discussionId: params.discussionId, + }); + + const name = discussion.data.title; - const cookieStore = cookies(); + return { + title: name, + }; +}; - const cookie = cookieStore.get('bulletproof_react_app_token')?.value; +const preloadData = async (discussionId: string) => { + const queryClient = new QueryClient(); - await queryClient.prefetchQuery( - getDiscussionQueryOptions(discussionId, cookie), - ); + await queryClient.prefetchQuery(getDiscussionQueryOptions(discussionId)); await queryClient.prefetchInfiniteQuery( - getInfiniteCommentsQueryOptions(discussionId, cookie), + getInfiniteCommentsQueryOptions(discussionId), ); return { diff --git a/apps/nextjs-app/src/components/layouts/auth-layout.tsx b/apps/nextjs-app/src/components/layouts/auth-layout.tsx index 3d81801d..358ab30b 100644 --- a/apps/nextjs-app/src/components/layouts/auth-layout.tsx +++ b/apps/nextjs-app/src/components/layouts/auth-layout.tsx @@ -1,8 +1,7 @@ 'use client'; import { useRouter, usePathname } from 'next/navigation'; -import * as React from 'react'; -import { useEffect } from 'react'; +import { ReactNode, useEffect, Suspense } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { Link } from '@/components/ui/link'; @@ -10,20 +9,17 @@ import { Spinner } from '@/components/ui/spinner'; import { useUser } from '@/lib/auth'; type LayoutProps = { - children: React.ReactNode; - title: string; + children: ReactNode; }; -// export const metadata = { -// title: 'Bulletproof React', -// description: 'Welcome to bulletproof react', -// }; - -export const AuthLayout = ({ children, title }: LayoutProps) => { +export const AuthLayout = ({ children }: LayoutProps) => { const user = useUser(); - const router = useRouter(); const pathname = usePathname(); + const isLoginPage = pathname === '/auth/login'; + const title = isLoginPage + ? 'Log in to your account' + : 'Register your account'; useEffect(() => { if (user.data) { @@ -32,7 +28,7 @@ export const AuthLayout = ({ children, title }: LayoutProps) => { }, [user.data, router]); return ( - @@ -60,6 +56,6 @@ export const AuthLayout = ({ children, title }: LayoutProps) => {
- + ); }; diff --git a/apps/nextjs-app/src/components/layouts/content-layout.tsx b/apps/nextjs-app/src/components/layouts/content-layout.tsx index a6e7edd6..bd5a193b 100644 --- a/apps/nextjs-app/src/components/layouts/content-layout.tsx +++ b/apps/nextjs-app/src/components/layouts/content-layout.tsx @@ -1,7 +1,7 @@ -import * as React from 'react'; +import { ReactNode } from 'react'; type ContentLayoutProps = { - children: React.ReactNode; + children: ReactNode; title: string; }; diff --git a/apps/nextjs-app/src/components/layouts/index.ts b/apps/nextjs-app/src/components/layouts/index.ts deleted file mode 100644 index fc6f49f3..00000000 --- a/apps/nextjs-app/src/components/layouts/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './content-layout'; -export * from './dashboard-layout'; diff --git a/apps/nextjs-app/src/components/ui/dialog/confirmation-dialog/confirmation-dialog.tsx b/apps/nextjs-app/src/components/ui/dialog/confirmation-dialog/confirmation-dialog.tsx index c8da8312..5cb56fdd 100644 --- a/apps/nextjs-app/src/components/ui/dialog/confirmation-dialog/confirmation-dialog.tsx +++ b/apps/nextjs-app/src/components/ui/dialog/confirmation-dialog/confirmation-dialog.tsx @@ -1,3 +1,5 @@ +'use client'; + import { CircleAlert, Info } from 'lucide-react'; import * as React from 'react'; import { useEffect } from 'react'; diff --git a/apps/nextjs-app/src/components/ui/dialog/dialog.tsx b/apps/nextjs-app/src/components/ui/dialog/dialog.tsx index e9a1caae..845a3226 100644 --- a/apps/nextjs-app/src/components/ui/dialog/dialog.tsx +++ b/apps/nextjs-app/src/components/ui/dialog/dialog.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as DialogPrimitive from '@radix-ui/react-dialog'; import { Cross2Icon } from '@radix-ui/react-icons'; import * as React from 'react'; diff --git a/apps/nextjs-app/src/components/ui/drawer/drawer.tsx b/apps/nextjs-app/src/components/ui/drawer/drawer.tsx index f4f2306d..3f17eb03 100644 --- a/apps/nextjs-app/src/components/ui/drawer/drawer.tsx +++ b/apps/nextjs-app/src/components/ui/drawer/drawer.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as DrawerPrimitive from '@radix-ui/react-dialog'; import { Cross2Icon } from '@radix-ui/react-icons'; import { cva, type VariantProps } from 'class-variance-authority'; diff --git a/apps/nextjs-app/src/components/ui/dropdown/dropdown.tsx b/apps/nextjs-app/src/components/ui/dropdown/dropdown.tsx index 94294654..00dcd19e 100644 --- a/apps/nextjs-app/src/components/ui/dropdown/dropdown.tsx +++ b/apps/nextjs-app/src/components/ui/dropdown/dropdown.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; import { CheckIcon, diff --git a/apps/nextjs-app/src/components/ui/form/form-drawer.tsx b/apps/nextjs-app/src/components/ui/form/form-drawer.tsx index 0d7572a8..a58ecde2 100644 --- a/apps/nextjs-app/src/components/ui/form/form-drawer.tsx +++ b/apps/nextjs-app/src/components/ui/form/form-drawer.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import { useDisclosure } from '@/hooks/use-disclosure'; diff --git a/apps/nextjs-app/src/components/ui/form/form.tsx b/apps/nextjs-app/src/components/ui/form/form.tsx index bc2faee7..63d9ef7e 100644 --- a/apps/nextjs-app/src/components/ui/form/form.tsx +++ b/apps/nextjs-app/src/components/ui/form/form.tsx @@ -1,3 +1,5 @@ +'use client'; + import { zodResolver } from '@hookform/resolvers/zod'; import * as LabelPrimitive from '@radix-ui/react-label'; import { Slot } from '@radix-ui/react-slot'; diff --git a/apps/nextjs-app/src/components/ui/form/label.tsx b/apps/nextjs-app/src/components/ui/form/label.tsx index ed9dfcf3..0728eefd 100644 --- a/apps/nextjs-app/src/components/ui/form/label.tsx +++ b/apps/nextjs-app/src/components/ui/form/label.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as LabelPrimitive from '@radix-ui/react-label'; import { cva, type VariantProps } from 'class-variance-authority'; import * as React from 'react'; diff --git a/apps/nextjs-app/src/components/ui/form/select.tsx b/apps/nextjs-app/src/components/ui/form/select.tsx index 6b38621a..39abf6c4 100644 --- a/apps/nextjs-app/src/components/ui/form/select.tsx +++ b/apps/nextjs-app/src/components/ui/form/select.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as React from 'react'; import { UseFormRegisterReturn } from 'react-hook-form'; diff --git a/apps/nextjs-app/src/components/ui/form/switch.tsx b/apps/nextjs-app/src/components/ui/form/switch.tsx index afa9e191..24dfe32b 100644 --- a/apps/nextjs-app/src/components/ui/form/switch.tsx +++ b/apps/nextjs-app/src/components/ui/form/switch.tsx @@ -1,3 +1,5 @@ +'use client'; + import * as SwitchPrimitives from '@radix-ui/react-switch'; import * as React from 'react'; diff --git a/apps/nextjs-app/src/components/ui/notifications/notification.tsx b/apps/nextjs-app/src/components/ui/notifications/notification.tsx index e5b17053..2591c37c 100644 --- a/apps/nextjs-app/src/components/ui/notifications/notification.tsx +++ b/apps/nextjs-app/src/components/ui/notifications/notification.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Info, CircleAlert, CircleX, CircleCheck } from 'lucide-react'; const icons = { diff --git a/apps/nextjs-app/src/components/ui/notifications/notifications.tsx b/apps/nextjs-app/src/components/ui/notifications/notifications.tsx index 080534dc..404bf8b8 100644 --- a/apps/nextjs-app/src/components/ui/notifications/notifications.tsx +++ b/apps/nextjs-app/src/components/ui/notifications/notifications.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Notification } from './notification'; import { useNotifications } from './notifications-store'; diff --git a/apps/nextjs-app/src/features/comments/components/comments-list.tsx b/apps/nextjs-app/src/features/comments/components/comments-list.tsx index 701c59cd..0cde44d3 100644 --- a/apps/nextjs-app/src/features/comments/components/comments-list.tsx +++ b/apps/nextjs-app/src/features/comments/components/comments-list.tsx @@ -1,3 +1,5 @@ +'use client'; + import { ArchiveX } from 'lucide-react'; import { usePathname } from 'next/navigation'; diff --git a/apps/nextjs-app/src/features/comments/components/comments.tsx b/apps/nextjs-app/src/features/comments/components/comments.tsx index 32b5307d..96ab2fc7 100644 --- a/apps/nextjs-app/src/features/comments/components/comments.tsx +++ b/apps/nextjs-app/src/features/comments/components/comments.tsx @@ -1,3 +1,5 @@ +'use client'; + import { usePathname } from 'next/navigation'; import { CommentsList } from './comments-list'; diff --git a/apps/nextjs-app/src/features/comments/components/create-comment.tsx b/apps/nextjs-app/src/features/comments/components/create-comment.tsx index cb8d7974..222338de 100644 --- a/apps/nextjs-app/src/features/comments/components/create-comment.tsx +++ b/apps/nextjs-app/src/features/comments/components/create-comment.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Plus } from 'lucide-react'; import { Button } from '@/components/ui/button'; diff --git a/apps/nextjs-app/src/features/comments/components/delete-comment.tsx b/apps/nextjs-app/src/features/comments/components/delete-comment.tsx index ac9b3969..4835efa3 100644 --- a/apps/nextjs-app/src/features/comments/components/delete-comment.tsx +++ b/apps/nextjs-app/src/features/comments/components/delete-comment.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Trash } from 'lucide-react'; import { Button } from '@/components/ui/button'; diff --git a/apps/nextjs-app/src/features/discussions/components/create-discussion.tsx b/apps/nextjs-app/src/features/discussions/components/create-discussion.tsx index f866e10a..cef8e56f 100644 --- a/apps/nextjs-app/src/features/discussions/components/create-discussion.tsx +++ b/apps/nextjs-app/src/features/discussions/components/create-discussion.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Plus } from 'lucide-react'; import { Button } from '@/components/ui/button'; diff --git a/apps/nextjs-app/src/features/discussions/components/delete-discussion.tsx b/apps/nextjs-app/src/features/discussions/components/delete-discussion.tsx index e4d625fb..d7709b23 100644 --- a/apps/nextjs-app/src/features/discussions/components/delete-discussion.tsx +++ b/apps/nextjs-app/src/features/discussions/components/delete-discussion.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Trash } from 'lucide-react'; import { Button } from '@/components/ui/button'; diff --git a/apps/nextjs-app/src/features/discussions/components/discussion-view.tsx b/apps/nextjs-app/src/features/discussions/components/discussion-view.tsx index 7c9b6299..af713183 100644 --- a/apps/nextjs-app/src/features/discussions/components/discussion-view.tsx +++ b/apps/nextjs-app/src/features/discussions/components/discussion-view.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Link as LinkIcon } from 'lucide-react'; import { usePathname } from 'next/navigation'; diff --git a/apps/nextjs-app/src/features/discussions/components/discussions-list.tsx b/apps/nextjs-app/src/features/discussions/components/discussions-list.tsx index acfa384a..0c586bb0 100644 --- a/apps/nextjs-app/src/features/discussions/components/discussions-list.tsx +++ b/apps/nextjs-app/src/features/discussions/components/discussions-list.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useQueryClient } from '@tanstack/react-query'; import { useSearchParams } from 'next/navigation'; diff --git a/apps/nextjs-app/src/features/discussions/components/update-discussion.tsx b/apps/nextjs-app/src/features/discussions/components/update-discussion.tsx index 9e28a1a5..11a9e8bb 100644 --- a/apps/nextjs-app/src/features/discussions/components/update-discussion.tsx +++ b/apps/nextjs-app/src/features/discussions/components/update-discussion.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Pen } from 'lucide-react'; import { Button } from '@/components/ui/button'; diff --git a/apps/nextjs-app/src/features/users/components/delete-user.tsx b/apps/nextjs-app/src/features/users/components/delete-user.tsx index 3078e160..949ae7c9 100644 --- a/apps/nextjs-app/src/features/users/components/delete-user.tsx +++ b/apps/nextjs-app/src/features/users/components/delete-user.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Button } from '@/components/ui/button'; import { ConfirmationDialog } from '@/components/ui/dialog'; import { useNotifications } from '@/components/ui/notifications'; diff --git a/apps/nextjs-app/src/features/users/components/update-profile.tsx b/apps/nextjs-app/src/features/users/components/update-profile.tsx index 4af708f2..e9a8c7e0 100644 --- a/apps/nextjs-app/src/features/users/components/update-profile.tsx +++ b/apps/nextjs-app/src/features/users/components/update-profile.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Pen } from 'lucide-react'; import { Button } from '@/components/ui/button'; diff --git a/apps/nextjs-app/src/features/users/components/users-list.tsx b/apps/nextjs-app/src/features/users/components/users-list.tsx index 380a3c12..a1d4270e 100644 --- a/apps/nextjs-app/src/features/users/components/users-list.tsx +++ b/apps/nextjs-app/src/features/users/components/users-list.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Spinner } from '@/components/ui/spinner'; import { Table } from '@/components/ui/table'; import { formatDate } from '@/utils/format'; diff --git a/apps/nextjs-app/src/lib/api-client.ts b/apps/nextjs-app/src/lib/api-client.ts index 8455e77a..ff44bdc4 100644 --- a/apps/nextjs-app/src/lib/api-client.ts +++ b/apps/nextjs-app/src/lib/api-client.ts @@ -33,6 +33,8 @@ api.interceptors.response.use( }, ); +// if the endpoint requires the visiting user to be authenticated, +// attaching cookies is required for requests made on the server side export const attachCookie = ( cookie?: string, headers?: Record, diff --git a/apps/nextjs-app/src/utils/auth.ts b/apps/nextjs-app/src/utils/auth.ts new file mode 100644 index 00000000..ee182bce --- /dev/null +++ b/apps/nextjs-app/src/utils/auth.ts @@ -0,0 +1,14 @@ +import { cookies } from 'next/headers'; + +export const AUTH_TOKEN_COOKIE_NAME = 'bulletproof_react_app_token'; + +export const getAuthTokenCookie = () => { + const cookieStore = cookies(); + return cookieStore.get(AUTH_TOKEN_COOKIE_NAME)?.value; +}; + +export const checkLoggedIn = () => { + const cookieStore = cookies(); + const isLoggedIn = !!cookieStore.get(AUTH_TOKEN_COOKIE_NAME); + return isLoggedIn; +}; diff --git a/apps/nextjs-pages/src/lib/api-client.ts b/apps/nextjs-pages/src/lib/api-client.ts index 93131e6e..5261d34d 100644 --- a/apps/nextjs-pages/src/lib/api-client.ts +++ b/apps/nextjs-pages/src/lib/api-client.ts @@ -45,6 +45,8 @@ api.interceptors.response.use( }, ); +// if the endpoint requires the visiting user to be authenticated, +// attaching cookies is required for requests made on the server side export const attachCookie = ( cookie?: string, headers?: Record,