Skip to content
Open
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
7 changes: 7 additions & 0 deletions apps/cafe/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "dotenv/config";
import type { ConfigContext, ExpoConfig } from "expo/config";

export default ({ config }: ConfigContext): ExpoConfig => ({
Expand Down Expand Up @@ -34,4 +35,10 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
typedRoutes: true,
baseUrl: "/app-factory/cafe",
},
extra: {
NAVER_CLIENT_ID: process.env.NAVER_CLIENT_ID,
NAVER_CLIENT_SECRET: process.env.NAVER_CLIENT_SECRET,
SUPABASE_URL: process.env.EXPO_PUBLIC_SUPABASE_URL,
SUPABASE_ANON_KEY: process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY,
},
});
4 changes: 4 additions & 0 deletions apps/cafe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
},
"dependencies": {
"@expo/vector-icons": "^14.0.4",
"@react-native-async-storage/async-storage": "^2.1.0",
"@react-navigation/native": "^7.1.14",
"@supabase/supabase-js": "^2.45.4",
"clsx": "^2.1.1",
"expo": "~52.0.47",
"expo-auth-session": "~6.0.2",
"expo-constants": "~17.0.8",
"expo-font": "~13.0.4",
"expo-linking": "~7.0.5",
Expand All @@ -40,6 +43,7 @@
"react-native-reanimated": "~3.16.7",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "4.4.0",
"react-native-url-polyfill": "^2.0.0",
"react-native-web": "~0.19.13",
"tailwindcss": "catalog:"
},
Expand Down
197 changes: 173 additions & 24 deletions apps/cafe/src/app/(tabs)/mypage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ import {
User,
} from "lucide-react-native";
import { useState } from "react";
import { Image, Modal, Pressable, ScrollView, View } from "react-native";

import {
Image,
type ImageSourcePropType,
Modal,
Pressable,
ScrollView,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { TouchableOpacity } from "react-native";
import { useAuth } from "@/features/auth/auth-context";
import { useSupabaseAuth } from "@/features/auth/supabase/auth-context";

interface MenuItem {
icon: LucideIcon;
Expand All @@ -27,8 +36,7 @@ interface RecentOrder {
name: string;
price: string;
date: string;
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
image: any;
image: ImageSourcePropType;
}

const recentOrders: RecentOrder[] = [
Expand Down Expand Up @@ -61,16 +69,49 @@ const MenuItem = ({ icon: Icon, label, onPress }: MenuItem) => (
onPress={onPress}
>
<ThemedView className="flex-row items-center">
<Icon size={20} className="mr-3 text-gray-600" />
<Icon className="mr-3 text-gray-600" size={20} />
<ThemedText>{label}</ThemedText>
</ThemedView>
<ChevronRight size={20} className="text-gray-400" />
<ChevronRight className="text-gray-400" size={20} />
</TouchableOpacity>
);

export default function MyPage() {
// biome-ignore lint/correctness/noUnusedVariables: <explanation>
const [isBottomSheetVisible, setIsBottomSheetVisible] = useState(false);
// Naver auth
const {
isSignedIn: isNaverSignedIn,
user: naverUser,
isLoading: isNaverLoading,
signInWithNaver,
signOut: signOutNaver,
} = useAuth();
// Supabase auth
const {
isSignedIn: isSupabaseSignedIn,
user: supabaseUser,
isLoading: isSupabaseLoading,
signIn,
signUp,
signOut: signOutSupabase,
} = useSupabaseAuth();

// Combined auth view state
const isSignedIn = isNaverSignedIn || isSupabaseSignedIn;
const isLoading = isNaverLoading || isSupabaseLoading;
const user = naverUser
? naverUser
: supabaseUser && {
id: supabaseUser.id,
email: supabaseUser.email ?? undefined,
};

const [email, setEmail] = useState("");
const [password, setPassword] = useState("");

const signOut = async () => {
await Promise.allSettled([signOutSupabase(), signOutNaver()]);
};

const menuItems: MenuItem[] = [
{ icon: ShoppingBag, label: "주문 내역" },
Expand All @@ -89,17 +130,125 @@ export default function MyPage() {
<ScrollView>
<ThemedView className="flex-1">
<ThemedView className="flex-row items-center border-gray-200 border-b p-4">
<User size={64} className="text-gray-600" />
<ThemedView className="ml-4">
<ThemedText type="title">사용자님</ThemedText>
<ThemedText className="text-gray-600">
[email protected]
</ThemedText>
</ThemedView>
{isSignedIn ? (
<>
{user?.profile_image ? (
<Image
className="h-16 w-16 rounded-full"
source={{ uri: user.profile_image }}
/>
) : (
<User className="text-gray-600" size={64} />
)}
<ThemedView className="ml-4">
<ThemedText type="title">
{user?.nickname || user?.name || "네이버 사용자"}
</ThemedText>
{!!user?.email && (
<ThemedText className="text-gray-600">
{user.email}
</ThemedText>
)}
</ThemedView>
</>
) : (
<>
<User className="text-gray-600" size={64} />
<ThemedView className="ml-4 flex-1">
<ThemedText type="title">로그인이 필요합니다</ThemedText>
<ThemedText className="text-gray-600">
네이버 또는 이메일로 로그인해 주세요.
</ThemedText>

{/* Supabase Email/Password Auth */}
<ThemedView className="mt-3 space-y-2 px-4">
<TextInput
autoCapitalize="none"
keyboardType="email-address"
onChangeText={setEmail}
placeholder="이메일"
style={{
borderWidth: 1,
borderColor: "#e5e7eb",
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
backgroundColor: "#fff",
}}
value={email}
/>
<TextInput
onChangeText={setPassword}
placeholder="비밀번호"
secureTextEntry
style={{
borderWidth: 1,
borderColor: "#e5e7eb",
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
backgroundColor: "#fff",
}}
value={password}
/>
<ThemedView className="mt-2 flex-row gap-2">
<TouchableOpacity
className={"border"}
disabled={isLoading || !email || !password}
onPress={() => signIn(email.trim(), password)}
style={{
flex: 1,
backgroundColor: "white",
paddingVertical: 10,
borderRadius: 8,
alignItems: "center",
}}
>
<ThemedText className="font-bold text-white">
이메일로 로그인
</ThemedText>
</TouchableOpacity>
<TouchableOpacity
className={"border"}
disabled={isLoading || !email || !password}
onPress={() => signUp(email.trim(), password)}
style={{
flex: 1,
backgroundColor: "white",
paddingVertical: 10,
borderRadius: 8,
alignItems: "center",
}}
>
<ThemedText className="font-bold text-white">
회원가입
</ThemedText>
</TouchableOpacity>
</ThemedView>
</ThemedView>
</ThemedView>

{/* Naver OAuth */}
<TouchableOpacity
disabled={isLoading}
onPress={signInWithNaver}
style={{
backgroundColor: "#03C75A",
paddingVertical: 10,
paddingHorizontal: 12,
borderRadius: 8,
}}
>
<ThemedText className="font-bold text-white">
{isLoading ? "로그인 중..." : "네이버로 로그인"}
</ThemedText>
</TouchableOpacity>
</>
)}
</ThemedView>

<ThemedView className="p-4">
<ThemedText type="subtitle" className="mb-2">
<ThemedText className="mb-2" type="subtitle">
나의 설정
</ThemedText>
{menuItems.map((item, index) => (
Expand All @@ -108,18 +257,18 @@ export default function MyPage() {
</ThemedView>

<ThemedView className="p-4">
<ThemedText type="subtitle" className="mb-2">
<ThemedText className="mb-2" type="subtitle">
최근 주문
</ThemedText>
<ThemedView className="space-y-2">
{recentOrders.map((order) => (
<ThemedView
key={order.id}
className="flex-row items-center rounded-lg bg-gray-100 p-4"
key={order.id}
>
<Image
source={order.image}
className="h-16 w-16 rounded-lg"
source={order.image}
/>
<ThemedView className="ml-4 flex-1">
<ThemedText className="font-bold">{order.name}</ThemedText>
Expand All @@ -138,18 +287,18 @@ export default function MyPage() {
</ScrollView>

<Modal
visible={isBottomSheetVisible}
animationType="slide"
transparent={true}
onRequestClose={() => setIsBottomSheetVisible(false)}
transparent={true}
visible={isBottomSheetVisible}
>
<Pressable
className="flex-1 bg-black/50"
onPress={() => setIsBottomSheetVisible(false)}
>
<View className="mt-auto rounded-t-xl bg-white p-4">
<ThemedView className="p-4">
<ThemedText type="title" className="mb-4">
<ThemedText className="mb-4" type="title">
설정
</ThemedText>

Expand Down Expand Up @@ -192,20 +341,20 @@ export default function MyPage() {
<TouchableOpacity className="py-2">
<ThemedText>개인정보 처리방침</ThemedText>
</TouchableOpacity>
<TouchableOpacity className="py-2">
<TouchableOpacity className="py-2" onPress={signOut}>
<ThemedText className="text-red-500">로그아웃</ThemedText>
</TouchableOpacity>
</ThemedView>
</ThemedView>

<TouchableOpacity
onPress={() => setIsBottomSheetVisible(false)}
style={{
backgroundColor: "#e5e7eb",
alignItems: "center",
paddingVertical: 12,
borderRadius: 8,
}}
onPress={() => setIsBottomSheetVisible(false)}
>
<ThemedText className="font-bold">닫기</ThemedText>
</TouchableOpacity>
Expand Down
25 changes: 18 additions & 7 deletions apps/cafe/src/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "../global.css";

import {
DarkTheme,
DefaultTheme,
Expand All @@ -13,6 +14,9 @@ import "react-native-reanimated";

import "@/i18n";

import { AuthProvider } from "@/features/auth/auth-context";
import { SupabaseAuthProvider } from "@/features/auth/supabase/auth-context";

// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();

Expand All @@ -35,12 +39,19 @@ export default function RootLayout() {
}

return (
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="cafe/[id]" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
</ThemeProvider>
<SupabaseAuthProvider>
<AuthProvider>
<ThemeProvider
value={colorScheme === "dark" ? DarkTheme : DefaultTheme}
>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="auth" options={{ headerShown: false }} />
<Stack.Screen name="cafe/[id]" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
</ThemeProvider>
</AuthProvider>
</SupabaseAuthProvider>
);
}
Loading
Loading