322 lines
9.1 KiB
TypeScript
322 lines
9.1 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
View,
|
|
ScrollView,
|
|
Pressable,
|
|
Image,
|
|
Switch,
|
|
Modal,
|
|
TouchableOpacity,
|
|
TouchableWithoutFeedback,
|
|
} from "react-native";
|
|
import { router } from "expo-router";
|
|
import { Text } from "@/components/ui/text";
|
|
import {
|
|
ArrowLeft,
|
|
Settings,
|
|
ChevronRight,
|
|
CreditCard,
|
|
ShieldCheck,
|
|
FileText,
|
|
HelpCircle,
|
|
History,
|
|
Bell,
|
|
LogOut,
|
|
User,
|
|
Lock,
|
|
} from "@/lib/icons";
|
|
import { ScreenWrapper } from "@/components/ScreenWrapper";
|
|
import { ShadowWrapper } from "@/components/ShadowWrapper";
|
|
import { useColorScheme } from "nativewind";
|
|
import { saveTheme, AppTheme } from "@/lib/theme";
|
|
|
|
// ── Theme bottom sheet ────────────────────────────────────────────
|
|
const THEME_OPTIONS = [
|
|
{ value: "light", label: "Light" },
|
|
{ value: "dark", label: "Dark" },
|
|
{ value: "system", label: "System Default" },
|
|
] as const;
|
|
|
|
type ThemeOption = (typeof THEME_OPTIONS)[number]["value"];
|
|
|
|
function ThemeSheet({
|
|
visible,
|
|
current,
|
|
onSelect,
|
|
onClose,
|
|
}: {
|
|
visible: boolean;
|
|
current: ThemeOption;
|
|
onSelect: (v: ThemeOption) => void;
|
|
onClose: () => void;
|
|
}) {
|
|
return (
|
|
<Modal
|
|
visible={visible}
|
|
transparent
|
|
animationType="slide"
|
|
onRequestClose={onClose}
|
|
>
|
|
<TouchableWithoutFeedback onPress={onClose}>
|
|
<View className="flex-1 bg-black/40 justify-end" />
|
|
</TouchableWithoutFeedback>
|
|
|
|
<View className="bg-card rounded-t-[16px] pb-10 px-4 pt-4">
|
|
{/* Handle */}
|
|
<View className="w-10 h-1 rounded-full bg-border self-center mb-5" />
|
|
|
|
<Text variant="p" className="text-foreground font-bold mb-4 px-1">
|
|
Appearance
|
|
</Text>
|
|
|
|
{THEME_OPTIONS.map((opt, i) => {
|
|
const selected = current === opt.value;
|
|
const isLast = i === THEME_OPTIONS.length - 1;
|
|
return (
|
|
<TouchableOpacity
|
|
key={opt.value}
|
|
activeOpacity={0.7}
|
|
onPress={() => {
|
|
onSelect(opt.value);
|
|
onClose();
|
|
}}
|
|
className={`flex-row items-center justify-between py-3.5 px-1 ${!isLast ? "border-b border-border/40" : ""}`}
|
|
>
|
|
<Text
|
|
variant="p"
|
|
className={
|
|
selected ? "text-primary font-semibold" : "text-foreground"
|
|
}
|
|
>
|
|
{opt.label}
|
|
</Text>
|
|
{selected && <View className="h-2 w-2 rounded-full bg-primary" />}
|
|
</TouchableOpacity>
|
|
);
|
|
})}
|
|
</View>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
// ── Shared menu components ────────────────────────────────────────
|
|
function MenuGroup({
|
|
label,
|
|
children,
|
|
}: {
|
|
label: string;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<View className="mb-5">
|
|
<Text variant="muted" className="text-xs font-semibold mb-2 px-1">
|
|
{label}
|
|
</Text>
|
|
<ShadowWrapper>
|
|
<View className="bg-card rounded-[10px] overflow-hidden">
|
|
{children}
|
|
</View>
|
|
</ShadowWrapper>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
function MenuItem({
|
|
icon,
|
|
label,
|
|
sublabel,
|
|
onPress,
|
|
right,
|
|
destructive = false,
|
|
isLast = false,
|
|
}: {
|
|
icon: React.ReactNode;
|
|
label: string;
|
|
sublabel?: string;
|
|
onPress?: () => void;
|
|
right?: React.ReactNode;
|
|
destructive?: boolean;
|
|
isLast?: boolean;
|
|
}) {
|
|
return (
|
|
<Pressable
|
|
onPress={onPress}
|
|
className={`flex-row items-center px-4 py-3 ${
|
|
!isLast ? "border-b border-border/40" : ""
|
|
}`}
|
|
>
|
|
<View className="h-9 w-9 rounded-[8px] items-center justify-center mr-3">
|
|
{icon}
|
|
</View>
|
|
<View className="flex-1 mt-[-10px]">
|
|
<Text
|
|
variant="p"
|
|
className={`font-medium ${destructive ? "text-red-500" : "text-foreground"}`}
|
|
>
|
|
{label}
|
|
</Text>
|
|
{sublabel ? (
|
|
<Text variant="muted" className="text-xs mt-0.5">
|
|
{sublabel}
|
|
</Text>
|
|
) : null}
|
|
</View>
|
|
{right !== undefined ? right : <ChevronRight color="#000" size={17} />}
|
|
</Pressable>
|
|
);
|
|
}
|
|
|
|
// ── Screen ────────────────────────────────────────────────────────
|
|
export default function ProfileScreen() {
|
|
const { setColorScheme, colorScheme } = useColorScheme();
|
|
const [notifications, setNotifications] = useState(true);
|
|
const [themeSheetVisible, setThemeSheetVisible] = useState(false);
|
|
|
|
const currentTheme: ThemeOption = (colorScheme as ThemeOption) ?? "system";
|
|
|
|
const handleThemeSelect = (val: AppTheme) => {
|
|
setColorScheme(val === "system" ? "system" : val);
|
|
saveTheme(val); // persist across restarts
|
|
};
|
|
|
|
return (
|
|
<ScreenWrapper className="bg-background">
|
|
{/* Header */}
|
|
<View className="px-6 pt-4 flex-row justify-between items-center">
|
|
<Pressable
|
|
onPress={() => router.back()}
|
|
className="h-10 w-10 rounded-[10px] bg-card items-center justify-center border border-border"
|
|
>
|
|
<ArrowLeft color="#0f172a" size={20} />
|
|
</Pressable>
|
|
<Text variant="h4" className="text-foreground font-semibold">
|
|
Profile
|
|
</Text>
|
|
{/* Edit Profile shortcut */}
|
|
<Pressable
|
|
onPress={() => {}}
|
|
className="h-10 w-10 rounded-[10px] bg-card items-center justify-center border border-border"
|
|
>
|
|
<User className="text-foreground" size={18} />
|
|
</Pressable>
|
|
</View>
|
|
|
|
<ScrollView
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={{
|
|
paddingHorizontal: 16,
|
|
paddingTop: 24,
|
|
paddingBottom: 80,
|
|
}}
|
|
>
|
|
{/* Avatar */}
|
|
<View className="items-center mb-8">
|
|
<View className="h-20 w-20 rounded-full border-2 border-border overflow-hidden bg-muted mb-3">
|
|
<Image
|
|
source={{
|
|
uri: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&q=80&w=300&h=300",
|
|
}}
|
|
className="h-full w-full"
|
|
/>
|
|
</View>
|
|
<Text variant="h4" className="text-foreground font-bold">
|
|
Ms. Charlotte
|
|
</Text>
|
|
<Text variant="muted" className="text-sm mt-0.5">
|
|
charlotte@example.com
|
|
</Text>
|
|
</View>
|
|
|
|
{/* Account */}
|
|
<MenuGroup label="Account">
|
|
<MenuItem
|
|
icon={<CreditCard className="text-foreground" size={17} />}
|
|
label="Subscription"
|
|
sublabel="Pro Plan — active"
|
|
onPress={() => {}}
|
|
/>
|
|
<MenuItem
|
|
icon={<History className="text-foreground" size={17} />}
|
|
label="Transaction History"
|
|
onPress={() => {}}
|
|
isLast
|
|
/>
|
|
</MenuGroup>
|
|
|
|
{/* Preferences */}
|
|
<MenuGroup label="Preferences">
|
|
<MenuItem
|
|
icon={<Bell className="text-foreground" size={17} />}
|
|
label="Push Notifications"
|
|
right={
|
|
<Switch
|
|
value={notifications}
|
|
onValueChange={setNotifications}
|
|
trackColor={{ true: "#ea580c" }}
|
|
/>
|
|
}
|
|
/>
|
|
<MenuItem
|
|
icon={<Settings className="text-foreground" size={17} />}
|
|
label="Appearance"
|
|
sublabel={
|
|
THEME_OPTIONS.find((o) => o.value === currentTheme)?.label ??
|
|
"System Default"
|
|
}
|
|
onPress={() => setThemeSheetVisible(true)}
|
|
/>
|
|
<MenuItem
|
|
icon={<Lock className="text-foreground" size={17} />}
|
|
label="Security"
|
|
sublabel="PIN & Biometrics"
|
|
onPress={() => {}}
|
|
isLast
|
|
/>
|
|
</MenuGroup>
|
|
|
|
{/* Support & Legal */}
|
|
<MenuGroup label="Support & Legal">
|
|
<MenuItem
|
|
icon={<HelpCircle className="text-foreground" size={17} />}
|
|
label="Help & Support"
|
|
onPress={() => {}}
|
|
/>
|
|
<MenuItem
|
|
icon={<ShieldCheck className="text-foreground" size={17} />}
|
|
label="Privacy Policy"
|
|
onPress={() => {}}
|
|
/>
|
|
<MenuItem
|
|
icon={<FileText className="text-foreground" size={17} />}
|
|
label="Terms of Use"
|
|
onPress={() => {}}
|
|
isLast
|
|
/>
|
|
</MenuGroup>
|
|
|
|
{/* Logout */}
|
|
<ShadowWrapper>
|
|
<View className="bg-card rounded-[10px] overflow-hidden">
|
|
<MenuItem
|
|
icon={<LogOut color="#ef4444" size={17} />}
|
|
label="Log Out"
|
|
destructive
|
|
onPress={() => {}}
|
|
right={null}
|
|
isLast
|
|
/>
|
|
</View>
|
|
</ShadowWrapper>
|
|
</ScrollView>
|
|
|
|
{/* Theme sheet */}
|
|
<ThemeSheet
|
|
visible={themeSheetVisible}
|
|
current={currentTheme}
|
|
onSelect={handleThemeSelect}
|
|
onClose={() => setThemeSheetVisible(false)}
|
|
/>
|
|
</ScreenWrapper>
|
|
);
|
|
}
|