Skip to content

Commit 5bd6a97

Browse files
committed
add:RN11 done
1 parent fc239ea commit 5bd6a97

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+3748
-685
lines changed

frontend/app/(tabs)/settings.tsx

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,85 @@
11
import React from "react";
2-
import { View, Text, StyleSheet } from "react-native";
2+
import { View, Text, StyleSheet, Image, ActivityIndicator, Pressable, Platform } from "react-native";
33
import { SafeAreaView } from "react-native-safe-area-context";
4+
import { useAuth } from "@/hooks/useAuth";
5+
import { useUserProfile } from "@/hooks/useUserProfile";
46

57
export default function SettingsScreen() {
8+
const { user, loading, error, signInWithGoogle, signOut } = useAuth();
9+
const { profile, loading: profileLoading } = useUserProfile(user?.uid);
10+
11+
const onPressSignIn = async () => {
12+
await signInWithGoogle();
13+
};
14+
15+
const onPressSignOut = async () => {
16+
await signOut();
17+
};
18+
619
return (
720
<SafeAreaView style={styles.container}>
821
<View style={styles.header}>
922
<Text style={styles.headerTitle}>設定</Text>
1023
</View>
1124
<View style={styles.content}>
12-
<Text style={styles.placeholderText}>設定機能は開発中です</Text>
25+
{loading ? (
26+
<ActivityIndicator size="large" color="#4C6EF5" />
27+
) : user ? (
28+
<View style={styles.card}>
29+
<View style={styles.row}>
30+
{user.photoURL ? (
31+
<Image source={{ uri: user.photoURL }} style={styles.avatar} />
32+
) : (
33+
<View style={[styles.avatar, styles.avatarFallback]}>
34+
<Text style={styles.avatarFallbackText}>
35+
{user.displayName?.[0]?.toUpperCase() || "U"}
36+
</Text>
37+
</View>
38+
)}
39+
<View style={{ flex: 1 }}>
40+
<Text style={styles.name}>{user.displayName || "Signed in user"}</Text>
41+
<Text style={styles.email}>{user.email}</Text>
42+
</View>
43+
</View>
44+
45+
<View style={styles.divider} />
46+
47+
<View style={styles.infoRow}>
48+
<Text style={styles.infoLabel}>UID</Text>
49+
<Text style={styles.infoValue} numberOfLines={1}>{user.uid}</Text>
50+
</View>
51+
<View style={styles.infoRow}>
52+
<Text style={styles.infoLabel}>Username</Text>
53+
{profileLoading ? (
54+
<ActivityIndicator size="small" />
55+
) : (
56+
<Text style={styles.infoValue}>{profile?.username ?? "未設定"}</Text>
57+
)}
58+
</View>
59+
60+
<View style={{ height: 16 }} />
61+
62+
<Pressable
63+
onPress={onPressSignOut}
64+
style={({ pressed }) => [styles.button, styles.signOutButton, pressed && styles.buttonPressed]}
65+
>
66+
<Text style={styles.buttonText}>サインアウト</Text>
67+
</Pressable>
68+
</View>
69+
) : (
70+
<View style={styles.card}>
71+
<Text style={styles.placeholderText}>サインインして機能を利用できます。</Text>
72+
{error ? <Text style={styles.errorText}>{String(error?.message || error)}</Text> : null}
73+
<View style={{ height: 16 }} />
74+
<Pressable
75+
disabled={loading}
76+
onPress={onPressSignIn}
77+
style={({ pressed }) => [styles.button, styles.signInButton, (pressed || loading) && styles.buttonPressed]}
78+
>
79+
<Text style={[styles.buttonText, { color: "#fff" }]}>Google でサインイン</Text>
80+
</Pressable>
81+
</View>
82+
)}
1383
</View>
1484
</SafeAreaView>
1585
);
@@ -34,11 +104,94 @@ const styles = StyleSheet.create({
34104
},
35105
content: {
36106
flex: 1,
107+
padding: 20,
108+
},
109+
card: {
110+
width: "100%",
111+
backgroundColor: "#fff",
112+
borderRadius: 16,
113+
padding: 20,
114+
...(Platform.select({
115+
web: { boxShadow: "0 2px 8px rgba(0,0,0,0.08)" },
116+
default: {
117+
shadowColor: "#000",
118+
shadowOpacity: 0.08,
119+
shadowRadius: 8,
120+
shadowOffset: { width: 0, height: 2 },
121+
elevation: 2,
122+
},
123+
}) as any),
124+
},
125+
row: {
126+
flexDirection: "row",
127+
alignItems: "center",
128+
gap: 12,
129+
},
130+
avatar: {
131+
width: 56,
132+
height: 56,
133+
borderRadius: 28,
134+
backgroundColor: "#dee2e6",
135+
},
136+
avatarFallback: {
137+
alignItems: "center",
37138
justifyContent: "center",
139+
},
140+
avatarFallbackText: {
141+
fontWeight: "700",
142+
color: "#495057",
143+
},
144+
name: {
145+
fontSize: 18,
146+
fontWeight: "700",
147+
color: "#212529",
148+
},
149+
email: {
150+
fontSize: 14,
151+
color: "#495057",
152+
},
153+
divider: {
154+
height: 1,
155+
backgroundColor: "#e9ecef",
156+
marginVertical: 16,
157+
},
158+
infoRow: {
159+
flexDirection: "row",
38160
alignItems: "center",
161+
justifyContent: "space-between",
162+
gap: 16,
163+
},
164+
infoLabel: {
165+
fontSize: 14,
166+
color: "#495057",
167+
},
168+
infoValue: {
169+
fontSize: 14,
170+
color: "#212529",
171+
flex: 1,
172+
textAlign: "right",
39173
},
40174
placeholderText: {
41175
fontSize: 16,
42176
color: "#666",
43177
},
178+
button: {
179+
paddingVertical: 14,
180+
borderRadius: 12,
181+
alignItems: "center",
182+
},
183+
buttonPressed: {
184+
opacity: 0.8,
185+
},
186+
signInButton: {
187+
backgroundColor: "#4C6EF5",
188+
},
189+
signOutButton: {
190+
backgroundColor: "#f1f3f5",
191+
},
192+
buttonText: {
193+
fontSize: 16,
194+
fontWeight: "600",
195+
color: "#212529",
196+
},
44197
});

frontend/hooks/useAuth.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { useEffect, useState } from 'react';
2+
import { getFirebaseAuth, getGoogleProvider } from '@/lib/firebase';
3+
import {
4+
onAuthStateChanged,
5+
signInWithPopup,
6+
signOut as fbSignOut,
7+
type User,
8+
} from 'firebase/auth';
9+
import { initUserProfile } from '@/services/profile';
10+
11+
type UseAuth = {
12+
user: User | null;
13+
loading: boolean;
14+
error: any;
15+
signInWithGoogle: () => Promise<User | null>;
16+
signOut: () => Promise<void>;
17+
};
18+
19+
export function useAuth(): UseAuth {
20+
const [user, setUser] = useState<User | null>(null);
21+
const [loading, setLoading] = useState<boolean>(true);
22+
const [error, setError] = useState<any>(null);
23+
24+
useEffect(() => {
25+
const auth = getFirebaseAuth();
26+
const unsub = onAuthStateChanged(auth, (u) => {
27+
setUser(u);
28+
setLoading(false);
29+
});
30+
return () => { try { unsub(); } catch { /* noop */ } };
31+
}, []);
32+
33+
const signInWithGoogle = async () => {
34+
setLoading(true);
35+
setError(null);
36+
try {
37+
const auth = getFirebaseAuth();
38+
const provider = getGoogleProvider();
39+
const cred = await signInWithPopup(auth, provider);
40+
setUser(cred.user);
41+
try { await initUserProfile(); } catch (_) { /* surface only auth error */ }
42+
return cred.user;
43+
} catch (e) {
44+
try {
45+
const code = (e as any)?.code;
46+
if (code === 'auth/configuration-not-found') {
47+
// Provide actionable guidance for common root causes
48+
const msg = [
49+
'Firebase Auth configuration not found.',
50+
'Check:',
51+
'- EXPO_PUBLIC_FIREBASE_API_KEY and EXPO_PUBLIC_FIREBASE_PROJECT_ID are defined',
52+
'- EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN is set (or fallback applied)',
53+
'- Google provider is enabled in Firebase Console',
54+
'- Restart dev server: expo start -c',
55+
].join('\n');
56+
setError(new Error(msg));
57+
} else {
58+
setError(e);
59+
}
60+
} catch {
61+
setError(e);
62+
}
63+
console.error(e);
64+
return null;
65+
} finally {
66+
setLoading(false);
67+
}
68+
};
69+
70+
const signOut = async () => {
71+
setLoading(true);
72+
setError(null);
73+
try {
74+
await fbSignOut(getFirebaseAuth());
75+
setUser(null);
76+
} catch (e) {
77+
setError(e);
78+
console.error(e);
79+
} finally {
80+
setLoading(false);
81+
}
82+
};
83+
84+
return { user, loading, error, signInWithGoogle, signOut };
85+
}
Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect, useState } from 'react';
2-
import { listenUserProfile, UserProfile } from '@/services/profile';
2+
import { listenUserProfile, type UserProfile } from '@/services/profile';
33

44
type UseUserProfile = {
55
profile: UserProfile | null;
@@ -22,18 +22,10 @@ export function useUserProfile(uid?: string | null): UseUserProfile {
2222
setError(null);
2323
const unsub = listenUserProfile(
2424
uid,
25-
(p) => {
26-
setProfile(p);
27-
setLoading(false);
28-
},
29-
(e) => {
30-
setError(e);
31-
setLoading(false);
32-
}
25+
(p) => { setProfile(p); setLoading(false); },
26+
(e) => { setError(e); setLoading(false); }
3327
);
34-
return () => {
35-
try { unsub && unsub(); } catch (_) { /* noop */ }
36-
};
28+
return () => { try { unsub && unsub(); } catch { /* noop */ } };
3729
}, [uid]);
3830

3931
return { profile, loading, error };

0 commit comments

Comments
 (0)