1import * as Schema from "effect/Schema";
2
3import {
4 EnvironmentId,
5 ProjectId,
6 PullRequestInvolvement,
7 PullRequestListFilters,
8 PullRequestListState,
9} from "@t3tools/contracts";
10
11export const PullRequestListSort = Schema.Literals([
12 "ready",
13 "blocked",
14 "updated",
15 "newest",
16 "oldest",
17 "largest",
18 "smallest",
19]);
20export type PullRequestListSort = typeof PullRequestListSort.Type;
21
22export interface PullRequestListPreferences {
23 readonly involvement: PullRequestInvolvement;
24 readonly state: PullRequestListState;
25 readonly environmentId?: EnvironmentId;
26 readonly projectId?: ProjectId;
27 readonly host?: string;
28 readonly q?: string | undefined;
29 readonly draft?: "only" | "hide";
30 readonly review?: NonNullable<PullRequestListFilters["review"]>;
31 readonly checks?: NonNullable<PullRequestListFilters["checks"]>;
32 readonly author?: string;
33 readonly labels?: ReadonlyArray<string>;
34 readonly sort?: PullRequestListSort;
35}
36
37export type PullRequestListPreferencePatch = {
38 [Key in keyof PullRequestListPreferences]?: PullRequestListPreferences[Key] | undefined;
39};
40
41const DEFAULT_PULL_REQUEST_LIST_PREFERENCES = {
42 involvement: "all",
43 state: "open",
44} as const satisfies PullRequestListPreferences;
45
46const BoundedPreference = Schema.String.check(Schema.isMaxLength(200));
47const PullRequestListPreferencesSchema = Schema.Struct({
48 involvement: PullRequestInvolvement,
49 state: PullRequestListState,
50 environmentId: Schema.optional(EnvironmentId),
51 projectId: Schema.optional(ProjectId),
52 host: Schema.optional(BoundedPreference),
53 q: Schema.optional(BoundedPreference),
54 draft: PullRequestListFilters.fields.draft,
55 review: PullRequestListFilters.fields.review,
56 checks: PullRequestListFilters.fields.checks,
57 author: Schema.optional(BoundedPreference),
58 labels: Schema.optional(Schema.Array(BoundedPreference).check(Schema.isMaxLength(10))),
59 sort: Schema.optional(PullRequestListSort),
60});
61
62const decodePullRequestListPreferences = Schema.decodeUnknownOption(
63 PullRequestListPreferencesSchema,
64);
65const PULL_REQUEST_LIST_PREFERENCES_STORAGE_KEY = "t3.pullRequests.preferences";
66type PreferenceStorage = Pick<Storage, "getItem" | "setItem">;
67
68function resolvePreferenceStorage(
69 storage: PreferenceStorage | undefined,
70): PreferenceStorage | undefined {
71 return storage ?? (typeof window === "undefined" ? undefined : window.localStorage);
72}
73
74/** Only list controls are remembered. The selected row remains a URL and right-panel concern. */
75export function pullRequestListPreferences(
76 search: PullRequestListPreferences | Schema.Schema.Type<typeof PullRequestListPreferencesSchema>,
77): PullRequestListPreferences {
78 return {
79 involvement: search.involvement,
80 state: search.state,
81 ...(search.environmentId ? { environmentId: search.environmentId } : {}),
82 ...(search.projectId ? { projectId: search.projectId } : {}),
83 ...(search.host ? { host: search.host } : {}),
84 ...(search.q ? { q: search.q } : {}),
85 ...(search.draft ? { draft: search.draft } : {}),
86 ...(search.review ? { review: search.review } : {}),
87 ...(search.checks ? { checks: search.checks } : {}),
88 ...(search.author ? { author: search.author } : {}),
89 ...(search.labels && search.labels.length > 0 ? { labels: search.labels } : {}),
90 ...(search.sort && search.sort !== "ready" ? { sort: search.sort } : {}),
91 };
92}
93
94export function readPullRequestListPreferences(
95 storage?: PreferenceStorage,
96): PullRequestListPreferences {
97 try {
98 const raw = resolvePreferenceStorage(storage)?.getItem(
99 PULL_REQUEST_LIST_PREFERENCES_STORAGE_KEY,
100 );
101 if (!raw) return DEFAULT_PULL_REQUEST_LIST_PREFERENCES;
102 const decoded = decodePullRequestListPreferences(JSON.parse(raw));
103 return decoded._tag === "Some"
104 ? pullRequestListPreferences(decoded.value)
105 : DEFAULT_PULL_REQUEST_LIST_PREFERENCES;
106 } catch {
107 return DEFAULT_PULL_REQUEST_LIST_PREFERENCES;
108 }
109}
110
111export function writePullRequestListPreferences(
112 preferences: PullRequestListPreferences,
113 storage?: PreferenceStorage,
114): void {
115 try {
116 resolvePreferenceStorage(storage)?.setItem(
117 PULL_REQUEST_LIST_PREFERENCES_STORAGE_KEY,
118 JSON.stringify(preferences),
119 );
120 } catch {
121 // Storage can be full or denied; the URL remains the source of truth for this visit.
122 }
123}