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

Use info logic/#82 #105

Merged
merged 41 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0f60da0
[web] feat: Update middleware.ts to improve guest path handling
seonghunYang Apr 25, 2024
ae40793
feat: Add signOut function and redirect to sign-in page
seonghunYang Apr 25, 2024
944399f
feat: Add SignOutButton component to UserInfoNavigator
seonghunYang Apr 25, 2024
4e06500
feat: Add USER_DEELETE dialog key
seonghunYang Apr 25, 2024
f6a2f42
feat: Add UserDeleteButton component to UserInfoNavigator
seonghunYang Apr 25, 2024
1ccb7b3
feat: Add UserDeleteModal component
seonghunYang Apr 25, 2024
5863c3b
feat: Add FloatingComponentContainer to MyPage
seonghunYang Apr 25, 2024
f125a64
feat: Add user delete dialog and handle toggle
seonghunYang Apr 25, 2024
804fa95
refactor: Refactor user-handler.mock.ts to add devModeAuthGuard function
seonghunYang Apr 25, 2024
d4e50cc
feat: Add unit tests for UserInfoNavigator component and update mock โ€ฆ
seonghunYang Apr 25, 2024
eca405f
feat: Update UserDeleteModal component to improve UI and add user delโ€ฆ
seonghunYang Apr 25, 2024
8bbc2f5
feat: add UserDeleteRequestBody interface
seonghunYang Apr 25, 2024
259ed10
feat: Add deleteUser function to mockDatabase and update user-handlerโ€ฆ
seonghunYang Apr 25, 2024
364e312
feat: add deleteUser function and update UserDeleteModal component
seonghunYang Apr 25, 2024
c2488a7
chore: merge commit
seonghunYang May 16, 2024
773c350
remove: Remove FloatingComponentContainer and add UserDeleteModal to โ€ฆ
seonghunYang May 16, 2024
c3bc438
fix: Fix typo in dialog key for user delete functionality
seonghunYang May 16, 2024
b6c6ae6
fix: Fix middleware.ts to use consistent variable naming conventions
seonghunYang May 16, 2024
336fd90
remove: Remove FloatingComponentContainer and add UserDeleteModal to โ€ฆ
seonghunYang May 16, 2024
15493c5
feat: Update NavigationBar component to include HamburgerMenuIcon forโ€ฆ
seonghunYang May 16, 2024
740215f
refactor: Update NavigationBar component to include SideNavigationBarโ€ฆ
seonghunYang May 16, 2024
3ae0765
feat: Update SideNavigationBar component to include Sheet for additioโ€ฆ
seonghunYang May 16, 2024
23499ba
refactor: Refactor file paths for NavigationBar component to match neโ€ฆ
seonghunYang May 16, 2024
7d929d5
refactor: Refactor file paths for NavigationBar and SideNavigationBarโ€ฆ
seonghunYang May 16, 2024
01d29df
refactor: Refactor file paths for NavigationBar and SideNavigationBarโ€ฆ
seonghunYang May 16, 2024
d738afc
refactor: Refactor user-info-navigator.tsx to improve profile pictureโ€ฆ
seonghunYang May 16, 2024
5fed2dd
feat: Add SignOutButton and UserDeleteButton to UserInfoNavigator comโ€ฆ
seonghunYang May 16, 2024
dc758b5
refactor: Refactor file paths for NavigationBar and SideNavigationBarโ€ฆ
seonghunYang May 16, 2024
630192c
refactor: Refactor user.query.ts and user.validation.ts to handle unaโ€ฆ
seonghunYang May 16, 2024
778c16f
refactor: Refactor file paths for NavigationBar and SideNavigationBarโ€ฆ
seonghunYang May 16, 2024
2626f5e
refactor: Refactor SignButtonGroup component to conditionally
seonghunYang May 16, 2024
cde228b
Update app/(sub-page)/components/side-navigation-bar.tsx
seonghunYang May 20, 2024
0405b87
refactor: Update user-info-navigator.tsx and sheet.tsx components
seonghunYang May 20, 2024
3b0a865
refactor: Refactor SideNavigationBar component to use interface insteโ€ฆ
seonghunYang May 20, 2024
1d6d7b8
refactor: Refactor Sheet component styles in sheet.tsx
seonghunYang May 20, 2024
15d27d7
refactor: Refactor navigation-bar.tsx and my/page.tsx components, andโ€ฆ
seonghunYang May 20, 2024
2dea51a
Update app/ui/user/user-info-navigator/user-delete-modal.tsx
seonghunYang May 20, 2024
2f32ce3
refactor: Refactor SideNavigationBar component to use useDialog hook โ€ฆ
seonghunYang May 20, 2024
78edbd4
Merge branch 'main' into use-info-logic/#82
seonghunYang May 20, 2024
e6a148c
refactor: Refactor SignButtonGroup component to import Button and Linโ€ฆ
seonghunYang May 20, 2024
d161298
refactor: Refactor SignButtonGroup component to import Button and Linโ€ฆ
seonghunYang May 20, 2024
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
17 changes: 17 additions & 0 deletions app/(sub-page)/components/navigation-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Image from 'next/image';
import logo from '../../../public/assets/logo.svg';
import Responsive from '../../ui/responsive';
import SideNavigationBar from './side-navigation-bar';
import UserInfoNavigator from '@/app/ui/user/user-info-navigator/user-info-navigator';
import SignButtonGroup from '@/app/ui/user/user-info-navigator/sign-button-group';

export default function NavigationBar() {
return (
<div className="absolute flex justify-between items-center p-4 border-b-[1px] w-full z-2">
<Image className="md:h-10 h-7 w-[110px] md:w-[150px]" width={150} height={100} src={logo} alt="main-logo" />
<Responsive maxWidth={1023}>
<SideNavigationBar header={<UserInfoNavigator />} content={<div>์ฝ˜ํ…์ธ </div>} footer={<SignButtonGroup />} />
</Responsive>
gahyuun marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
}
39 changes: 39 additions & 0 deletions app/(sub-page)/components/side-navigation-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client';
import { HamburgerMenuIcon } from '@radix-ui/react-icons';
import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetFooter } from '../../ui/view/molecule/sheet/sheet';
import { DIALOG_KEY } from '@/app/utils/key/dialog-key.util';
import useDialog from '@/app/hooks/useDialog';

interface SideNavigationBarProps {
header: React.ReactNode;
footer: React.ReactNode;
content: React.ReactNode;
}

export default function SideNavigationBar({ header, content, footer }: SideNavigationBarProps) {
const { isOpen, open, close } = useDialog(DIALOG_KEY.SIDE_NAVIGATION);

const handleSideNavOpen = (value: boolean) => {
if (value) {
open();
} else {
close();
}
};

return (
<Sheet open={isOpen} onOpenChange={handleSideNavOpen}>
<SheetTrigger className="h-6">
<HamburgerMenuIcon className="w-6 h-6 text-white" />
</SheetTrigger>
<SheetContent className="z-3">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tailwind configํŒŒ์ผ์„ ๋ณด๋ฉด z-index์— ๋Œ€ํ•œ ์žฌ์ •์˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น style์†์„ฑ์œผ๋กœ ๊ต์ฒด๊ฐ€ ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™์•„์š”.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • z-3์ด ํ•ด๋‹น ์žฌ์ •์˜๋ฅผ ๋ฐ˜์˜ํ•œ ๊ฒƒ ์•„๋‹Œ๊ฐ€์š”? ๋” ์„ค๋ช…ํ•ด์ฃผ์‹œ๊ฒ ์–ด์š”?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tailwind.config.ts์—์„œ ์•„๋ž˜ ์žฌ์ •์˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

      zIndex: {
        1: '100', // upper layout, navigation bar, main page content
        2: '200', // upper content , main page graduation cap
        3: '300', // upper all
      },

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์Œ, ๋ญ”๊ฐ€ ์ฐฉ์˜ค๊ฐ€ ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋„ค์š”. tailwind.config.ts์—์„œ ๋ง์”€ํ•˜์‹  ๊ฒƒ ์ฒ˜๋Ÿผ z-index๋ฅผ ์žฌ์ •์˜ํ•˜๋ฉด, 'zIndex-1', 'zIndex-2'๋กœ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ์ด ์•„๋‹ˆ๋ผ, 'z-1', 'z-2โ€™๋กœ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์žฌ์ •์˜ํ•˜์ง€ ์•Š์•˜์„ ๋•Œ๋Š” 'z-10', 'z-20'์ด๊ณ ์š”. ์„œ๋น„์Šค์—์„œ 'zIndex-1'์„ ์‚ฌ์šฉํ•˜์…จ๋‹ค๋ฉด, ์•„๋งˆ ๋™์ž‘ํ•˜์ง€ ์•Š์•˜์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ™•์ธ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

Copy link
Member

@yougyung yougyung May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ํ™•์ธํ•ด๋ดค๋Š”๋ฐ ์ง€๊ธˆ๊นŒ์ง€ ์ž˜๋ชป์ ์šฉ๋˜๊ณ  ์žˆ์—ˆ๋„ค์š” ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค ! z-index๊ด€๋ จ ์ˆ˜์ •์€ ์ผ๊ด„์ ์œผ๋กœ ์ œ๊ฐ€ ์ง„ํ–‰ํ• ๊ฒŒ์š”.

<div className="flex h-full flex-col justify-between">
<SheetHeader>{header}</SheetHeader>
<div className="w-full h-1 rounded-full my-4 bg-gray-200" />
<div className="h-full">{content}</div>
<SheetFooter>{footer}</SheetFooter>
</div>
</SheetContent>
</Sheet>
);
}
2 changes: 1 addition & 1 deletion app/(sub-page)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Image from 'next/image';
import background from '../../public/assets/background.png';
import NavigationBar from '../ui/view/molecule/navigation-bar';
import NavigationBar from './components/navigation-bar';

interface LayoutProps {
children: React.ReactNode;
Expand Down
17 changes: 12 additions & 5 deletions app/(sub-page)/my/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@ import Drawer from '@/app/ui/view/molecule/drawer/drawer';
import { DIALOG_KEY } from '@/app/utils/key/dialog-key.util';
import { Suspense } from 'react';
import MyResultContainer from './components/my-result-container';
import SignButtonGroup from '@/app/ui/user/user-info-navigator/sign-button-group';
import Responsive from '@/app/ui/responsive';
import TakenLectureSkeleton from '@/app/ui/lecture/taken-lecture/taken-lecture-skeleton';

export default function MyPage() {
return (
<>
<ContentContainer className="flex">
<div className="hidden lg:w-[30%] lg:block">
<Suspense fallback={<UserInfoNavigatorSkeleton />}>
<UserInfoNavigator />
</Suspense>
</div>
<Responsive minWidth={1023}>
<div className="lg:w-[30%]">
<Suspense fallback={<UserInfoNavigatorSkeleton />}>
<UserInfoNavigator />
<div className="mt-9">
<SignButtonGroup />
</div>
</Suspense>
</div>
</Responsive>
<div className="w-full lg:w-[70%] lg:px-[20px] pt-12 pb-2 flex flex-col gap-12">
<MyResultContainer />
<Suspense fallback={<TakenLectureSkeleton />}>
Expand Down
21 changes: 21 additions & 0 deletions app/__test__/ui/user/user-info-navigator.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import '@testing-library/jest-dom';
import UserInfoNavigator from '@/app/ui/user/user-info-navigator/user-info-navigator';
import { render, screen } from '@testing-library/react';

jest.mock('next/headers', () => ({
cookies: jest.fn().mockReturnValue({
get: jest.fn().mockReturnValue({
value: 'fake-access-token',
}),
}),
}));

describe('UserInfoNavigator', () => {
it('UserInfoNavigator๋ฅผ ๋ Œ๋”๋งํ•œ๋‹ค.', async () => {
render(await UserInfoNavigator());

expect(await screen.findByText(/์žฅ์ง„์šฑ/i)).toBeInTheDocument();
expect(await screen.findByText(/๋””์ง€ํ„ธ์ฝ˜ํ…์ธ ๋””์ž์ธํ•™๊ณผ/i)).toBeInTheDocument();
expect(await screen.findByText(/60181666/i)).toBeInTheDocument();
});
});
49 changes: 48 additions & 1 deletion app/business/user/user.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { FormState } from '@/app/ui/view/molecule/form/form-root';
import { API_PATH } from '../api-path';
import { SignUpRequestBody, SignInRequestBody, ValidateTokenResponse } from './user.type';
import { SignUpRequestBody, SignInRequestBody, ValidateTokenResponse, UserDeleteRequestBody } from './user.type';
import { httpErrorHandler } from '@/app/utils/http/http-error-handler';
import { BadRequestError } from '@/app/utils/http/http-error';
import {
Expand All @@ -15,6 +15,53 @@ import { cookies } from 'next/headers';
import { isValidation } from '@/app/utils/zod/validation.util';
import { redirect } from 'next/navigation';

export async function signOut() {
cookies().delete('accessToken');
cookies().delete('refreshToken');

redirect('/sign-in');
}

export async function deleteUser(prevState: FormState, formData: FormData): Promise<FormState> {
try {
const body: UserDeleteRequestBody = {
password: formData.get('password') as string,
};

const response = await fetch(`${API_PATH.user}/delete-me`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${cookies().get('accessToken')?.value}`,
},
body: JSON.stringify(body),
});
const result = await response.json();

httpErrorHandler(response, result);
} catch (error) {
if (error instanceof BadRequestError) {
// ์ž˜๋ชป๋œ ์š”์ฒญ ์ฒ˜๋ฆฌ ๋กœ์ง
return {
isSuccess: false,
isFailure: true,
validationError: {},
message: error.message,
};
} else {
// ๋‚˜๋จธ์ง€ ์—๋Ÿฌ๋Š” ๋” ์ƒ์œ„ ์ˆ˜์ค€์—์„œ ์ฒ˜๋ฆฌ
throw error;
}
}

return {
isSuccess: true,
isFailure: false,
validationError: {},
message: 'ํšŒ์› ํƒˆํ‡ด๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
};
}

export async function validateToken(): Promise<ValidateTokenResponse | false> {
const accessToken = cookies().get('accessToken')?.value;
const refreshToken = cookies().get('refreshToken')?.value;
Expand Down
13 changes: 13 additions & 0 deletions app/business/user/user.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ import { cookies } from 'next/headers';
import { isValidation } from '@/app/utils/zod/validation.util';
import { InitUserInfoResponse, UserInfoResponse } from './user.type';
import { UserInfoResponseSchema, InitUserInfoResponseSchema } from './user.validation';
import { UnauthorizedError } from '@/app/utils/http/http-error';

export async function auth(): Promise<InitUserInfoResponse | UserInfoResponse | undefined> {
try {
const result = await fetchUserInfo();
return result;
} catch (error) {
if (error instanceof UnauthorizedError) {
return;
}
throw error;
}
}

export async function fetchUserInfo(): Promise<InitUserInfoResponse | UserInfoResponse> {
try {
Expand Down
9 changes: 9 additions & 0 deletions app/business/user/user.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ export interface SignUpRequestBody {
engLv: string;
}

export interface SignInRequestBody {
authId: string;
password: string;
}

export interface UserDeleteRequestBody {
password: string;
}

export type SignInResponse = z.infer<typeof SignInResponseSchema>;

export type UserInfoResponse = z.infer<typeof UserInfoResponseSchema>;
Expand Down
4 changes: 4 additions & 0 deletions app/business/user/user.validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from 'zod';
import { UserInfoResponse, InitUserInfoResponse } from './user.type';

export const UserInfoResponseSchema = z.object({
studentNumber: z.string(),
Expand Down Expand Up @@ -71,3 +72,6 @@ export const SignUpFormSchema = z
});
}
});
export function isInitUser(x: UserInfoResponse | InitUserInfoResponse): x is InitUserInfoResponse {
return typeof x.studentName === null;
}
3 changes: 2 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Metadata } from 'next';
import './globals.css';
import NavigationBar from './ui/view/molecule/navigation-bar';
import { Toaster } from './ui/view/molecule/toast/toaster';
import Provider from './provider';
import MSWComponent from './mocks/msw-component.mock';
import UserDeleteModal from './ui/user/user-info-navigator/user-delete-modal';

export const metadata: Metadata = {
title: 'Create Next App',
Expand Down Expand Up @@ -31,6 +31,7 @@ export default function RootLayout({
</Provider>
</div>
<Toaster />
<UserDeleteModal />
</body>
</html>
);
Expand Down
9 changes: 9 additions & 0 deletions app/mocks/db.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type MockDatabaseAction = {
deleteTakenLecture: (lectureId: number) => boolean;
createUser: (user: SignUpRequestBody) => boolean;
signIn: (userData: SignInRequestBody) => boolean;
deleteUser: (authId: string, password: string) => boolean;
getCredits: () => CreditResponse[];
getUserInfo: (authId: string) => UserInfoResponse | InitUserInfoResponse;
getResultCategoryDetailInfo: () => ResultCategoryDetailResponse;
Expand Down Expand Up @@ -84,6 +85,14 @@ export const mockDatabase: MockDatabaseAction = {
}
return mockDatabaseStore.userInfo;
},
deleteUser: (authId: string, password: string) => {
const user = mockDatabaseStore.users.find((u) => u.authId === authId && u.password === password);
if (user) {
mockDatabaseStore.users = mockDatabaseStore.users.filter((u) => u.authId !== authId);
return true;
}
return false;
},
};

const initialState: MockDatabaseState = {
Expand Down
33 changes: 33 additions & 0 deletions app/mocks/handlers/user-handler.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import {
SignInResponse,
ValidateTokenResponse,
UserInfoResponse,
UserDeleteRequestBody,
InitUserInfoResponse,
} from '@/app/business/user/user.type';
import { ErrorResponseData } from '@/app/utils/http/http-error-handler';
import { StrictRequest } from 'msw';

function mockDecryptToken(token: string) {
if (token === 'fake-access-token') {
Expand All @@ -22,6 +24,21 @@ function mockDecryptToken(token: string) {
};
}

export const devModeAuthGuard = (request: StrictRequest<any>) => {
if (process.env.NODE_ENV === 'development') {
const accessToken = request.headers.get('Authorization')?.replace('Bearer ', '');
if (accessToken === 'undefined' || !accessToken) {
throw new Error('Unauthorized');
}

return mockDecryptToken(accessToken);
} else {
return {
authId: 'admin',
};
}
};

export const userHandlers = [
http.get<never, never, never>(`${API_PATH.auth}/failure`, async ({ request }) => {
await delay(500);
Expand All @@ -32,6 +49,22 @@ export const userHandlers = [
accessToken: 'fake-access-token',
});
}),
http.delete<never, UserDeleteRequestBody, never>(`${API_PATH.user}/delete-me`, async ({ request }) => {
try {
const { authId } = devModeAuthGuard(request);
const { password } = await request.json();

const result = mockDatabase.deleteUser(authId, password);

if (result) {
return HttpResponse.json({ status: 200 });
} else {
return HttpResponse.json({ status: 400, message: '๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค' }, { status: 400 });
}
} catch {
return HttpResponse.json({ status: 401, message: 'Unauthorized' }, { status: 401 });
}
}),
http.get<never, never, UserInfoResponse | InitUserInfoResponse | ErrorResponseData>(
API_PATH.user,
async ({ request }) => {
Expand Down
2 changes: 1 addition & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import mainBookBackground from '../public/assets/main-book-background.png';
import mainMyongjiLogo from '../public/assets/main-myongji-logo.png';
import graduationCap from '../public/assets/graduation-cap.png';
import Responsive from './ui/responsive';
import NavigationBar from './ui/view/molecule/navigation-bar';
import NavigationBar from './(sub-page)/components/navigation-bar';
import Button from './ui/view/atom/button/button';
import Link from 'next/link';

Expand Down
2 changes: 2 additions & 0 deletions app/store/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const initialState = {
[DIALOG_KEY.RESULT_CATEGORY]: false,
[DIALOG_KEY.DIALOG_TEST]: true,
[DIALOG_KEY.LECTURE_SEARCH]: false,
[DIALOG_KEY.USER_DELETE]: false,
[DIALOG_KEY.SIDE_NAVIGATION]: false,
};

const dialogAtom = atom(initialState);
Expand Down
14 changes: 5 additions & 9 deletions app/ui/user/user-info-card/user-info-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@ import { fetchUserInfo } from '@/app/business/user/user.query';
import { InitUserInfoResponse, UserInfoResponse } from '@/app/business/user/user.type';
import InitUserAnnounce from './init-user-announce';
import UserInfoContent from './user-info-content';
import { isInitUser } from '@/app/business/user/user.validation';

function renderUserInfo(data: UserInfoResponse | InitUserInfoResponse) {
isInitUser(data) ? <InitUserAnnounce /> : <UserInfoContent data={data} />;
}

async function UserInfoCard() {
const data = await fetchUserInfo();

function isInitUser(x: UserInfoResponse | InitUserInfoResponse): x is InitUserInfoResponse {
return typeof x.studentName === null;
}

function renderUserInfo(data: UserInfoResponse | InitUserInfoResponse) {
isInitUser(data) ? <InitUserAnnounce /> : <UserInfoContent data={data} />;
}

return <>{renderUserInfo(data)}</>;
}

Expand Down
20 changes: 20 additions & 0 deletions app/ui/user/user-info-navigator/sign-button-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { auth } from '@/app/business/user/user.query';
import SignOutButton from './sign-out-button';
import UserDeleteButton from './user-delete-button';
import SignInLinkButton from './sign-in-link-button';

export default async function SignButtonGroup() {
const userInfo = await auth();
return (
<div className="flex flex-col items-center mt-9 space-y-2">
{userInfo ? (
<>
<SignOutButton />
<UserDeleteButton />
</>
) : (
<SignInLinkButton />
)}
</div>
);
}
Loading
Loading