o2o-triple-pick/lib/myPredictions.ts

86 lines
3.8 KiB
TypeScript

// 내 지난 예측 — 로그인 없이 이메일 기준 조회/저장
// ─────────────────────────────────────────────────────────────
// UI(Arena.tsx)는 아래 4개 함수의 "시그니처(입출력 타입)"에만 의존한다.
// 백엔드 개발자는 fetchMyPredictions / saveMyPrediction 의 *본문만*
// Firebase 콜러블 함수(getMyPredictions / submitPrediction) 호출로 교체하면
// UI 코드는 수정할 필요가 없다. (rememberEmail/recallEmail 은 그대로 클라 보관)
//
// 현재 구현: 데모용 localStorage (기기 1대 기준). 운영에서는 Firestore
// user_predictions 를 이메일로 조회하는 콜러블로 대체 → 기기 간 동기화.
import type { Outcome } from "./types";
/** 이메일 기준으로 조회되는 1건의 사용자 예측 (표시·정렬용 필드 포함) */
export type MyPrediction = {
matchId: string;
teamAShort: string; // "멕시코"
teamBShort: string; // "한국"
kickoffKst: string; // ISO — 정렬/표시용
outcome: Outcome;
scoreA: number;
scoreB: number;
submittedAt: string; // ISO — 제출 시각(최신순 정렬 기준)
/** 경기 종료 시 백엔드가 채움. 미종료면 undefined */
result?: { outcome: Outcome; scoreA: number; scoreB: number; hitOutcome: boolean };
};
const STORE_KEY = "tp_my_predictions_v1"; // { [emailLower]: MyPrediction[] }
const EMAIL_KEY = "tp_email_v1";
const normEmail = (email: string) => email.trim().toLowerCase();
function readStore(): Record<string, MyPrediction[]> {
if (typeof window === "undefined") return {};
try {
return JSON.parse(window.localStorage.getItem(STORE_KEY) || "{}");
} catch {
return {};
}
}
function writeStore(store: Record<string, MyPrediction[]>) {
if (typeof window === "undefined") return;
window.localStorage.setItem(STORE_KEY, JSON.stringify(store));
}
/* ============================================================
* 백엔드 연동 지점 ① — 내 예측 목록 조회
* 개발자: 본문을 getMyPredictions 콜러블 호출로 교체.
* const { fns } = getFirebase();
* const res = await httpsCallable(fns, "getMyPredictions")({ email });
* return res.data as MyPrediction[]; // 최신순 정렬 보장
* ============================================================ */
export async function fetchMyPredictions(email: string): Promise<MyPrediction[]> {
if (!email) return [];
const list = readStore()[normEmail(email)] ?? [];
// 최신 제출순(내림차순)
return [...list].sort((a, b) => b.submittedAt.localeCompare(a.submittedAt));
}
/* ============================================================
* 백엔드 연동 지점 ② — 내 예측 제출/갱신
* 개발자: 본문을 submitPrediction 콜러블 호출로 교체(서버에서 crowd_stats
* 원자적 증분 + PII 보호). 경기당 1건(matchId 기준 upsert).
* ============================================================ */
export async function saveMyPrediction(email: string, rec: MyPrediction): Promise<void> {
if (!email) return;
const key = normEmail(email);
const store = readStore();
const list = store[key] ?? [];
const next = list.filter((p) => p.matchId !== rec.matchId); // 같은 경기는 최신 픽으로 교체
next.push(rec);
store[key] = next;
writeStore(store);
}
/** 마지막 사용 이메일 기억 (재방문 시 자동 복원 — 로그인 대체) */
export function rememberEmail(email: string) {
if (typeof window === "undefined" || !email) return;
window.localStorage.setItem(EMAIL_KEY, normEmail(email));
}
/** 기억된 이메일 불러오기 (없으면 "") */
export function recallEmail(): string {
if (typeof window === "undefined") return "";
return window.localStorage.getItem(EMAIL_KEY) || "";
}