import { useQuery } from "@tanstack/react-query"; import { useRouter } from "expo-router"; import React, { useState } from "react"; import { Linking, ScrollView, Switch, Text, View } from "react-native"; import { useApp } from "../src/app-context"; import { AppHeader } from "../src/components/AppHeader"; import { ChatInputBar } from "../src/components/ChatInputBar"; import { HanLogo } from "../src/components/HanLogo"; import { PopularQuestionsList } from "../src/components/PopularQuestionsList"; import { QuickActions } from "../src/components/QuickActions"; import { ScreenShell } from "../src/components/ScreenShell"; import { clearPendingTextIntent, createPendingTextIntent, savePendingTextIntent, type PendingTextIntent, } from "../src/pending-intent"; import { dialogApi, publicApi, uploadAttachment } from "../src/services"; import type { Consents } from "../src/types"; import { Button, ErrorNotice, Loading, styles } from "../src/ui"; export default function HomeScreen() { const { authStatus, authorize } = useApp(); const config = useQuery({ queryKey: ["public-config"], queryFn: publicApi.config }); const content = useQuery({ queryKey: ["public-content"], queryFn: publicApi.content }); const [consentOpen, setConsentOpen] = useState(false); const [required, setRequired] = useState({ personal: false, agreement: false, marketing: false }); const [pending, setPending] = useState(null); const [message, setMessage] = useState(""); const [sendError, setSendError] = useState(); const [sending, setSending] = useState(false); const router = useRouter(); const sendAuthenticated = async (intent: PendingTextIntent) => { setSending(true); setSendError(undefined); try { const dialog = await dialogApi.create(intent.dialogKey); await dialogApi.sendText(dialog.dialog_id, intent.text, intent.messageKey); setMessage(""); setPending(null); clearPendingTextIntent(); router.push(`/dialogs/${dialog.dialog_id}`); } catch (error) { setSendError(error); } finally { setSending(false); } }; const send = async (text: string) => { const normalized = text.trim(); if (!normalized) return; if (normalized.length > 4000) { setSendError(new Error("Сообщение слишком длинное. Максимум — 4000 символов.")); return; } const intent = createPendingTextIntent(normalized); if (authStatus !== "authenticated") { setPending(intent); savePendingTextIntent(intent); setConsentOpen(true); return; } await sendAuthenticated(intent); }; const chooseFile = () => { if (authStatus !== "authenticated") { setConsentOpen(true); return; } if (typeof document === "undefined") { setSendError(new Error("Выбор файла в этой сборке доступен только в web.")); return; } const input = document.createElement("input"); input.type = "file"; input.accept = "image/*,application/pdf"; input.onchange = () => { const file = input.files?.[0]; if (file) void sendFile(file); }; input.click(); }; const sendFile = async (file: File) => { const limits = config.data?.attachments; const max = (limits?.max_size_mb ?? 5) * 1024 * 1024; const allowed = limits?.allowed_mime_types ?? ["image/jpeg", "image/png", "image/webp", "application/pdf"]; if (file.size > max || !allowed.includes(file.type)) { setSendError(new Error("Недопустимый тип файла или превышен допустимый размер.")); return; } setSending(true); setSendError(undefined); try { const dialog = await dialogApi.create(crypto.randomUUID()); const uploaded = await uploadAttachment(dialog.dialog_id, file); await dialogApi.sendFile( dialog.dialog_id, uploaded.attachmentId, uploaded.checksum, crypto.randomUUID(), ); router.push(`/dialogs/${dialog.dialog_id}`); } catch (error) { setSendError(error); } finally { setSending(false); } }; const accept = async () => { if (!required.personal || !required.agreement) return; const versions = config.data?.consents; const consents: Consents = { personal_data: { accepted: true, version: versions?.personal_data?.version ?? "current" }, user_agreement: { accepted: true, version: versions?.user_agreement?.version ?? "current" }, marketing: { accepted: required.marketing, version: versions?.marketing?.version ?? "current" }, }; setConsentOpen(false); try { const authorized = await authorize(consents); if (authorized && pending) await sendAuthenticated(pending); } catch (error) { setSendError(error); } }; const welcome = content.data?.texts.welcome; const questions = content.data?.popular_questions ?? []; return ( {(config.isLoading || content.isLoading) && } {(config.error || content.error) && ( { void config.refetch(); void content.refetch(); }} /> )} {welcome ? ( {welcome} ) : null} { setMessage(text); void send(text); }} /> void send(message)} sending={sending} value={message} /> {sendError && ( )} {consentOpen && ( Согласия перед входом Для отправки сообщения или файла необходимо войти по номеру телефона. Код вводится только на защищённой странице авторизации. {(["personal_data", "user_agreement", "marketing"] as const).map((key) => { const item = config.data?.consents?.[key]; if (!item) return null; return item.document_url ? ( void Linking.openURL(item.document_url!)}> {key === "personal_data" ? "Политика персональных данных" : key === "user_agreement" ? "Пользовательское соглашение" : "Согласие на рекламу"} · версия {item.version} ) : null; })} setRequired({ ...required, personal })} /> setRequired({ ...required, agreement })} /> setRequired({ ...required, marketing })} />