aboutsummaryrefslogtreecommitdiffstats
path: root/src/helpers
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2026-08-03 17:37:22 -0700
committerPinapelz <yukais@pinapelz.com>2026-08-03 17:40:15 -0700
commitb37a7ffc5b19f7330050970ebac7f949a89812a2 (patch)
treea7c194528273721902874c9e71f3245934c0168d /src/helpers
parent1f52e962086856ddd6981139e2d4aca20201e9cb (diff)
feat: group streaks
- maintain completion streaks as a group - join via a provided code, streaks increment when you guess correctly
Diffstat (limited to 'src/helpers')
-rw-r--r--src/helpers/fetchSolution.ts2
-rw-r--r--src/helpers/group.ts141
2 files changed, 143 insertions, 0 deletions
diff --git a/src/helpers/fetchSolution.ts b/src/helpers/fetchSolution.ts
index b7895bb..a91932a 100644
--- a/src/helpers/fetchSolution.ts
+++ b/src/helpers/fetchSolution.ts
@@ -55,6 +55,8 @@ interface SubmitDailyGuessRequest {
state: DailyGameState;
sig: string;
guess?: SongGuessPayload | null;
+ username?: string;
+ groupId?: string;
}
interface SubmitDailyGuessResponse {
diff --git a/src/helpers/group.ts b/src/helpers/group.ts
new file mode 100644
index 0000000..2a10c36
--- /dev/null
+++ b/src/helpers/group.ts
@@ -0,0 +1,141 @@
+const API_URL = import.meta.env.VITE_HEARDLE_API_URL ?? "http://localhost:3001";
+
+export const GROUP_MEMBERSHIP_STORAGE_KEY = "groupMembership";
+
+export interface GroupMembership {
+ groupId: string;
+ groupName: string;
+ username: string;
+ joinToken?: string;
+}
+
+export interface GroupDailyStatus {
+ groupId: string;
+ groupName: string;
+ currentStreak: number;
+ finishedUsers: string[];
+}
+
+export type GroupStatusMode = "daily" | "dailyMV";
+
+export function getStoredGroupMembership(): GroupMembership | null {
+ const raw = localStorage.getItem(GROUP_MEMBERSHIP_STORAGE_KEY);
+ if (!raw) return null;
+
+ try {
+ const parsed = JSON.parse(raw) as Partial<GroupMembership>;
+ if (
+ typeof parsed.groupId !== "string" ||
+ typeof parsed.groupName !== "string" ||
+ typeof parsed.username !== "string"
+ ) {
+ localStorage.removeItem(GROUP_MEMBERSHIP_STORAGE_KEY);
+ return null;
+ }
+
+ return {
+ groupId: parsed.groupId,
+ groupName: parsed.groupName,
+ username: parsed.username,
+ joinToken: typeof parsed.joinToken === "string" ? parsed.joinToken : undefined,
+ };
+ } catch {
+ localStorage.removeItem(GROUP_MEMBERSHIP_STORAGE_KEY);
+ return null;
+ }
+}
+
+export function saveGroupMembership(membership: GroupMembership) {
+ localStorage.setItem(GROUP_MEMBERSHIP_STORAGE_KEY, JSON.stringify(membership));
+}
+
+export async function createGroup(
+ name: string,
+ username: string
+): Promise<GroupMembership> {
+ const response = await fetch(`${API_URL}/create-group`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ name, username }),
+ });
+
+ if (!response.ok) {
+ const fallback = `Failed to create group: ${response.statusText}`;
+ try {
+ const payload = (await response.json()) as { error?: unknown };
+ throw new Error(typeof payload.error === "string" ? payload.error : fallback);
+ } catch {
+ throw new Error(fallback);
+ }
+ }
+
+ const payload = (await response.json()) as {
+ group: {
+ id: string;
+ name: string;
+ joinToken: string;
+ };
+ };
+
+ return {
+ groupId: payload.group.id,
+ groupName: payload.group.name,
+ username,
+ joinToken: payload.group.joinToken,
+ };
+}
+
+export async function joinGroupByToken(
+ joinToken: string,
+ username: string
+): Promise<GroupMembership> {
+ const response = await fetch(`${API_URL}/join-group`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ joinToken, username }),
+ });
+
+ if (!response.ok) {
+ const fallback = `Failed to join group: ${response.statusText}`;
+ try {
+ const payload = (await response.json()) as { error?: unknown };
+ throw new Error(typeof payload.error === "string" ? payload.error : fallback);
+ } catch {
+ throw new Error(fallback);
+ }
+ }
+
+ const payload = (await response.json()) as {
+ groupId: string;
+ groupName: string;
+ username: string;
+ joinToken: string;
+ };
+
+ return {
+ groupId: payload.groupId,
+ groupName: payload.groupName,
+ username: payload.username,
+ joinToken: payload.joinToken,
+ };
+}
+
+export async function getGroupDailyStatus(
+ groupId: string,
+ date: string,
+ mode: GroupStatusMode = "daily"
+): Promise<GroupDailyStatus> {
+ const response = await fetch(
+ `${API_URL}/group-status?groupId=${encodeURIComponent(groupId)}&date=${encodeURIComponent(date)}&mode=${encodeURIComponent(mode)}`
+ );
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch group status: ${response.statusText}`);
+ }
+
+ return (await response.json()) as GroupDailyStatus;
+}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage