forked from StellarSplit/StellarSplit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsplitGroupDataSource.ts
More file actions
120 lines (105 loc) · 3.19 KB
/
Copy pathsplitGroupDataSource.ts
File metadata and controls
120 lines (105 loc) · 3.19 KB
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { MOCK_GROUPS } from "@components/SplitGroup/data";
import type { Group } from "@src/types/split-group";
const STORAGE_KEY = "stellarsplit:groups:v1";
export interface SplitGroupDataSource {
list(): Promise<Group[]>;
create(group: Group): Promise<Group>;
update(group: Group): Promise<Group>;
remove(id: string): Promise<void>;
touchSplit(id: string): Promise<Group | null>;
}
function cloneGroup(group: Group): Group {
return {
...group,
members: group.members.map((member) => ({ ...member })),
createdAt: new Date(group.createdAt),
lastActivityAt: new Date(group.lastActivityAt),
};
}
function cloneGroups(groups: Group[]): Group[] {
return groups.map(cloneGroup);
}
function readStoredGroups(): Group[] | null {
if (typeof window === "undefined") return null;
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw) as Array<
Omit<Group, "createdAt" | "lastActivityAt"> & {
createdAt: string;
lastActivityAt: string;
}
>;
return parsed.map((group) => ({
...group,
createdAt: new Date(group.createdAt),
lastActivityAt: new Date(group.lastActivityAt),
}));
} catch {
return null;
}
}
function persistGroups(groups: Group[]) {
if (typeof window === "undefined") return;
window.localStorage.setItem(
STORAGE_KEY,
JSON.stringify(
groups.map((group) => ({
...group,
createdAt: group.createdAt.toISOString(),
lastActivityAt: group.lastActivityAt.toISOString(),
})),
),
);
}
class LocalSplitGroupDataSource implements SplitGroupDataSource {
private groups: Group[] | null = null;
private ensureLoaded() {
if (this.groups) return;
const stored = readStoredGroups();
this.groups = cloneGroups(stored ?? MOCK_GROUPS);
persistGroups(this.groups);
}
async list(): Promise<Group[]> {
this.ensureLoaded();
return cloneGroups(this.groups ?? []);
}
async create(group: Group): Promise<Group> {
this.ensureLoaded();
this.groups = [cloneGroup(group), ...(this.groups ?? [])];
persistGroups(this.groups);
return cloneGroup(group);
}
async update(group: Group): Promise<Group> {
this.ensureLoaded();
this.groups = (this.groups ?? []).map((entry) =>
entry.id === group.id ? cloneGroup(group) : entry,
);
persistGroups(this.groups);
return cloneGroup(group);
}
async remove(id: string): Promise<void> {
this.ensureLoaded();
this.groups = (this.groups ?? []).filter((entry) => entry.id !== id);
persistGroups(this.groups);
}
async touchSplit(id: string): Promise<Group | null> {
this.ensureLoaded();
let updated: Group | null = null;
this.groups = (this.groups ?? []).map((entry) => {
if (entry.id !== id) return entry;
updated = {
...entry,
lastActivityAt: new Date(),
};
return updated;
});
persistGroups(this.groups);
return updated ? cloneGroup(updated) : null;
}
}
let singleton: SplitGroupDataSource | null = null;
export function getSplitGroupDataSource(): SplitGroupDataSource {
if (!singleton) singleton = new LocalSplitGroupDataSource();
return singleton;
}