Реализованы задачи бэклога 1-5 (1я не до конца)

This commit is contained in:
mi
2026-07-21 12:50:46 +03:00
parent 3b71caf7b3
commit 0d7f7a819f
105 changed files with 7185 additions and 38 deletions
@@ -1,5 +1,5 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useLocalSearchParams } from "expo-router";
import { useLocalSearchParams, useRouter } from "expo-router";
import React, { useEffect, useMemo, useState } from "react";
import { Platform, ScrollView, Text, View } from "react-native";
import { useApp } from "../../src/app-context";
@@ -23,9 +23,11 @@ export default function ChatScreen() {
const { dialogId } = useLocalSearchParams<{ dialogId: string }>();
const app = useApp();
const client = useQueryClient();
const router = useRouter();
const [text, setText] = useState("");
const [error, setError] = useState<unknown>();
const [sending, setSending] = useState(false);
const [creating, setCreating] = useState(false);
const config = useQuery({ queryKey: ["public-config"], queryFn: publicApi.config });
const dialog = useQuery({ queryKey: ["dialog", dialogId], queryFn: () => dialogApi.get(dialogId), enabled: Boolean(dialogId) && app.authStatus === "authenticated" });
const messages = useQuery({ queryKey: ["messages", dialogId], queryFn: () => dialogApi.messages(dialogId), enabled: Boolean(dialogId) && app.authStatus === "authenticated" });
@@ -104,6 +106,15 @@ export default function ChatScreen() {
} catch (reason) { setError(reason); }
};
const createDialog = async () => {
setCreating(true); setError(undefined);
try {
const created = await dialogApi.create(crypto.randomUUID());
router.replace(`/dialogs/${created.dialog_id}`);
} catch (reason) { setError(reason); }
finally { setCreating(false); }
};
const closed = dialog.data?.status === "closed";
return <ScrollView contentContainerStyle={styles.page}>
<Header status={app.authStatus} realtime={app.realtimeState} onLogout={app.authStatus === "authenticated" ? () => void app.signOut() : undefined} />
@@ -122,7 +133,11 @@ export default function ChatScreen() {
</View>)}
{!messages.isLoading && !messages.data?.items.length && <Text style={styles.muted}>Сообщений пока нет.</Text>}
</View>
{closed ? <Text style={styles.muted}>Диалог закрыт и доступен только для чтения.</Text> : <View style={styles.card}>
{closed ? <View style={styles.card}>
<Text style={styles.muted}>Диалог закрыт и доступен только для чтения.</Text>
<Button title={creating ? "Создание…" : "Начать новый диалог"} disabled={creating} onPress={() => void createDialog()} />
{error && <ErrorNotice error={error} retry={() => void createDialog()} />}
</View> : <View style={styles.card}>
<Field label="Новое сообщение" multiline value={text} onChangeText={setText} />
<View style={styles.row}>
<Button title={sending ? "Отправка…" : "Отправить"} disabled={sending || !text.trim()} onPress={() => void sendText()} />
@@ -1,6 +1,6 @@
import { useInfiniteQuery } from "@tanstack/react-query";
import { Link } from "expo-router";
import React from "react";
import { Link, useRouter } from "expo-router";
import React, { useState } from "react";
import { ScrollView, Text, View } from "react-native";
import { useApp } from "../../src/app-context";
import { dialogApi } from "../../src/services";
@@ -15,6 +15,9 @@ const statusLabels = {
export default function DialogsScreen() {
const app = useApp();
const router = useRouter();
const [creating, setCreating] = useState(false);
const [createError, setCreateError] = useState<unknown>();
const dialogs = useInfiniteQuery({
queryKey: ["dialogs"],
queryFn: ({ pageParam }) => dialogApi.list(pageParam),
@@ -22,6 +25,20 @@ export default function DialogsScreen() {
getNextPageParam: (page) => page.next_cursor ?? undefined,
enabled: app.authStatus === "authenticated",
});
const createDialog = async () => {
setCreating(true);
setCreateError(undefined);
try {
const dialog = await dialogApi.create(crypto.randomUUID());
router.push(`/dialogs/${dialog.dialog_id}`);
} catch (error) {
setCreateError(error);
} finally {
setCreating(false);
}
};
if (app.authStatus !== "authenticated") return <ScrollView contentContainerStyle={styles.page}>
<Header status={app.authStatus} realtime={app.realtimeState} />
<Text accessibilityRole="header" style={styles.title}>История диалогов</Text>
@@ -33,6 +50,8 @@ export default function DialogsScreen() {
return <ScrollView contentContainerStyle={styles.page}>
<Header status={app.authStatus} realtime={app.realtimeState} onLogout={() => void app.signOut()} />
<Text accessibilityRole="header" style={styles.title}>История диалогов</Text>
<Button title={creating ? "Создание…" : "Новый диалог"} disabled={creating} onPress={() => void createDialog()} />
{createError && <ErrorNotice error={createError} retry={() => void createDialog()} />}
{dialogs.isLoading && <Loading />}
{dialogs.error && <ErrorNotice error={dialogs.error} retry={() => void dialogs.refetch()} />}
{!dialogs.isLoading && !items.length && <Text style={styles.muted}>Диалогов пока нет.</Text>}