diff --git a/src/App.tsx b/src/App.tsx index ceab610..807f64f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ - import { Toaster } from "@/components/ui/toaster"; import { Toaster as Sonner } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; @@ -19,7 +18,7 @@ import Login from "./pages/Login"; import Register from "./pages/Register"; import ForgotPassword from "./pages/ForgotPassword"; import { initSyncSettings } from "./utils/syncUtils"; -import { AuthProvider } from "./contexts/AuthContext"; +import { AuthProvider } from "./contexts/auth"; // 전역 오류 처리 const handleError = (error: any) => { diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 06371f5..a06ddb0 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Bell } from 'lucide-react'; -import { useAuth } from '@/contexts/AuthContext'; +import { useAuth } from '@/contexts/auth'; const Header: React.FC = () => { const { user } = useAuth(); diff --git a/src/components/SyncSettings.tsx b/src/components/SyncSettings.tsx index df0f817..08a65b2 100644 --- a/src/components/SyncSettings.tsx +++ b/src/components/SyncSettings.tsx @@ -4,8 +4,8 @@ import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { CloudUpload, RefreshCw } from "lucide-react"; import { isSyncEnabled, setSyncEnabled, syncAllData, getLastSyncTime } from "@/utils/syncUtils"; -import { toast } from "@/components/ui/use-toast"; -import { useAuth } from "@/contexts/AuthContext"; +import { toast } from "@/hooks/useToast.wrapper"; +import { useAuth } from "@/contexts/auth"; import { useNavigate } from "react-router-dom"; import { Button } from "@/components/ui/button"; diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 1889106..219517c 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,215 +1,4 @@ -import React, { createContext, useContext, useEffect, useState } from 'react'; -import { supabase } from '@/lib/supabase'; -import { Session, User } from '@supabase/supabase-js'; -import { useToast } from '@/components/ui/use-toast'; +import { AuthProvider, useAuth } from './auth'; -type AuthContextType = { - session: Session | null; - user: User | null; - loading: boolean; - signIn: (email: string, password: string) => Promise<{ error: any }>; - signUp: (email: string, password: string, username: string) => Promise<{ error: any, user: any }>; - signOut: () => Promise; - resetPassword: (email: string) => Promise<{ error: any }>; -}; - -const AuthContext = createContext(undefined); - -export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [session, setSession] = useState(null); - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - const { toast } = useToast(); - - useEffect(() => { - // 현재 세션 체크 - const getSession = async () => { - try { - const { data, error } = await supabase.auth.getSession(); - - if (error) { - console.error('세션 로딩 중 오류:', error); - toast({ - title: '세션 로드 오류', - description: '사용자 세션을 불러오는 중 문제가 발생했습니다.', - variant: 'destructive', - }); - } else { - setSession(data.session); - setUser(data.session?.user ?? null); - } - } catch (error) { - console.error('세션 확인 중 예외 발생:', error); - } finally { - setLoading(false); - } - }; - - getSession(); - - // auth 상태 변경 리스너 - const { data: { subscription } } = supabase.auth.onAuthStateChange( - async (event, session) => { - console.log('Supabase auth 이벤트:', event); - setSession(session); - setUser(session?.user ?? null); - setLoading(false); - } - ); - - // 리스너 정리 - return () => { - subscription.unsubscribe(); - }; - }, [toast]); - - const signIn = async (email: string, password: string) => { - try { - const { error } = await supabase.auth.signInWithPassword({ email, password }); - if (error) { - console.error('로그인 오류:', error); - toast({ - title: '로그인 실패', - description: error.message, - variant: 'destructive', - }); - return { error }; - } - - toast({ - title: '로그인 성공', - description: '환영합니다!', - }); - return { error: null }; - } catch (error: any) { - console.error('로그인 중 예외 발생:', error); - toast({ - title: '로그인 오류', - description: '예상치 못한 오류가 발생했습니다.', - variant: 'destructive', - }); - return { error }; - } - }; - - const signUp = async (email: string, password: string, username: string) => { - try { - // Supabase 인증으로 사용자 생성 - const { data, error } = await supabase.auth.signUp({ - email, - password, - options: { - data: { - username, - }, - }, - }); - - if (error) { - console.error('회원가입 오류:', error); - toast({ - title: '회원가입 실패', - description: error.message, - variant: 'destructive', - }); - return { error, user: null }; - } - - toast({ - title: '회원가입 성공', - description: '이메일 확인 후 로그인해주세요.', - }); - - return { error: null, user: data.user }; - } catch (error: any) { - console.error('회원가입 중 예외 발생:', error); - toast({ - title: '회원가입 오류', - description: '예상치 못한 오류가 발생했습니다.', - variant: 'destructive', - }); - return { error, user: null }; - } - }; - - const signOut = async () => { - try { - const { error } = await supabase.auth.signOut(); - - if (error) { - console.error('로그아웃 오류:', error); - toast({ - title: '로그아웃 실패', - description: error.message, - variant: 'destructive', - }); - return; - } - - toast({ - title: '로그아웃 성공', - description: '다음에 또 만나요!', - }); - } catch (error) { - console.error('로그아웃 중 예외 발생:', error); - toast({ - title: '로그아웃 오류', - description: '예상치 못한 오류가 발생했습니다.', - variant: 'destructive', - }); - } - }; - - const resetPassword = async (email: string) => { - try { - const { error } = await supabase.auth.resetPasswordForEmail(email, { - redirectTo: window.location.origin + '/reset-password', - }); - - if (error) { - console.error('비밀번호 재설정 오류:', error); - toast({ - title: '비밀번호 재설정 실패', - description: error.message, - variant: 'destructive', - }); - return { error }; - } - - toast({ - title: '비밀번호 재설정 이메일 전송됨', - description: '이메일을 확인하여 비밀번호를 재설정해주세요.', - }); - return { error: null }; - } catch (error: any) { - console.error('비밀번호 재설정 중 예외 발생:', error); - toast({ - title: '비밀번호 재설정 오류', - description: '예상치 못한 오류가 발생했습니다.', - variant: 'destructive', - }); - return { error }; - } - }; - - const value = { - session, - user, - loading, - signIn, - signUp, - signOut, - resetPassword, - }; - - return {children}; -}; - -export const useAuth = () => { - const context = useContext(AuthContext); - if (context === undefined) { - throw new Error('useAuth는 AuthProvider 내부에서 사용해야 합니다'); - } - return context; -}; +export { AuthProvider, useAuth }; diff --git a/src/contexts/auth/AuthProvider.tsx b/src/contexts/auth/AuthProvider.tsx new file mode 100644 index 0000000..6dc86e7 --- /dev/null +++ b/src/contexts/auth/AuthProvider.tsx @@ -0,0 +1,78 @@ + +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { supabase } from '@/lib/supabase'; +import { Session, User } from '@supabase/supabase-js'; +import { toast } from '@/hooks/useToast.wrapper'; +import { AuthContextType } from './types'; +import * as authActions from './authActions'; + +const AuthContext = createContext(undefined); + +export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [session, setSession] = useState(null); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // 현재 세션 체크 + const getSession = async () => { + try { + const { data, error } = await supabase.auth.getSession(); + + if (error) { + console.error('세션 로딩 중 오류:', error); + toast({ + title: '세션 로드 오류', + description: '사용자 세션을 불러오는 중 문제가 발생했습니다.', + variant: 'destructive', + }); + } else { + setSession(data.session); + setUser(data.session?.user ?? null); + } + } catch (error) { + console.error('세션 확인 중 예외 발생:', error); + } finally { + setLoading(false); + } + }; + + getSession(); + + // auth 상태 변경 리스너 + const { data: { subscription } } = supabase.auth.onAuthStateChange( + async (event, session) => { + console.log('Supabase auth 이벤트:', event); + setSession(session); + setUser(session?.user ?? null); + setLoading(false); + } + ); + + // 리스너 정리 + return () => { + subscription.unsubscribe(); + }; + }, []); + + // 인증 작업 메서드들 + const value: AuthContextType = { + session, + user, + loading, + signIn: authActions.signIn, + signUp: authActions.signUp, + signOut: authActions.signOut, + resetPassword: authActions.resetPassword, + }; + + return {children}; +}; + +export const useAuth = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth는 AuthProvider 내부에서 사용해야 합니다'); + } + return context; +}; diff --git a/src/contexts/auth/authActions.ts b/src/contexts/auth/authActions.ts new file mode 100644 index 0000000..895d017 --- /dev/null +++ b/src/contexts/auth/authActions.ts @@ -0,0 +1,134 @@ + +import { supabase } from '@/lib/supabase'; +import { toast } from '@/hooks/useToast.wrapper'; + +export const signIn = async (email: string, password: string) => { + try { + const { error } = await supabase.auth.signInWithPassword({ email, password }); + if (error) { + console.error('로그인 오류:', error); + toast({ + title: '로그인 실패', + description: error.message, + variant: 'destructive', + }); + return { error }; + } + + toast({ + title: '로그인 성공', + description: '환영합니다!', + }); + return { error: null }; + } catch (error: any) { + console.error('로그인 중 예외 발생:', error); + toast({ + title: '로그인 오류', + description: '예상치 못한 오류가 발생했습니다.', + variant: 'destructive', + }); + return { error }; + } +}; + +export const signUp = async (email: string, password: string, username: string) => { + try { + // Supabase 인증으로 사용자 생성 + const { data, error } = await supabase.auth.signUp({ + email, + password, + options: { + data: { + username, + }, + }, + }); + + if (error) { + console.error('회원가입 오류:', error); + toast({ + title: '회원가입 실패', + description: error.message, + variant: 'destructive', + }); + return { error, user: null }; + } + + toast({ + title: '회원가입 성공', + description: '이메일 확인 후 로그인해주세요.', + }); + + return { error: null, user: data.user }; + } catch (error: any) { + console.error('회원가입 중 예외 발생:', error); + toast({ + title: '회원가입 오류', + description: '예상치 못한 오류가 발생했습니다.', + variant: 'destructive', + }); + return { error, user: null }; + } +}; + +export const signOut = async () => { + try { + const { error } = await supabase.auth.signOut(); + + if (error) { + console.error('로그아웃 오류:', error); + toast({ + title: '로그아웃 실패', + description: error.message, + variant: 'destructive', + }); + return { error }; + } + + toast({ + title: '로그아웃 성공', + description: '다음에 또 만나요!', + }); + return { error: null }; + } catch (error: any) { + console.error('로그아웃 중 예외 발생:', error); + toast({ + title: '로그아웃 오류', + description: '예상치 못한 오류가 발생했습니다.', + variant: 'destructive', + }); + return { error }; + } +}; + +export const resetPassword = async (email: string) => { + try { + const { error } = await supabase.auth.resetPasswordForEmail(email, { + redirectTo: window.location.origin + '/reset-password', + }); + + if (error) { + console.error('비밀번호 재설정 오류:', error); + toast({ + title: '비밀번호 재설정 실패', + description: error.message, + variant: 'destructive', + }); + return { error }; + } + + toast({ + title: '비밀번호 재설정 이메일 전송됨', + description: '이메일을 확인하여 비밀번호를 재설정해주세요.', + }); + return { error: null }; + } catch (error: any) { + console.error('비밀번호 재설정 중 예외 발생:', error); + toast({ + title: '비밀번호 재설정 오류', + description: '예상치 못한 오류가 발생했습니다.', + variant: 'destructive', + }); + return { error }; + } +}; diff --git a/src/contexts/auth/index.ts b/src/contexts/auth/index.ts new file mode 100644 index 0000000..a682455 --- /dev/null +++ b/src/contexts/auth/index.ts @@ -0,0 +1,3 @@ + +export { AuthProvider, useAuth } from './AuthProvider'; +export type { AuthContextType } from './types'; diff --git a/src/contexts/auth/types.ts b/src/contexts/auth/types.ts new file mode 100644 index 0000000..6135f2f --- /dev/null +++ b/src/contexts/auth/types.ts @@ -0,0 +1,12 @@ + +import { Session, User } from '@supabase/supabase-js'; + +export type AuthContextType = { + session: Session | null; + user: User | null; + loading: boolean; + signIn: (email: string, password: string) => Promise<{ error: any }>; + signUp: (email: string, password: string, username: string) => Promise<{ error: any, user: any }>; + signOut: () => Promise; + resetPassword: (email: string) => Promise<{ error: any }>; +}; diff --git a/src/hooks/useToast.wrapper.ts b/src/hooks/useToast.wrapper.ts new file mode 100644 index 0000000..375b775 --- /dev/null +++ b/src/hooks/useToast.wrapper.ts @@ -0,0 +1,5 @@ + +import { useToast as useOriginalToast, toast as originalToast } from '@/hooks/use-toast'; + +export const useToast = useOriginalToast; +export const toast = originalToast; diff --git a/src/pages/ForgotPassword.tsx b/src/pages/ForgotPassword.tsx index 61d1306..3e6c9d7 100644 --- a/src/pages/ForgotPassword.tsx +++ b/src/pages/ForgotPassword.tsx @@ -1,11 +1,10 @@ - import React, { useState } from "react"; import { Link } from "react-router-dom"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { ArrowLeft, Mail, ArrowRight } from "lucide-react"; -import { useAuth } from "@/contexts/AuthContext"; +import { useAuth } from "@/contexts/auth"; const ForgotPassword = () => { const [email, setEmail] = useState(""); diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index ae3332a..9281bbb 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,12 +1,11 @@ - import React, { useState, useEffect } from "react"; import { Link, useNavigate } from "react-router-dom"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { ArrowRight, Mail, KeyRound, Eye, EyeOff } from "lucide-react"; -import { useToast } from "@/components/ui/use-toast"; -import { useAuth } from "@/contexts/AuthContext"; +import { useToast } from "@/hooks/useToast.wrapper"; +import { useAuth } from "@/contexts/auth"; const Login = () => { const [email, setEmail] = useState(""); diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index f090762..092bd15 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -1,12 +1,11 @@ - import React, { useState, useEffect } from "react"; import { Link, useNavigate } from "react-router-dom"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { ArrowRight, Mail, KeyRound, User, Eye, EyeOff } from "lucide-react"; -import { useToast } from "@/components/ui/use-toast"; -import { useAuth } from "@/contexts/AuthContext"; +import { useToast } from "@/hooks/useToast.wrapper"; +import { useAuth } from "@/contexts/auth"; const Register = () => { const [username, setUsername] = useState(""); diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 24e608a..adf438f 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,11 +1,10 @@ - import React from 'react'; import { useNavigate } from 'react-router-dom'; import NavBar from '@/components/NavBar'; import SyncSettings from '@/components/SyncSettings'; import { User, CreditCard, Bell, Lock, HelpCircle, LogOut, ChevronRight } from 'lucide-react'; import { cn } from '@/lib/utils'; -import { useAuth } from '@/contexts/AuthContext'; +import { useAuth } from '@/contexts/auth'; const SettingsOption = ({ icon: Icon,