Yaltopia-Tickets-App/app/profile.tsx
2026-03-11 22:48:53 +03:00

363 lines
10 KiB
TypeScript

import React, { useState } from "react";
import {
View,
ScrollView,
Pressable,
Image,
Switch,
InteractionManager,
} 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,
Globe,
} 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";
import { useLanguageStore, AppLanguage } from "@/lib/language-store";
import { LanguageModal } from "@/components/LanguageModal";
import { ThemeModal } from "@/components/ThemeModal";
// ── 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"];
const LANGUAGE_OPTIONS = [
{ value: "en", label: "English" },
{ value: "am", label: "Amharic" },
] as const;
type LanguageOption = (typeof LANGUAGE_OPTIONS)[number]["value"];
// ── 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={destructive ? "#ef4444" : "#94a3b8"} size={18} />
)}
</Pressable>
);
}
// ── Screen ────────────────────────────────────────────────────────
export default function ProfileScreen() {
const nav = useSirouRouter<AppRoutes>();
const { user, logout } = useAuthStore();
const { setColorScheme, colorScheme } = useColorScheme();
const { language, setLanguage } = useLanguageStore();
const [notifications, setNotifications] = useState(true);
const [themeSheetVisible, setThemeSheetVisible] = useState(false);
const [languageSheetVisible, setLanguageSheetVisible] = useState(false);
const currentTheme: ThemeOption = (colorScheme as ThemeOption) ?? "system";
const currentLanguage: LanguageOption = (language as LanguageOption) ?? "en";
const handleThemeSelect = (val: AppTheme) => {
// NativeWind 4 handles system/light/dark
setColorScheme(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={colorScheme === "dark" ? "#f8fafc" : "#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
color={colorScheme === "dark" ? "#f8fafc" : "#0f172a"}
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>
<MenuGroup label="Account">
{/* <MenuItem
icon={
<CreditCard
color={colorScheme === "dark" ? "#f8fafc" : "#0f172a"}
size={17}
/>
}
label="Subscription"
sublabel="Pro Plan — active"
onPress={() => {}}
/> */}
<MenuItem
icon={
<History
color={colorScheme === "dark" ? "#f8fafc" : "#0f172a"}
size={17}
/>
}
label="Transaction History"
onPress={() => nav.go("history")}
isLast
/>
</MenuGroup>
{/* Preferences */}
<MenuGroup label="Preferences">
{/* <MenuItem
icon={
<Bell
color={colorScheme === "dark" ? "#f8fafc" : "#0f172a"}
size={17}
/>
}
label="Push Notifications"
right={
<Switch
value={notifications}
onValueChange={setNotifications}
trackColor={{ true: "#ea580c" }}
/>
}
/> */}
<MenuItem
icon={
<Settings
color={colorScheme === "dark" ? "#f8fafc" : "#0f172a"}
size={17}
/>
}
label="Appearance"
sublabel={
THEME_OPTIONS.find((o) => o.value === currentTheme)?.label ??
"System Default"
}
onPress={() => setThemeSheetVisible(true)}
/>
<MenuItem
icon={
<Globe
color={colorScheme === "dark" ? "#f8fafc" : "#0f172a"}
size={17}
/>
}
label="Language"
sublabel={
LANGUAGE_OPTIONS.find((o) => o.value === currentLanguage)?.label ??
"English"
}
onPress={() => setLanguageSheetVisible(true)}
/>
<MenuItem
icon={
<Lock
color={colorScheme === "dark" ? "#f8fafc" : "#0f172a"}
size={17}
/>
}
label="Security"
sublabel="PIN & Biometrics"
onPress={() => {}}
isLast
/>
</MenuGroup>
{/* Support & Legal */}
<MenuGroup label="Support & Legal">
<MenuItem
icon={
<HelpCircle
color={colorScheme === "dark" ? "#f8fafc" : "#0f172a"}
size={17}
/>
}
label="Help & Support"
onPress={() => nav.go("help")}
/>
<MenuItem
icon={
<ShieldCheck
color={colorScheme === "dark" ? "#f8fafc" : "#0f172a"}
size={17}
/>
}
label="Privacy Policy"
onPress={() => nav.go("privacy")}
/>
<MenuItem
icon={
<FileText
color={colorScheme === "dark" ? "#f8fafc" : "#0f172a"}
size={17}
/>
}
label="Terms of Use"
onPress={() => nav.go("terms")}
isLast
/>
</MenuGroup>
{/* Logout */}
<ShadowWrapper>
<View className="bg-card rounded-[10px] overflow-hidden">
<MenuItem
icon={
<LogOut
color="#ef4444"
size={17}
/>
}
label="Log Out"
destructive
onPress={async () => {
await logout();
nav.go("login");
}}
right={null}
isLast
/>
</View>
</ShadowWrapper>
</ScrollView>
{/* Theme sheet */}
<ThemeModal
visible={themeSheetVisible}
current={currentTheme}
onSelect={(theme) => handleThemeSelect(theme)}
onClose={() => setThemeSheetVisible(false)}
/>
<LanguageModal
visible={languageSheetVisible}
current={currentLanguage}
onSelect={(lang) => setLanguage(lang)}
onClose={() => setLanguageSheetVisible(false)}
/>
</ScreenWrapper>
);
}