86 lines
3.8 KiB
TypeScript
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) || "";
|
|
}
|