Yaltopia-Tickets-App/app/profile.tsx

332 lines
9.6 KiB
TypeScript

import React, { useState } from "react";
import {
View,
ScrollView,
Pressable,
Image,
Switch,
Modal,
TouchableOpacity,
TouchableWithoutFeedback,
} from "react-native";
import { useSirouRouter } from "@sirou/react-native";
import { AppRoutes } from "@/lib/routes";
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";
import { useAuthStore } from "@/lib/auth-store";
// ── Constants ─────────────────────────────────────────────────────
const AVATAR_FALLBACK_BASE =
"https://ui-avatars.com/api/?background=ea580c&color=fff&name=";
// ── 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 nav = useSirouRouter<AppRoutes>();
const { user, logout } = useAuthStore();
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={() => nav.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={() => nav.go("edit-profile")}
className="h-10 w-10 rounded-[10px] bg-card items-center justify-center border border-border"
>
<User className="text-foreground" color="#000" 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 overflow-hidden bg-muted mb-3">
<Image
source={{
uri:
user?.avatar ||
`${AVATAR_FALLBACK_BASE}${encodeURIComponent(`${user?.firstName} ${user?.lastName}`)}`,
}}
className="h-full w-full"
/>
</View>
<Text variant="h4" className="text-foreground">
{user?.firstName} {user?.lastName}
</Text>
<Text variant="muted" className="text-sm mt-0.5">
{user?.email}
</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={logout}
right={null}
isLast
/>
</View>
</ShadowWrapper>
</ScrollView>
{/* Theme sheet */}
<ThemeSheet
visible={themeSheetVisible}
current={currentTheme}
onSelect={handleThemeSelect}
onClose={() => setThemeSheetVisible(false)}
/>
</ScreenWrapper>
);
}