139 lines
6.6 KiB
TypeScript
139 lines
6.6 KiB
TypeScript
import { Feather } from "@expo/vector-icons";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { Link, useRouter } from "expo-router";
|
|
import React, { useState } from "react";
|
|
import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
|
|
import { useApp } from "../src/app-context";
|
|
import { AccordionSection } from "../src/components/AccordionSection";
|
|
import { AppHeader } from "../src/components/AppHeader";
|
|
import { ScreenShell } from "../src/components/ScreenShell";
|
|
import { isProduction } from "../src/config";
|
|
import { profileApi } from "../src/services";
|
|
import { Button, ErrorNotice, Loading, styles } from "../src/ui";
|
|
import { colors, radii, spacing } from "../src/theme";
|
|
|
|
export default function ProfileScreen() {
|
|
const app = useApp();
|
|
const router = useRouter();
|
|
const enabled = app.authStatus === "authenticated";
|
|
const profile = useQuery({ queryKey: ["profile"], queryFn: profileApi.me, enabled });
|
|
const documents = useQuery({ queryKey: ["documents"], queryFn: profileApi.documents, enabled });
|
|
const [downloadError, setDownloadError] = useState<unknown>();
|
|
|
|
const download = async (id: string) => {
|
|
try {
|
|
const result = await profileApi.documentUrl(id);
|
|
if (typeof window !== "undefined") window.location.assign(result.download_url);
|
|
} catch (error) { setDownloadError(error); }
|
|
};
|
|
|
|
if (!enabled) {
|
|
return (
|
|
<ScreenShell>
|
|
<AppHeader guestLabel="Гость" />
|
|
<View style={{ flex: 1, padding: spacing.lg, justifyContent: "center", gap: spacing.md }}>
|
|
<Text accessibilityRole="header" style={styles.title}>Профиль</Text>
|
|
<Text style={styles.text}>Профиль доступен после авторизации.</Text>
|
|
<Link href="/" style={styles.link}>Перейти на главную для входа</Link>
|
|
</View>
|
|
</ScreenShell>
|
|
);
|
|
}
|
|
|
|
const personal = profile.data?.profile.personal_data;
|
|
const fullName = personal?.full_name ?? "Пользователь";
|
|
|
|
return (
|
|
<ScreenShell>
|
|
<ScrollView contentContainerStyle={{ paddingBottom: spacing.xl }}>
|
|
<View style={stylesLocal.topBar}>
|
|
<Pressable accessibilityRole="button" accessibilityLabel="Назад" onPress={() => router.back()} style={({ pressed }) => [stylesLocal.backButton, pressed && stylesLocal.pressed]}>
|
|
<Feather name="arrow-left" size={20} color={colors.foreground} />
|
|
</Pressable>
|
|
<Text accessibilityRole="header" style={styles.title}>Профиль</Text>
|
|
</View>
|
|
|
|
<View style={stylesLocal.avatarBlock}>
|
|
<View style={stylesLocal.avatar}>
|
|
<Feather name="user" size={40} color={colors.primaryForeground} />
|
|
</View>
|
|
<Text style={stylesLocal.name}>{fullName}</Text>
|
|
<Text style={styles.muted}>{personal?.citizenship ? `Гражданство: ${personal.citizenship}` : "Мигрант"}</Text>
|
|
</View>
|
|
|
|
{(profile.isLoading || documents.isLoading) && <View style={{ padding: spacing.lg }}><Loading /></View>}
|
|
{(profile.error || documents.error) && (
|
|
<View style={{ paddingHorizontal: spacing.lg }}>
|
|
<ErrorNotice error={profile.error ?? documents.error} retry={() => { void profile.refetch(); void documents.refetch(); }} />
|
|
</View>
|
|
)}
|
|
|
|
<View style={{ paddingHorizontal: spacing.lg }}>
|
|
<AccordionSection
|
|
defaultOpen
|
|
title="Личная информация"
|
|
items={[
|
|
{ title: "Имя", value: personal?.full_name ?? "Не указано", icon: "user" },
|
|
{ title: "Телефон в РФ", value: personal?.russian_phone ?? "Не указано", icon: "phone" },
|
|
{ title: "Зарубежный телефон", value: personal?.foreign_phone ?? "Не указано", icon: "phone" },
|
|
{ title: "Email", value: personal?.email ?? "Не указано", icon: "mail" },
|
|
]}
|
|
/>
|
|
|
|
<AccordionSection
|
|
title="Готовые документы"
|
|
items={documents.data?.items.length
|
|
? documents.data.items.map((doc) => ({
|
|
title: doc.name,
|
|
value: new Date(doc.sent_at).toLocaleDateString("ru-RU"),
|
|
icon: "file-text" as const,
|
|
action: "download",
|
|
}))
|
|
: [{ title: "Документов пока нет", value: "Оператор отправит их в этот раздел", icon: "file-text" }]}
|
|
onItemPress={(index) => {
|
|
const doc = documents.data?.items[index];
|
|
if (doc) void download(doc.document_id);
|
|
}}
|
|
/>
|
|
|
|
<Text style={[styles.muted, { marginBottom: spacing.md }]}>
|
|
Редактирование профиля недоступно. Для изменения данных напишите оператору.
|
|
</Text>
|
|
<Link href="/" style={styles.link}>Написать оператору</Link>
|
|
|
|
{!isProduction && (
|
|
<Pressable onPress={() => router.push("/diagnostics")} style={({ pressed }) => [stylesLocal.menuItem, pressed && stylesLocal.pressed]}>
|
|
<Text style={stylesLocal.menuText}>Диагностика (dev)</Text>
|
|
<Feather name="chevron-right" size={20} color={colors.mutedForeground} />
|
|
</Pressable>
|
|
)}
|
|
|
|
<Button title="Выйти из аккаунта" danger onPress={() => void app.signOut()} />
|
|
{Boolean(downloadError) && <ErrorNotice error={downloadError} />}
|
|
</View>
|
|
</ScrollView>
|
|
</ScreenShell>
|
|
);
|
|
}
|
|
|
|
const stylesLocal = StyleSheet.create({
|
|
topBar: { flexDirection: "row", alignItems: "center", gap: spacing.md, paddingHorizontal: spacing.lg, paddingVertical: spacing.lg },
|
|
backButton: { width: 36, height: 36, borderRadius: radii.full, alignItems: "center", justifyContent: "center" },
|
|
avatarBlock: { alignItems: "center", paddingVertical: spacing.lg, marginBottom: spacing.sm },
|
|
avatar: { width: 80, height: 80, borderRadius: radii.full, backgroundColor: colors.primary, alignItems: "center", justifyContent: "center", marginBottom: spacing.md },
|
|
name: { fontSize: 18, fontWeight: "500", color: colors.foreground, marginBottom: 4 },
|
|
menuItem: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
backgroundColor: colors.card,
|
|
borderWidth: 1,
|
|
borderColor: colors.border,
|
|
borderRadius: radii.lg,
|
|
padding: spacing.lg,
|
|
marginBottom: spacing.md,
|
|
},
|
|
menuText: { fontSize: 14, fontWeight: "500", color: colors.foreground },
|
|
pressed: { backgroundColor: colors.accent },
|
|
});
|