1import {
2 projectCloneDisplayName,
3 projectCloneProgressSummary,
4 type ProjectCloneSnapshot,
5} from "@t3tools/contracts";
6import { Pressable, View } from "react-native";
7
8import { AppActivityIndicator } from "./AppActivityIndicator";
9import { cn } from "../lib/cn";
10import { AppText as Text } from "./AppText";
11
12/**
13 * Live state of the clone that backs a freshly added project, shown above
14 * the composer while the draft waits for its files. Running clones offer
15 * Cancel; failed or cancelled ones offer Retry and Remove project.
16 */
17export function ProjectCloneBanner(props: {
18 readonly clone: ProjectCloneSnapshot;
19 readonly onCancel: () => void;
20 readonly onRetry: () => void;
21 readonly onRemove: () => void;
22}) {
23 const { clone } = props;
24 const name = projectCloneDisplayName(clone);
25 if (clone.phase === "running") {
26 return (
27 <View className="flex-row items-center gap-3 rounded-2xl border border-border bg-card px-3.5 py-3">
28 <AppActivityIndicator size="small" />
29 <View className="min-w-0 flex-1">
30 <Text className="font-t3-medium text-sm" numberOfLines={1}>
31 Cloning {name}
32 </Text>
33 <Text className="text-xs text-foreground-muted" numberOfLines={1}>
34 {projectCloneProgressSummary(clone)}
35 </Text>
36 </View>
37 <BannerAction label="Cancel" onPress={props.onCancel} />
38 </View>
39 );
40 }
41 const cancelled = clone.phase === "cancelled";
42 return (
43 <View
44 className={cn(
45 "rounded-2xl border px-3.5 py-3",
46 cancelled ? "border-warning-border bg-warning" : "border-danger-border bg-danger",
47 )}
48 >
49 <Text
50 className={cn(
51 "font-t3-medium text-sm",
52 cancelled ? "text-warning-foreground" : "text-danger-foreground",
53 )}
54 numberOfLines={1}
55 >
56 {cancelled ? `Cancelled cloning ${name}` : `Failed to clone ${name}`}
57 </Text>
58 {clone.error ? (
59 <Text className="mt-0.5 text-xs text-danger-foreground" numberOfLines={3}>
60 {clone.error}
61 </Text>
62 ) : null}
63 <View className="mt-2 flex-row justify-end gap-2">
64 <BannerAction label="Remove project" onPress={props.onRemove} />
65 <BannerAction label="Retry" onPress={props.onRetry} />
66 </View>
67 </View>
68 );
69}
70
71function BannerAction(props: { readonly label: string; readonly onPress: () => void }) {
72 return (
73 <Pressable
74 accessibilityRole="button"
75 accessibilityLabel={props.label}
76 className="rounded-full border border-border bg-card px-3 py-1.5"
77 onPress={props.onPress}
78 >
79 <Text className="font-t3-medium text-xs">{props.label}</Text>
80 </Pressable>
81 );
82}