summaryrefslogtreecommitdiff
path: root/apps/web/src/actions/freeze-frame-store.ts
blob: 17102780818d08cdf786e8a37c4fc17b1cdb19b7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
"use client";

import { create } from "zustand";
import { persist } from "zustand/middleware";

export const DEFAULT_FREEZE_DURATION_SECONDS = 3;
export const MAX_FREEZE_DURATION_SECONDS = 600;

export function isValidFreezeDuration({
	seconds,
}: {
	seconds: number;
}): boolean {
	return (
		Number.isFinite(seconds) &&
		seconds > 0 &&
		seconds <= MAX_FREEZE_DURATION_SECONDS
	);
}

type DurationResolver = ({ seconds }: { seconds: number | null }) => void;

interface FreezeFrameState {
	isOpen: boolean;
	lastDurationSeconds: number;
	resolver: DurationResolver | null;
	requestDuration: () => Promise<number | null>;
	confirmDuration: ({ seconds }: { seconds: number }) => void;
	cancelDuration: () => void;
}

export const useFreezeFrameStore = create<FreezeFrameState>()(
	persist(
		(set, get) => ({
			isOpen: false,
			lastDurationSeconds: DEFAULT_FREEZE_DURATION_SECONDS,
			resolver: null,

			requestDuration: () =>
				new Promise<number | null>((resolve) => {
					// A new invocation supersedes any pending one: resolve it as
					// cancelled so its handler aborts instead of hanging.
					get().resolver?.({ seconds: null });
					set({
						isOpen: true,
						resolver: ({ seconds }) => resolve(seconds),
					});
				}),
			confirmDuration: ({ seconds }) => {
				if (!isValidFreezeDuration({ seconds })) {
					return;
				}
				get().resolver?.({ seconds });
				set({
					isOpen: false,
					resolver: null,
					lastDurationSeconds: seconds,
				});
			},
			cancelDuration: () => {
				get().resolver?.({ seconds: null });
				set({ isOpen: false, resolver: null });
			},
		}),
		{
			name: "freeze-frame-store",
			partialize: (state) => ({
				lastDurationSeconds: state.lastDurationSeconds,
			}),
		},
	),
);