Skip to content
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
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 sham

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2 changes: 1 addition & 1 deletion apps/web/app/ErrorWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { Suspense } from 'react';

import ErrorContent from '@/components/ErrorContnet';
import ErrorContent from '@/components/ErrorContent';

export default function ErrorWrapper({ children }: { children: React.ReactNode }) {
return (
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export default function Sidebar() {
</SheetHeader>
<div className="space-y-6">
<div className="flex flex-col items-center space-y-2">
<div className="bg-primary text-primary-foreground flex h-16 w-16 items-center justify-center rounded-full text-xl font-bold">
{user?.nickname.slice(0, 3)}
<div className="bg-primary text-primary-foreground flex h-24 w-24 items-center justify-center rounded-full text-xl font-bold">
{user?.nickname.slice(0, 4)}
</div>
<div className="w-full text-center">
<div className="relative flex w-full items-center justify-center gap-2 font-medium">
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function AuthProvider({ children }: { children: React.ReactNode }

useEffect(() => {
const handleAuth = async () => {
const allowPaths = ['/login', '/signup', 'update-password'];
const allowPaths = ['/login', '/signup', '/update-password'];

const isAuthenticated = await checkAuth();
if (!isAuthenticated && !allowPaths.includes(pathname)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ export default function ErrorContent({ children }: { children: React.ReactNode }
const errorDescription = searchParams.get('error_description');

if (error) {
const errorMessage = {
const message = {
code: errorCode || 'unknown',
message: errorDescription?.replace(/\+/g, ' ') || '인증 오류가 발생했습니다.',
type: error,
};

throw new Error(JSON.stringify(errorMessage));
throw new Error(JSON.stringify(message));
}
}, [searchParams]);

Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface AuthError {
}

export default function Error({ error }: ErrorPageProps) {
const errorMessage = error.message;
const message = error.message;
let errorDetails: AuthError | null = null;
let isAuthError = false;

Expand Down Expand Up @@ -66,7 +66,7 @@ export default function Error({ error }: ErrorPageProps) {
<AlertDescription>
{errorDetails
? decodeURIComponent(errorDetails.message)
: errorMessage || '서버에서 오류가 발생했습니다.'}
: message || '서버에서 오류가 발생했습니다.'}
</AlertDescription>
</Alert>

Expand Down
6 changes: 2 additions & 4 deletions apps/web/app/error/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import { useSearchParams } from 'next/navigation';

export default function ErrorPage() {
const searchParams = useSearchParams();
const errorMessage = searchParams.get('message');
const message = searchParams.get('message');

return (
<div className="error-container">
<h1>인증 오류</h1>
<div className="error-content">
<p className="error-message">
{errorMessage ? errorMessage : '알 수 없는 오류가 발생했습니다.'}
</p>
<p className="error-message">{message ? message : '알 수 없는 오류가 발생했습니다.'}</p>
<div className="error-actions">
<button onClick={() => (window.location.href = '/login')} className="retry-button">
로그인 페이지로 돌아가기
Expand Down
2 changes: 0 additions & 2 deletions apps/web/app/library/stats/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export default function StatsPage() {

const { userStat } = useUserStat();

console.log('userStat', userStat);

return (
<div className="bg-background h-full py-8">
<div className="mb-6 flex items-center">
Expand Down
6 changes: 3 additions & 3 deletions apps/web/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ export default function LoginPage() {

const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const { isSuccess, errorTitle, errorMessage } = await login(email, password);
const { isSuccess, title, message } = await login(email, password);
if (isSuccess) {
checkAuth();
router.push('/');
} else {
openMessage({
title: errorTitle,
message: errorMessage || '로그인 실패',
title: title,
message: message || '로그인 실패',
variant: 'error',
});
}
Expand Down
1 change: 0 additions & 1 deletion apps/web/app/signup/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export async function register(email: string, password: string) {
};

const response = await supabase.auth.signUp(data);
console.log('response : ', response);
if (response.error) {
// 에러를 클라이언트에 전달

Expand Down
10 changes: 5 additions & 5 deletions apps/web/app/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,19 @@ export default function SignupPage() {
return;
}

const { isSuccess, errorTitle, errorMessage } = await register(email, password);
const { isSuccess, title, message } = await register(email, password);

if (isSuccess) {
openMessage({
title: '회원가입 성공',
message: '입력한 이메일로 인증 메일을 보냈어요.',
title: title,
message: message,
variant: 'success',
onButtonClick: () => router.push('/login'),
});
} else {
openMessage({
title: errorTitle,
message: errorMessage || '회원가입 실패',
title: title,
message: message || '회원가입 실패',
variant: 'error',
});
}
Expand Down
55 changes: 31 additions & 24 deletions apps/web/app/stores/useAuthStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { immer } from 'zustand/middleware/immer';

import createClient from '@/lib/supabase/client';
import { User } from '@/types/user';
import { getErrorMessage } from '@/utils/getErrorMessage';
import { getSupabaseErrorMessage } from '@/utils/getErrorMessage';

import { withLoading } from './middleware';

Expand All @@ -28,15 +28,15 @@ interface AuthState {

changeNickname: (nickname: string) => Promise<boolean>;
sendPasswordResetLink: (email: string) => Promise<void>;
changePassword: (password: string) => Promise<boolean>;
changePassword: (password: string) => Promise<ModalResponseState>;
}

// useModalStore에서 사용할 데이터를 전달해줘야 할 때의 타입
// 기본적인 toast 제어는 store 단에서 처리할 계획
interface ModalResponseState {
isSuccess: boolean;
errorTitle?: string;
errorMessage?: string;
title: string;
message: string;
}

const useAuthStore = create(
Expand All @@ -54,21 +54,24 @@ const useAuthStore = create(
if (data.user?.identities?.length === 0) {
return {
isSuccess: false,
errorTitle: '이메일 중복',
errorMessage: '이미 가입된 이메일입니다.',
title: '이메일 중복',
message: '이미 가입된 이메일입니다.',
};
}

toast.success('회원가입 성공', { description: '만나서 반가워요!' });
return { isSuccess: true };
return {
isSuccess: true,
title: '회원가입 성공',
message: '입력한 이메일로 인증 메일을 보냈어요.',
};
} catch (error) {
if (error instanceof AuthError) {
return getErrorMessage(error.code as string);
return getSupabaseErrorMessage(error.code as string);
}
return {
isSuccess: false,
errorTitle: '회원가입 실패',
errorMessage: '회원 가입이 실패했어요.',
title: '회원가입 실패',
message: '회원 가입이 실패했어요.',
};
}
});
Expand All @@ -80,18 +83,22 @@ const useAuthStore = create(
const { error } = await supabase.auth.signInWithPassword({ email, password });
if (error) throw error;
toast.success('로그인 성공', { description: '다시 만나서 반가워요!' });
return { isSuccess: true };
return { isSuccess: true, title: '로그인 성공', message: '다시 만나서 반가워요!' };
} catch (error) {
const { code } = error as AuthError;
return getErrorMessage(code as string);
return getSupabaseErrorMessage(code as string);
}
});
},
authKaKaoLogin: async () => {
try {
const { error } = await supabase.auth.signInWithOAuth({
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'kakao',
options: {
redirectTo: `${process.env.NEXT_PUBLIC_APP_URL}`,
},
});
console.log(data);
if (error) throw error;

return true;
Expand Down Expand Up @@ -196,7 +203,7 @@ const useAuthStore = create(
return await withLoading(set, get, async () => {
try {
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `http://localhost:3000/update-password`,
redirectTo: `${process.env.NEXT_PUBLIC_APP_URL}/update-password`,
});
if (error) {
throw error;
Expand All @@ -219,16 +226,16 @@ const useAuthStore = create(
const { error } = await supabase.auth.updateUser({ password });
if (error) throw error;

toast.success('비밀번호 변경 완료', {
description: '비밀번호가 성공적으로 변경되었어요.',
});
return true;
return {
isSuccess: true,
title: '비밀번호 변경 완료',
message: '비밀번호가 성공적으로 변경되었어요.',
};
} catch (error) {
console.error('비밀번호 변경 실패:', error);
toast.error('비밀번호 변경 실패', {
description: '비밀번호 변경 중 오류가 발생했어요.',
});
return false;
// console.log(error);
// console.log(Object.entries(error));
const { code } = error as AuthError;
return getSupabaseErrorMessage(code as string);
}
});
},
Expand Down
50 changes: 42 additions & 8 deletions apps/web/app/update-password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,59 @@ export default function UpdatePasswordPage() {
return;
}

const result = await changePassword(password);

if (result) {
const { isSuccess, title, message } = await changePassword(password);
if (isSuccess) {
openMessage({
title: '비밀번호 변경 성공',
message: '비밀번호가 성공적으로 변경되었어요.',
title: title,
message: message,
variant: 'success',
onButtonClick: () => router.push('/login'),
});
} else {
openMessage({
title: title,
message: message,
variant: 'error',
});
}
};

useEffect(() => {
const supabase = createClient();
supabase.auth.onAuthStateChange(async event => {
if (event == 'PASSWORD_RECOVERY') {
setStep('reset'); // 비밀번호 재설정 단계로 이동

// 현재 세션 상태 확인
const checkCurrentSession = async () => {
const {
data: { session },
} = await supabase.auth.getSession();
if (session?.user.email) {
console.log('Current session:', session);
setStep('reset');
toast.success('이메일 인증 확인', { description: '비밀번호를 재설정 해주세요.' });
}
};

// 초기 상태 확인
checkCurrentSession();

// 인증 상태 변경 감지
const {
data: { subscription },
} = supabase.auth.onAuthStateChange(async (event, session) => {
console.log('Auth event:', event); // 디버깅용
console.log('Session:', session); // 디버깅용

if (event === 'SIGNED_IN') {
console.log('Password recovery detected');
setStep('reset');
toast.success('이메일 인증 확인', { description: '비밀번호를 재설정 해주세요.' });
}
});

// 클린업 함수
return () => {
subscription.unsubscribe();
};
}, []);

return (
Expand Down
Loading