255 lines
11 KiB
TypeScript
255 lines
11 KiB
TypeScript
import React from "react";
|
|
import {
|
|
View, Text, ScrollView, TouchableOpacity,
|
|
Switch, Modal, useColorScheme,
|
|
} from "react-native";
|
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { useAuth } from "../context/AuthContext";
|
|
import AuthGate from "./AuthGate";
|
|
|
|
// ─── Sub-components ────────────────────────────────────────────────────────
|
|
|
|
type SettingRowProps = {
|
|
icon: any;
|
|
label: string;
|
|
value?: string;
|
|
toggle?: boolean;
|
|
toggleValue?: boolean;
|
|
onToggle?: (v: boolean) => void;
|
|
onPress?: () => void;
|
|
accent?: string;
|
|
};
|
|
|
|
function SettingRow({ icon, label, value, toggle, toggleValue, onToggle, onPress, accent }: SettingRowProps) {
|
|
const dark = useColorScheme() === "dark";
|
|
const iconColor = accent ?? (dark ? "#706D67" : "#8A8278");
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
onPress={onPress}
|
|
activeOpacity={onPress ? 0.7 : 1}
|
|
className={`flex-row items-center px-4 py-3.5 border-b ${dark ? "border-border-dark" : "border-border-light"}`}
|
|
>
|
|
<View
|
|
className="w-8 h-8 rounded-xl items-center justify-center mr-3"
|
|
style={{ backgroundColor: iconColor + "18" }}
|
|
>
|
|
<Ionicons name={icon} size={16} color={iconColor} />
|
|
</View>
|
|
<Text className={`flex-1 text-sm font-sans ${dark ? "text-ink-dark" : "text-ink-light"}`}>
|
|
{label}
|
|
</Text>
|
|
{toggle ? (
|
|
<Switch
|
|
value={toggleValue}
|
|
onValueChange={onToggle}
|
|
trackColor={{ true: dark ? "#E07B45" : "#C4622D", false: dark ? "#2C2A27" : "#E8E2D5" }}
|
|
thumbColor="#FFFFFF"
|
|
/>
|
|
) : value ? (
|
|
<Text className={`text-sm font-sans ${dark ? "text-muted-dark" : "text-muted-light"}`}>{value}</Text>
|
|
) : (
|
|
<Ionicons name="chevron-forward" size={14} color={dark ? "#706D67" : "#8A8278"} />
|
|
)}
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
|
|
function SectionLabel({ title }: { title: string }) {
|
|
const dark = useColorScheme() === "dark";
|
|
return (
|
|
<Text className={`px-4 pt-5 pb-2 text-xs font-sans font-bold tracking-widest uppercase ${dark ? "text-muted-dark" : "text-muted-light"}`}>
|
|
{title}
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
// ─── Guest profile (logged out) ────────────────────────────────────────────
|
|
|
|
function GuestProfile({ onSignIn }: { onSignIn: () => void }) {
|
|
const dark = useColorScheme() === "dark";
|
|
const accent = dark ? "#E07B45" : "#C4622D";
|
|
|
|
return (
|
|
<ScrollView contentContainerStyle={{ paddingBottom: 48 }} showsVerticalScrollIndicator={false}>
|
|
{/* Avatar block */}
|
|
<View
|
|
className={`items-center pt-8 pb-6 mx-4 mt-4 rounded-3xl ${dark ? "bg-obsidian-100" : "bg-surface-light"}`}
|
|
style={{
|
|
shadowColor: dark ? "#000" : "#1A1714",
|
|
shadowOpacity: dark ? 0.35 : 0.06,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowRadius: 10,
|
|
elevation: 3,
|
|
}}
|
|
>
|
|
{/* Anonymous avatar */}
|
|
<View
|
|
className={`w-20 h-20 rounded-full items-center justify-center mb-3 ${dark ? "bg-obsidian-50" : "bg-parchment-200"}`}
|
|
>
|
|
<Ionicons name="person-outline" size={36} color={dark ? "#706D67" : "#8A8278"} />
|
|
</View>
|
|
<Text className={`text-xl font-display font-bold ${dark ? "text-ink-dark" : "text-ink-light"}`}>
|
|
Guest
|
|
</Text>
|
|
<Text className={`text-sm font-sans mt-1 ${dark ? "text-muted-dark" : "text-muted-light"}`}>
|
|
Sign in to sync your reading
|
|
</Text>
|
|
</View>
|
|
|
|
{/* Settings available to guests */}
|
|
<View
|
|
className={`mx-4 mt-5 rounded-2xl overflow-hidden border ${dark ? "bg-obsidian-100 border-border-dark" : "bg-surface-light border-border-light"}`}
|
|
>
|
|
<SectionLabel title="General" />
|
|
<SettingRow icon="help-circle-outline" label="Help & support" onPress={() => {}} />
|
|
<SettingRow icon="information-circle-outline" label="About" onPress={() => {}} />
|
|
</View>
|
|
|
|
{/* Sign in / Register CTA */}
|
|
<TouchableOpacity
|
|
onPress={onSignIn}
|
|
className="mx-4 mt-4 py-4 rounded-2xl items-center"
|
|
style={{ backgroundColor: accent }}
|
|
activeOpacity={0.8}
|
|
>
|
|
<View className="flex-row items-center gap-2">
|
|
<Ionicons name="log-in-outline" size={18} color="#fff" />
|
|
<Text className="text-sm font-sans font-semibold text-white">
|
|
Sign in / Register
|
|
</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
|
|
<Text className={`text-center text-xs font-sans mt-3 ${dark ? "text-muted-dark" : "text-muted-light"}`}>
|
|
You can still browse and save locally without an account.
|
|
</Text>
|
|
</ScrollView>
|
|
);
|
|
}
|
|
|
|
// ─── Authenticated profile (logged in) ────────────────────────────────────
|
|
|
|
function AuthenticatedProfile({ onSignOut }: { onSignOut: () => void }) {
|
|
const dark = useColorScheme() === "dark";
|
|
const { user } = useAuth();
|
|
const [notifications, setNotifications] = React.useState(true);
|
|
const [digest, setDigest] = React.useState(false);
|
|
|
|
return (
|
|
<ScrollView contentContainerStyle={{ paddingBottom: 48 }} showsVerticalScrollIndicator={false}>
|
|
{/* Avatar + name block */}
|
|
<View
|
|
className={`items-center pt-8 pb-6 mx-4 mt-4 rounded-3xl ${dark ? "bg-obsidian-100" : "bg-surface-light"}`}
|
|
style={{
|
|
shadowColor: dark ? "#000" : "#1A1714",
|
|
shadowOpacity: dark ? 0.35 : 0.06,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowRadius: 10,
|
|
elevation: 3,
|
|
}}
|
|
>
|
|
<View className={`w-20 h-20 rounded-full items-center justify-center mb-3 ${dark ? "bg-accent-dark/20" : "bg-accent/10"}`}>
|
|
<Text className="text-3xl">📰</Text>
|
|
</View>
|
|
<Text className={`text-xl font-display font-bold ${dark ? "text-ink-dark" : "text-ink-light"}`}>
|
|
{user?.email?.split("@")[0] ?? "Reader"}
|
|
</Text>
|
|
<Text className={`text-sm font-sans mt-1 ${dark ? "text-muted-dark" : "text-muted-light"}`}>
|
|
{user?.email}
|
|
</Text>
|
|
|
|
{/* Stats row */}
|
|
<View className="flex-row mt-5 gap-8">
|
|
{[
|
|
{ label: "Subscribed", value: "5" },
|
|
{ label: "Read", value: "128" },
|
|
{ label: "Saved", value: "34" },
|
|
].map((stat) => (
|
|
<View key={stat.label} className="items-center">
|
|
<Text className={`text-xl font-display font-bold ${dark ? "text-ink-dark" : "text-ink-light"}`}>
|
|
{stat.value}
|
|
</Text>
|
|
<Text className={`text-xs font-sans mt-0.5 ${dark ? "text-muted-dark" : "text-muted-light"}`}>
|
|
{stat.label}
|
|
</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
{/* Settings sections */}
|
|
<View
|
|
className={`mx-4 mt-5 rounded-2xl overflow-hidden border ${dark ? "bg-obsidian-100 border-border-dark" : "bg-surface-light border-border-light"}`}
|
|
>
|
|
<SectionLabel title="Notifications" />
|
|
<SettingRow
|
|
icon="notifications-outline" label="Push notifications"
|
|
toggle toggleValue={notifications} onToggle={setNotifications}
|
|
accent={dark ? "#E07B45" : "#C4622D"}
|
|
/>
|
|
<SettingRow
|
|
icon="mail-outline" label="Daily digest email"
|
|
toggle toggleValue={digest} onToggle={setDigest}
|
|
accent={dark ? "#5E9EF4" : "#3B7DD8"}
|
|
/>
|
|
|
|
<SectionLabel title="Subscriptions" />
|
|
<SettingRow
|
|
icon="bookmark-outline" label="Manage subscriptions"
|
|
onPress={() => {}} accent={dark ? "#4EC992" : "#2E9E6B"}
|
|
/>
|
|
|
|
<SectionLabel title="Account" />
|
|
<SettingRow icon="person-outline" label="Edit profile" onPress={() => {}} />
|
|
<SettingRow icon="help-circle-outline" label="Help & support" onPress={() => {}} />
|
|
</View>
|
|
|
|
{/* Sign out */}
|
|
<TouchableOpacity
|
|
onPress={onSignOut}
|
|
className={`mx-4 mt-4 py-4 rounded-2xl items-center border ${dark ? "border-red-900/40 bg-red-950/20" : "border-red-200 bg-red-50"}`}
|
|
activeOpacity={0.7}
|
|
>
|
|
<View className="flex-row items-center gap-2">
|
|
<Ionicons name="log-out-outline" size={16} color="#ef4444" />
|
|
<Text className="text-sm font-sans font-semibold text-red-500">Sign out</Text>
|
|
</View>
|
|
</TouchableOpacity>
|
|
</ScrollView>
|
|
);
|
|
}
|
|
|
|
// ─── Root export ───────────────────────────────────────────────────────────
|
|
|
|
export default function Profile() {
|
|
const dark = useColorScheme() === "dark";
|
|
const { user, logout } = useAuth();
|
|
const [showAuth, setShowAuth] = React.useState(false);
|
|
|
|
return (
|
|
<SafeAreaView className={`flex-1 ${dark ? "bg-obsidian-200" : "bg-parchment-50"}`}>
|
|
{user
|
|
? <AuthenticatedProfile onSignOut={logout} />
|
|
: <GuestProfile onSignIn={() => setShowAuth(true)} />
|
|
}
|
|
|
|
{/* Login / Register modal */}
|
|
<Modal
|
|
visible={showAuth}
|
|
animationType="slide"
|
|
presentationStyle="pageSheet"
|
|
onRequestClose={() => setShowAuth(false)}
|
|
>
|
|
{/* Close handle */}
|
|
<View className={`pt-3 pb-1 items-center ${dark ? "bg-obsidian-200" : "bg-parchment-50"}`}>
|
|
<View className={`w-10 h-1 rounded-full ${dark ? "bg-obsidian-50" : "bg-parchment-300"}`} />
|
|
</View>
|
|
|
|
{/* AuthGate auto-closes the modal on success because user becomes non-null */}
|
|
<AuthGate onSuccess={() => setShowAuth(false)} />
|
|
</Modal>
|
|
</SafeAreaView>
|
|
);
|
|
} |