Skip to content

Commit

Permalink
feat(bottle): Error, Not-Found Page (#38)
Browse files Browse the repository at this point in the history
* refactor(bottle): change prefetchQuery to fetchQuery to catch errors

* feat(bottle): add error, not-found page

* fix(bottle): import type

* refactor(bottle): makeQueryClient on the server
  • Loading branch information
stakbucks authored Aug 14, 2024
1 parent 4259f4f commit ab6a8ba
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 64 deletions.
6 changes: 3 additions & 3 deletions apps/bottle/src/app/bottles/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getServerSideTokens } from '@/features/server/serverSideTokens';
import { Suspense } from 'react';
import { PrefetchBoundary } from '../../../store/query/PrefetchBoundary';
import { ServerFetchBoundary } from '../../../store/query/ServerFetchBoundary';
import { bottleDetailQueryOptions } from '../../../store/query/useBottleDetailQuery';
import { BottleType } from '../Bottles';
import { ActionButtons } from './ActionButtons';
Expand All @@ -22,9 +22,9 @@ export default function BottleItemPage({
<>
<BottlePageHeader />
<Suspense>
<PrefetchBoundary prefetchOptions={prefetchOptions}>
<ServerFetchBoundary fetchOptions={prefetchOptions}>
<BottleDetail id={id} />
</PrefetchBoundary>
</ServerFetchBoundary>
<ActionButtons type={type} id={id} />
</Suspense>
</>
Expand Down
8 changes: 4 additions & 4 deletions apps/bottle/src/app/bottles/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { getServerSideTokens } from '@/features/server/serverSideTokens';
import { Suspense } from 'react';
import { PrefetchBoundary } from '../../store/query/PrefetchBoundary';
import { ServerFetchBoundary } from '../../store/query/ServerFetchBoundary';
import { bottlesQueryOptions } from '../../store/query/useBottlesQuery';
import { userInfoQueryOptions } from '../../store/query/useNameQuery';
import { Bottles } from './Bottles';

export default function BottlesPage() {
const tokens = getServerSideTokens();
const prefetchOptions = [userInfoQueryOptions(tokens), bottlesQueryOptions(tokens)];
const serverFetchOptions = [userInfoQueryOptions(tokens), bottlesQueryOptions(tokens)];

return (
<Suspense>
<PrefetchBoundary prefetchOptions={prefetchOptions}>
<ServerFetchBoundary fetchOptions={serverFetchOptions}>
<Bottles />
</PrefetchBoundary>
</ServerFetchBoundary>
</Suspense>
);
}
7 changes: 0 additions & 7 deletions apps/bottle/src/app/create-profile/error.tsx

This file was deleted.

8 changes: 4 additions & 4 deletions apps/bottle/src/app/create-profile/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { getServerSideTokens } from '@/features/server/serverSideTokens';
import { StepProvider } from '@/features/steps/StepProvider';
import { PrefetchBoundary } from '@/store/query/PrefetchBoundary';
import { ServerFetchBoundary } from '@/store/query/ServerFetchBoundary';
import { userInfoQueryOptions } from '@/store/query/useNameQuery';
import { regionsQueryOptions } from '@/store/query/useRegionsQuery';
import { ReactNode, Suspense } from 'react';
import { CreateProfileProvider } from './CreateProfileProvider';

export default async function CreateProfileLayout({ children }: { children: ReactNode }) {
const prefetchOptions = [regionsQueryOptions(getServerSideTokens()), userInfoQueryOptions(getServerSideTokens())];
const serverFetchOptions = [regionsQueryOptions(getServerSideTokens()), userInfoQueryOptions(getServerSideTokens())];

return (
<Suspense>
<PrefetchBoundary prefetchOptions={prefetchOptions}>
<ServerFetchBoundary fetchOptions={serverFetchOptions}>
<CreateProfileProvider>
<StepProvider maxStep={10} uri="/create-profile">
{children}
</StepProvider>
</CreateProfileProvider>
</PrefetchBoundary>
</ServerFetchBoundary>
</Suspense>
);
}
45 changes: 45 additions & 0 deletions apps/bottle/src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client';

import BasketImage from '@/assets/basket.webp';
import { Header } from '@/components/header';
import { AppBridgeMessageType, useAppBridge } from '@/features/app-bridge';
import { Asset, Button, Paragraph, spacings } from '@bottlesteam/ui';
import Image from 'next/image';
import { useEffect } from 'react';
import { errorImageContainer } from './layout.css';

interface ErrorPageProps {
error: Error & { digest?: string };
reset: () => void;
}

export default function DefaultErrorPage({ error, reset }: ErrorPageProps) {
const { send } = useAppBridge();

useEffect(() => {
// Log error
console.error(error);
}, [error]);

return (
<>
<Header>
<button
onClick={() => send({ type: AppBridgeMessageType.WEB_VIEW_CLOSE })}
style={{ background: 'none', border: 'none' }}
>
<Asset type="icon-arrow-left" />
</button>
</Header>
<Paragraph typography="t2" color="black100" style={{ marginTop: spacings.xl, marginBottom: spacings.xxl }}>
{'앗, 오류가 발생했어요!\n보틀을 다시 실행해 주세요'}
</Paragraph>
<div className={errorImageContainer}>
<Image alt="basket" src={BasketImage} width={250} height={250} />
<Button variant="solid" size="sm" onClick={reset}>
다시 시도하기
</Button>
</div>
</>
);
}
10 changes: 9 additions & 1 deletion apps/bottle/src/app/layout.css.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { colors } from '@bottlesteam/ui';
import { colors, spacings } from '@bottlesteam/ui';
import { style } from '@vanilla-extract/css';

export const layoutStyle = style({
Expand All @@ -16,3 +16,11 @@ export const layoutStyle = style({
margin: '0 auto',
position: 'relative',
});

export const errorImageContainer = style({
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: spacings.xl,
});
8 changes: 4 additions & 4 deletions apps/bottle/src/app/my/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { getServerSideTokens } from '@/features/server/serverSideTokens';
import { PrefetchBoundary } from '@/store/query/PrefetchBoundary';
import { ServerFetchBoundary } from '@/store/query/ServerFetchBoundary';
import { myInformationQueryOptions } from '@/store/query/useMyInformation';
import { Suspense } from 'react';
import { MyInformation } from './MyInformation';

export default async function MyPage() {
const prefetchOptions = myInformationQueryOptions(getServerSideTokens());
const serverFetchOptions = myInformationQueryOptions(getServerSideTokens());

return (
<>
<Suspense>
<PrefetchBoundary prefetchOptions={prefetchOptions}>
<ServerFetchBoundary fetchOptions={serverFetchOptions}>
<MyInformation />
</PrefetchBoundary>
</ServerFetchBoundary>
</Suspense>
</>
);
Expand Down
33 changes: 33 additions & 0 deletions apps/bottle/src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use client';

import BasketImage from '@/assets/basket.webp';
import { Header } from '@/components/header';
import { AppBridgeMessageType, useAppBridge } from '@/features/app-bridge';
import { Asset, Button, Paragraph, spacings } from '@bottlesteam/ui';
import Image from 'next/image';
import { errorImageContainer } from './layout.css';

export default function DefaultErrorPage() {
const { send } = useAppBridge();

const closeWebView = () => send({ type: AppBridgeMessageType.WEB_VIEW_CLOSE });

return (
<>
<Header>
<button onClick={closeWebView} style={{ background: 'none', border: 'none' }}>
<Asset type="icon-arrow-left" />
</button>
</Header>
<Paragraph typography="t2" color="black100" style={{ marginTop: spacings.xl, marginBottom: spacings.xxl }}>
{'앗, 오류가 발생했어요!\n존재하지 않는 페이지에요.'}
</Paragraph>
<div className={errorImageContainer}>
<Image alt="basket" src={BasketImage} width={250} height={250} />
<Button variant="solid" size="sm" onClick={closeWebView}>
돌아가기
</Button>
</div>
</>
);
}
Binary file added apps/bottle/src/assets/basket.webp
Binary file not shown.
18 changes: 0 additions & 18 deletions apps/bottle/src/store/query/PrefetchBoundary.tsx

This file was deleted.

25 changes: 2 additions & 23 deletions apps/bottle/src/store/query/QueryClientProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,8 @@
'use client';

import { isServer, QueryClient, QueryClientProvider as _QueryClientProvider } from '@tanstack/react-query';
import { QueryClientProvider as _QueryClientProvider } from '@tanstack/react-query';
import { ReactNode } from 'react';

function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
gcTime: Infinity,
},
},
});
}

let browserQueryClient: QueryClient | undefined = undefined;

function getQueryClient() {
if (isServer) {
return makeQueryClient();
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}
import { getQueryClient } from './getQueryClient';

export function QueryClientProvider({ children }: { children: ReactNode }) {
const queryClient = getQueryClient();
Expand Down
20 changes: 20 additions & 0 deletions apps/bottle/src/store/query/ServerFetchBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { type FetchQueryOptions, HydrationBoundary, dehydrate } from '@tanstack/react-query';
import type { ReactNode } from 'react';
import { getQueryClient } from './getQueryClient';

export type FetchOptions = Pick<FetchQueryOptions, 'queryKey' | 'queryFn'>;

type Props = {
fetchOptions: FetchOptions[] | FetchOptions;
children: ReactNode | ReactNode[];
};

export async function ServerFetchBoundary({ fetchOptions, children }: Props) {
const queryClient = getQueryClient();

Array.isArray(fetchOptions)
? Promise.all(fetchOptions.map(prefetchOption => queryClient.fetchQuery(prefetchOption)))
: queryClient.fetchQuery(fetchOptions);

return <HydrationBoundary state={dehydrate(queryClient)}>{children}</HydrationBoundary>;
}
32 changes: 32 additions & 0 deletions apps/bottle/src/store/query/getQueryClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
isServer,
QueryClient,
QueryClientProvider as _QueryClientProvider,
defaultShouldDehydrateQuery,
} from '@tanstack/react-query';

const DEFAULT_STALE_TIME = 10 * 60 * 1000;

function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: DEFAULT_STALE_TIME,
},
dehydrate: {
shouldDehydrateQuery: query => defaultShouldDehydrateQuery(query) || query.state.status === 'pending',
},
},
});
}

let browserQueryClient: QueryClient | undefined = undefined;

export function getQueryClient() {
if (isServer) {
return makeQueryClient();
} else {
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}

0 comments on commit ab6a8ba

Please sign in to comment.