Skip to content

Commit

Permalink
add public page with ssr
Browse files Browse the repository at this point in the history
  • Loading branch information
alan2207 committed Aug 18, 2024
1 parent ddb3afa commit 00d7b16
Show file tree
Hide file tree
Showing 25 changed files with 216 additions and 98 deletions.
4 changes: 3 additions & 1 deletion apps/nextjs-pages/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ yarn-error.log*
# local
mocked-db.json

/.next
/.next

tsconfig.tsbuildinfo
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,25 @@ import {
createDiscussion,
createUser,
within,
waitForLoadingToFinish,
} from '@/testing/test-utils';

import { Discussion } from '../discussion';
import { DiscussionPage } from '../discussion';

const renderDiscussion = async () => {
const fakeUser = await createUser();
const fakeDiscussion = await createDiscussion({ teamId: fakeUser.teamId });

mockRouter.query = { discussionId: fakeDiscussion.id };

const utils = await renderApp(<Discussion />, {
const utils = await renderApp(<DiscussionPage />, {
user: fakeUser,
path: `/app/discussions/:discussionId`,
url: `/app/discussions/${fakeDiscussion.id}`,
});

await waitForLoadingToFinish();

await screen.findByText(fakeDiscussion.title);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
screen,
userEvent,
waitFor,
waitForLoadingToFinish,
within,
} from '@/testing/test-utils';
import { formatDate } from '@/utils/format';
Expand All @@ -24,14 +25,9 @@ test(
'should create, render and delete discussions',
{ timeout: 10000 },
async () => {
await renderApp(
<DiscussionsPage
dehydratedState={{
mutations: [],
queries: [],
}}
/>,
);
await renderApp(<DiscussionsPage />);

await waitForLoadingToFinish();

const newDiscussion = createDiscussion();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const getServerSideProps = (async ({ query, req }) => {
};
}) satisfies GetServerSideProps<DiscussionPageProps>;

export const Discussion = () => {
export const DiscussionPage = () => {
const router = useRouter();
const discussionId = router.query.discussionId as string;

Expand Down Expand Up @@ -77,18 +77,18 @@ export const Discussion = () => {
);
};

const DiscussionPage = ({
DiscussionPage.getLayout = (page: ReactElement) => {
return <DashboardLayout>{page}</DashboardLayout>;
};

export default DiscussionPage;

export const PublicDiscussionPage = ({
dehydratedState,
}: InferGetServerSidePropsType<typeof getServerSideProps>) => {
return (
<HydrationBoundary state={dehydratedState}>
<Discussion />
<DiscussionPage />
</HydrationBoundary>
);
};

DiscussionPage.getLayout = (page: ReactElement) => {
return <DashboardLayout>{page}</DashboardLayout>;
};

export default DiscussionPage;
Original file line number Diff line number Diff line change
@@ -1,43 +1,16 @@
import {
HydrationBoundary,
QueryClient,
useQueryClient,
dehydrate,
} from '@tanstack/react-query';
import type { InferGetServerSidePropsType, GetServerSideProps } from 'next';
import { useQueryClient } from '@tanstack/react-query';
import { ReactElement } from 'react';

import { ContentLayout, DashboardLayout } from '@/components/layouts';
import { getInfiniteCommentsQueryOptions } from '@/features/comments/api/get-comments';
import { getDiscussionsQueryOptions } from '@/features/discussions/api/get-discussions';
import { CreateDiscussion } from '@/features/discussions/components/create-discussion';
import { DiscussionsList } from '@/features/discussions/components/discussions-list';

type DiscussionsPageProps = {
dehydratedState?: unknown;
};

export const getServerSideProps = (async ({ query, req }) => {
const queryClient = new QueryClient();
const page = Number(query.page || 1);
const cookie = req.headers.cookie;

await queryClient.prefetchQuery(getDiscussionsQueryOptions({ page, cookie }));

return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
}) satisfies GetServerSideProps<DiscussionsPageProps>;

const DiscussionsPage = ({
dehydratedState,
}: InferGetServerSidePropsType<typeof getServerSideProps>) => {
const DiscussionsPage = () => {
const queryClient = useQueryClient();

return (
<HydrationBoundary state={dehydratedState}>
<>
<div className="flex justify-end">
<CreateDiscussion />
</div>
Expand All @@ -51,7 +24,7 @@ const DiscussionsPage = ({
}}
/>
</div>
</HydrationBoundary>
</>
);
};

Expand Down
12 changes: 10 additions & 2 deletions apps/nextjs-pages/src/components/layouts/dashboard-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,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 { useLogout } from '@/lib/auth';
import { AuthLoader, useLogout } from '@/lib/auth';
import { ROLES, useAuthorization } from '@/lib/authorization';
import { cn } from '@/utils/cn';

Expand Down Expand Up @@ -235,7 +235,15 @@ export const DashboardLayout = ({
key={router.pathname}
fallback={<div>Something went wrong!</div>}
>
{children}
<AuthLoader
renderLoading={() => (
<div className="flex size-full items-center justify-center">
<Spinner size="xl" />
</div>
)}
>
{children}
</AuthLoader>
</ErrorBoundary>
</Suspense>
</Layout>
Expand Down
1 change: 1 addition & 0 deletions apps/nextjs-pages/src/components/ui/link/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { cn } from '@/utils/cn';
export type LinkProps = {
className?: string;
children: React.ReactNode;
target?: string;
} & NextLinkProps;

export const Link = ({ className, children, href, ...props }: LinkProps) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ArchiveX } from 'lucide-react';
import { usePathname } from 'next/navigation';

import { Button } from '@/components/ui/button';
import { MDPreview } from '@/components/ui/md-preview';
Expand All @@ -19,6 +20,8 @@ type CommentsListProps = {
export const CommentsList = ({ discussionId }: CommentsListProps) => {
const user = useUser();
const commentsQuery = useInfiniteComments({ discussionId });
const pathname = usePathname();
const isPublicView = pathname?.startsWith?.('/public/');

if (commentsQuery.isLoading) {
return (
Expand Down Expand Up @@ -51,28 +54,29 @@ export const CommentsList = ({ discussionId }: CommentsListProps) => {
key={comment.id || index}
className="w-full bg-white p-4 shadow-sm"
>
<Authorization
policyCheck={POLICIES['comment:delete'](
user.data as User,
comment,
)}
>
<div className="flex justify-between">
<div>
<span className="text-xs font-semibold">
{formatDate(comment.createdAt)}
<div className="flex justify-between">
<div>
<span className="text-xs font-semibold">
{formatDate(comment.createdAt)}
</span>
{comment.author && (
<span className="text-xs font-bold">
{' '}
by {comment.author.firstName} {comment.author.lastName}
</span>
{comment.author && (
<span className="text-xs font-bold">
{' '}
by {comment.author.firstName} {comment.author.lastName}
</span>
)}
</div>
<DeleteComment discussionId={discussionId} id={comment.id} />
)}
</div>
</Authorization>

{!isPublicView && (
<Authorization
policyCheck={POLICIES['comment:delete'](
user.data as User,
comment,
)}
>
<DeleteComment discussionId={discussionId} id={comment.id} />
</Authorization>
)}
</div>
<MDPreview value={comment.body} />
</li>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { usePathname } from 'next/navigation';

import { CommentsList } from './comments-list';
import { CreateComment } from './create-comment';

Expand All @@ -6,11 +8,13 @@ type CommentsProps = {
};

export const Comments = ({ discussionId }: CommentsProps) => {
const pathname = usePathname();
const isPublicView = pathname?.startsWith?.('/public/');
return (
<div>
<div className="mb-4 flex items-center justify-between">
<h3 className="text-xl font-bold">Comments:</h3>
<CreateComment discussionId={discussionId} />
{!isPublicView && <CreateComment discussionId={discussionId} />}
</div>
<CommentsList discussionId={discussionId} />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getDiscussionsQueryOptions } from './get-discussions';
export const createDiscussionInputSchema = z.object({
title: z.string().min(1, 'Required'),
body: z.string().min(1, 'Required'),
public: z.boolean(),
});

export type CreateDiscussionInput = z.infer<typeof createDiscussionInputSchema>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getDiscussionQueryOptions } from './get-discussion';
export const updateDiscussionInputSchema = z.object({
title: z.string().min(1, 'Required'),
body: z.string().min(1, 'Required'),
public: z.boolean(),
});

export type UpdateDiscussionInput = z.infer<typeof updateDiscussionInputSchema>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { Plus } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { Form, FormDrawer, Input, Textarea } from '@/components/ui/form';
import {
Form,
FormDrawer,
Input,
Label,
Switch,
Textarea,
} from '@/components/ui/form';
import { useNotifications } from '@/components/ui/notifications';
import { Authorization, ROLES } from '@/lib/authorization';

Expand Down Expand Up @@ -50,8 +57,15 @@ export const CreateDiscussion = () => {
createDiscussionMutation.mutate({ data: values });
}}
schema={createDiscussionInputSchema}
options={{
defaultValues: {
title: '',
body: '',
public: false,
},
}}
>
{({ register, formState }) => (
{({ register, formState, setValue, watch }) => (
<>
<Input
label="Title"
Expand All @@ -64,6 +78,17 @@ export const CreateDiscussion = () => {
error={formState.errors['body']}
registration={register('body')}
/>

<div className="flex items-center space-x-2">
<Switch
name="public"
onCheckedChange={(value) => setValue('public', value)}
checked={watch('public')}
className={` relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2`}
id="public"
/>
<Label htmlFor="airplane-mode">Public</Label>
</div>
</>
)}
</Form>
Expand Down
Loading

0 comments on commit 00d7b16

Please sign in to comment.