1import { Pressable, View } from "react-native";
2import Animated, { FadeIn, ReduceMotion } from "react-native-reanimated";
3import { AppText as Text } from "../../../components/AppText";
4import { SymbolView } from "../../../components/AppSymbol";
5import { PR_ROW_EXIT, PR_ROW_LAYOUT } from "../pullRequestAnimations";
6import { guideNoticeState } from "./reviewGuideState";
7import type { useReviewGuide } from "./useReviewGuide";
8
9const NOTICE_ENTER = FadeIn.delay(180).duration(120).reduceMotion(ReduceMotion.System);
10
11/** Keep the action in one place as loading, writing, and retry states change. */
12export function GuideNotice({ state }: { readonly state: ReturnType<typeof useReviewGuide> }) {
13 const notice = guideNoticeState(state);
14 if (notice.kind === "ready") return null;
15 const compact = state.data?.guide != null;
16 const failed = notice.kind === "failed" || notice.kind === "unavailable";
17 const busy = notice.kind === "loading" || notice.kind === "writing";
18 return (
19 <Animated.View
20 layout={PR_ROW_LAYOUT}
21 entering={compact ? NOTICE_ENTER : undefined}
22 exiting={PR_ROW_EXIT}
23 className={
24 compact ? "mb-5 rounded-2xl bg-grouped-card p-4" : "rounded-2xl bg-grouped-card p-5"
25 }
26 accessibilityLiveRegion="polite"
27 accessibilityState={{ busy }}
28 >
29 <View className="flex-row items-center gap-3">
30 <SymbolView
31 name={failed ? "exclamationmark.circle" : busy ? "clock" : "list.number"}
32 size={19}
33 tintColorClassName={failed ? "accent-danger-foreground" : "accent-icon-muted"}
34 />
35 <Text className="text-base font-t3-semibold">
36 {compact ? notice.title : "Review guide"}
37 </Text>
38 </View>
39 <View style={compact ? undefined : { minHeight: 100 }} className="pt-3 pb-4">
40 <Text className="text-sm leading-6 text-foreground-muted">{notice.description}</Text>
41 </View>
42 <View className="min-h-12 flex-row items-center justify-between gap-3">
43 {state.canWrite || busy || notice.kind === "unavailable" ? (
44 <Pressable
45 accessibilityRole="button"
46 accessibilityState={{ disabled: busy, busy }}
47 disabled={busy}
48 style={{ width: 152 }}
49 className={
50 compact
51 ? "min-h-12 items-center justify-center rounded-full bg-secondary px-4 active:opacity-70"
52 : "min-h-12 items-center justify-center rounded-full bg-foreground px-4 active:opacity-70"
53 }
54 onPress={() => (notice.kind === "unavailable" ? state.refresh() : void state.write())}
55 >
56 <Text
57 className={
58 compact
59 ? "text-center font-t3-medium text-secondary-foreground"
60 : "text-center font-t3-medium text-screen"
61 }
62 >
63 {notice.action}
64 </Text>
65 </Pressable>
66 ) : (
67 <Text className="text-xs text-foreground-muted">
68 {notice.kind === "failed"
69 ? "Reconnect or ask someone with write access to retry."
70 : "Writing a guide requires a connection and write access."}
71 </Text>
72 )}
73 {notice.kind === "writing" && state.data?.run?._tag === "Running" ? (
74 <Text className="shrink text-xs text-foreground-muted">
75 Started{" "}
76 {new Date(state.data.run.startedAt).toLocaleTimeString([], {
77 hour: "2-digit",
78 minute: "2-digit",
79 })}
80 </Text>
81 ) : null}
82 </View>
83 </Animated.View>
84 );
85}