@orangecheck/vote-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/badge.d.mts +18 -0
- package/dist/badge.d.ts +18 -0
- package/dist/badge.js +153 -0
- package/dist/badge.js.map +1 -0
- package/dist/badge.mjs +151 -0
- package/dist/badge.mjs.map +1 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +370 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +363 -0
- package/dist/index.mjs.map +1 -0
- package/dist/poll.d.mts +18 -0
- package/dist/poll.d.ts +18 -0
- package/dist/poll.js +275 -0
- package/dist/poll.js.map +1 -0
- package/dist/poll.mjs +273 -0
- package/dist/poll.mjs.map +1 -0
- package/dist/types-Czxkdz7H.d.mts +32 -0
- package/dist/types-Czxkdz7H.d.ts +32 -0
- package/dist/use-tally.d.mts +16 -0
- package/dist/use-tally.d.ts +16 -0
- package/dist/use-tally.js +48 -0
- package/dist/use-tally.js.map +1 -0
- package/dist/use-tally.mjs +46 -0
- package/dist/use-tally.mjs.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
interface TallyTurnout {
|
|
2
|
+
voters: number;
|
|
3
|
+
weight: number;
|
|
4
|
+
}
|
|
5
|
+
interface TallyResponse {
|
|
6
|
+
poll_id: string;
|
|
7
|
+
state: 'tallied' | 'awaiting_reveal';
|
|
8
|
+
question?: string;
|
|
9
|
+
creator?: string;
|
|
10
|
+
deadline?: string;
|
|
11
|
+
weight_mode?: string;
|
|
12
|
+
mode?: 'public' | 'secret';
|
|
13
|
+
snapshot_block?: number;
|
|
14
|
+
turnout?: TallyTurnout;
|
|
15
|
+
tallies?: Record<string, number>;
|
|
16
|
+
ballot_count?: number;
|
|
17
|
+
reveal_published?: boolean;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
interface Theme {
|
|
21
|
+
bg: string;
|
|
22
|
+
text: string;
|
|
23
|
+
muted: string;
|
|
24
|
+
border: string;
|
|
25
|
+
accent: string;
|
|
26
|
+
accentSoft: string;
|
|
27
|
+
}
|
|
28
|
+
declare const THEMES: Record<'light' | 'dark', Theme>;
|
|
29
|
+
declare const DEFAULT_API_BASE = "https://vote.ochk.io";
|
|
30
|
+
declare const POLL_ID_RE: RegExp;
|
|
31
|
+
|
|
32
|
+
export { DEFAULT_API_BASE as D, POLL_ID_RE as P, THEMES as T, type TallyResponse as a, type TallyTurnout as b, type Theme as c };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
interface TallyTurnout {
|
|
2
|
+
voters: number;
|
|
3
|
+
weight: number;
|
|
4
|
+
}
|
|
5
|
+
interface TallyResponse {
|
|
6
|
+
poll_id: string;
|
|
7
|
+
state: 'tallied' | 'awaiting_reveal';
|
|
8
|
+
question?: string;
|
|
9
|
+
creator?: string;
|
|
10
|
+
deadline?: string;
|
|
11
|
+
weight_mode?: string;
|
|
12
|
+
mode?: 'public' | 'secret';
|
|
13
|
+
snapshot_block?: number;
|
|
14
|
+
turnout?: TallyTurnout;
|
|
15
|
+
tallies?: Record<string, number>;
|
|
16
|
+
ballot_count?: number;
|
|
17
|
+
reveal_published?: boolean;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
interface Theme {
|
|
21
|
+
bg: string;
|
|
22
|
+
text: string;
|
|
23
|
+
muted: string;
|
|
24
|
+
border: string;
|
|
25
|
+
accent: string;
|
|
26
|
+
accentSoft: string;
|
|
27
|
+
}
|
|
28
|
+
declare const THEMES: Record<'light' | 'dark', Theme>;
|
|
29
|
+
declare const DEFAULT_API_BASE = "https://vote.ochk.io";
|
|
30
|
+
declare const POLL_ID_RE: RegExp;
|
|
31
|
+
|
|
32
|
+
export { DEFAULT_API_BASE as D, POLL_ID_RE as P, THEMES as T, type TallyResponse as a, type TallyTurnout as b, type Theme as c };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { a as TallyResponse } from './types-Czxkdz7H.mjs';
|
|
2
|
+
|
|
3
|
+
interface UseTallyOptions {
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
refreshMs?: number;
|
|
6
|
+
initialData?: TallyResponse;
|
|
7
|
+
}
|
|
8
|
+
interface UseTallyState {
|
|
9
|
+
data: TallyResponse | null;
|
|
10
|
+
error: Error | null;
|
|
11
|
+
loading: boolean;
|
|
12
|
+
refetch: () => void;
|
|
13
|
+
}
|
|
14
|
+
declare function useTally(pollId: string, opts?: UseTallyOptions): UseTallyState;
|
|
15
|
+
|
|
16
|
+
export { type UseTallyOptions, type UseTallyState, useTally };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { a as TallyResponse } from './types-Czxkdz7H.js';
|
|
2
|
+
|
|
3
|
+
interface UseTallyOptions {
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
refreshMs?: number;
|
|
6
|
+
initialData?: TallyResponse;
|
|
7
|
+
}
|
|
8
|
+
interface UseTallyState {
|
|
9
|
+
data: TallyResponse | null;
|
|
10
|
+
error: Error | null;
|
|
11
|
+
loading: boolean;
|
|
12
|
+
refetch: () => void;
|
|
13
|
+
}
|
|
14
|
+
declare function useTally(pollId: string, opts?: UseTallyOptions): UseTallyState;
|
|
15
|
+
|
|
16
|
+
export { type UseTallyOptions, type UseTallyState, useTally };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/types.ts
|
|
6
|
+
var DEFAULT_API_BASE = "https://vote.ochk.io";
|
|
7
|
+
var POLL_ID_RE = /^[0-9a-f]{64}$/;
|
|
8
|
+
|
|
9
|
+
// src/use-tally.ts
|
|
10
|
+
function useTally(pollId, opts = {}) {
|
|
11
|
+
const baseUrl = opts.baseUrl ?? DEFAULT_API_BASE;
|
|
12
|
+
const refreshMs = opts.refreshMs ?? 6e4;
|
|
13
|
+
const [data, setData] = react.useState(opts.initialData ?? null);
|
|
14
|
+
const [error, setError] = react.useState(null);
|
|
15
|
+
const [loading, setLoading] = react.useState(!opts.initialData);
|
|
16
|
+
const pollIdRef = react.useRef(pollId);
|
|
17
|
+
pollIdRef.current = pollId;
|
|
18
|
+
const fetchOnce = react.useCallback(async () => {
|
|
19
|
+
const pid = pollIdRef.current;
|
|
20
|
+
if (!POLL_ID_RE.test(pid)) {
|
|
21
|
+
setError(new Error("pollId must be 64 hex chars"));
|
|
22
|
+
setLoading(false);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(`${baseUrl}/api/tally?poll=${pid}`);
|
|
27
|
+
const body = await res.json();
|
|
28
|
+
if (!res.ok) throw new Error(body.error ?? `http ${res.status}`);
|
|
29
|
+
setData(body);
|
|
30
|
+
setError(null);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
33
|
+
} finally {
|
|
34
|
+
setLoading(false);
|
|
35
|
+
}
|
|
36
|
+
}, [baseUrl]);
|
|
37
|
+
react.useEffect(() => {
|
|
38
|
+
void fetchOnce();
|
|
39
|
+
if (refreshMs <= 0) return;
|
|
40
|
+
const timer = setInterval(() => void fetchOnce(), refreshMs);
|
|
41
|
+
return () => clearInterval(timer);
|
|
42
|
+
}, [fetchOnce, refreshMs]);
|
|
43
|
+
return { data, error, loading, refetch: fetchOnce };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
exports.useTally = useTally;
|
|
47
|
+
//# sourceMappingURL=use-tally.js.map
|
|
48
|
+
//# sourceMappingURL=use-tally.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/use-tally.ts"],"names":["useState","useRef","useCallback","useEffect"],"mappings":";;;;;AAqDO,IAAM,gBAAA,GAAmB,sBAAA;AAEzB,IAAM,UAAA,GAAa,gBAAA;;;ACtBnB,SAAS,QAAA,CACZ,MAAA,EACA,IAAA,GAAwB,EAAC,EACZ;AACb,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,IAAW,gBAAA;AAChC,EAAA,MAAM,SAAA,GAAY,KAAK,SAAA,IAAa,GAAA;AAEpC,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,IAAIA,cAAA,CAA+B,IAAA,CAAK,eAAe,IAAI,CAAA;AAC/E,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,IAAIA,cAAA,CAAS,CAAC,KAAK,WAAW,CAAA;AACxD,EAAA,MAAM,SAAA,GAAYC,aAAO,MAAM,CAAA;AAC/B,EAAA,SAAA,CAAU,OAAA,GAAU,MAAA;AAEpB,EAAA,MAAM,SAAA,GAAYC,kBAAY,YAAY;AACtC,IAAA,MAAM,MAAM,SAAA,CAAU,OAAA;AACtB,IAAA,IAAI,CAAC,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,EAAG;AACvB,MAAA,QAAA,CAAS,IAAI,KAAA,CAAM,6BAA6B,CAAC,CAAA;AACjD,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACJ;AACA,IAAA,IAAI;AACA,MAAA,MAAM,MAAM,MAAM,KAAA,CAAM,GAAG,OAAO,CAAA,gBAAA,EAAmB,GAAG,CAAA,CAAE,CAAA;AAC1D,MAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,MAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,IAAA,CAAK,KAAA,IAAS,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAC/D,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACjB,SAAS,GAAA,EAAK;AACV,MAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAAA,IAChE,CAAA,SAAE;AACE,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA,IACpB;AAAA,EACJ,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAAC,eAAA,CAAU,MAAM;AACZ,IAAA,KAAK,SAAA,EAAU;AACf,IAAA,IAAI,aAAa,CAAA,EAAG;AACpB,IAAA,MAAM,QAAQ,WAAA,CAAY,MAAM,KAAK,SAAA,IAAa,SAAS,CAAA;AAC3D,IAAA,OAAO,MAAM,cAAc,KAAK,CAAA;AAAA,EACpC,CAAA,EAAG,CAAC,SAAA,EAAW,SAAS,CAAC,CAAA;AAEzB,EAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,OAAA,EAAS,SAAS,SAAA,EAAU;AACtD","file":"use-tally.js","sourcesContent":["// Wire types for the /api/tally response.\n// Deliberately a subset of what vote.ochk.io returns — we don't re-export\n// every field, just what the components render.\n\nexport interface TallyTurnout {\n voters: number;\n weight: number;\n}\n\nexport interface TallyResponse {\n poll_id: string;\n state: 'tallied' | 'awaiting_reveal';\n question?: string;\n creator?: string;\n deadline?: string;\n weight_mode?: string;\n mode?: 'public' | 'secret';\n snapshot_block?: number;\n turnout?: TallyTurnout;\n tallies?: Record<string, number>;\n ballot_count?: number;\n reveal_published?: boolean;\n error?: string;\n}\n\nexport interface Theme {\n bg: string;\n text: string;\n muted: string;\n border: string;\n accent: string;\n accentSoft: string;\n}\n\nexport const THEMES: Record<'light' | 'dark', Theme> = {\n light: {\n bg: '#ffffff',\n text: '#0a0a0a',\n muted: '#666666',\n border: '#e5e5e5',\n accent: '#f97316',\n accentSoft: '#fff4ec',\n },\n dark: {\n bg: '#0a0a0a',\n text: '#fafafa',\n muted: '#a0a0a0',\n border: '#262626',\n accent: '#fb923c',\n accentSoft: '#2a1a0a',\n },\n};\n\nexport const DEFAULT_API_BASE = 'https://vote.ochk.io';\n\nexport const POLL_ID_RE = /^[0-9a-f]{64}$/;\n","'use client';\n\n// useTally(pollId) — React hook that fetches + polls /api/tally.\n//\n// Defaults: fetch once on mount; refetch every 60s (matches the /api/tally\n// edge cache TTL so we never get stale data from our own server-side cache\n// but don't thrash the relay set either). Pass { refreshMs: 0 } for no\n// auto-refresh, or a custom interval.\n//\n// Consumers that want independent verification should NOT use this hook —\n// use @orangecheck/vote-core + @orangecheck/vote-cli with their own\n// node/bitcoin source. This hook trusts vote.ochk.io (or whatever baseUrl\n// you pass).\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { DEFAULT_API_BASE, POLL_ID_RE } from './types.js';\nimport type { TallyResponse } from './types.js';\n\nexport interface UseTallyOptions {\n baseUrl?: string;\n refreshMs?: number;\n /** If set, provides initial data (SSR hydration). */\n initialData?: TallyResponse;\n}\n\nexport interface UseTallyState {\n data: TallyResponse | null;\n error: Error | null;\n loading: boolean;\n refetch: () => void;\n}\n\nexport function useTally(\n pollId: string,\n opts: UseTallyOptions = {}\n): UseTallyState {\n const baseUrl = opts.baseUrl ?? DEFAULT_API_BASE;\n const refreshMs = opts.refreshMs ?? 60_000;\n\n const [data, setData] = useState<TallyResponse | null>(opts.initialData ?? null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(!opts.initialData);\n const pollIdRef = useRef(pollId);\n pollIdRef.current = pollId;\n\n const fetchOnce = useCallback(async () => {\n const pid = pollIdRef.current;\n if (!POLL_ID_RE.test(pid)) {\n setError(new Error('pollId must be 64 hex chars'));\n setLoading(false);\n return;\n }\n try {\n const res = await fetch(`${baseUrl}/api/tally?poll=${pid}`);\n const body = (await res.json()) as TallyResponse;\n if (!res.ok) throw new Error(body.error ?? `http ${res.status}`);\n setData(body);\n setError(null);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setLoading(false);\n }\n }, [baseUrl]);\n\n useEffect(() => {\n void fetchOnce();\n if (refreshMs <= 0) return;\n const timer = setInterval(() => void fetchOnce(), refreshMs);\n return () => clearInterval(timer);\n }, [fetchOnce, refreshMs]);\n\n return { data, error, loading, refetch: fetchOnce };\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/types.ts
|
|
4
|
+
var DEFAULT_API_BASE = "https://vote.ochk.io";
|
|
5
|
+
var POLL_ID_RE = /^[0-9a-f]{64}$/;
|
|
6
|
+
|
|
7
|
+
// src/use-tally.ts
|
|
8
|
+
function useTally(pollId, opts = {}) {
|
|
9
|
+
const baseUrl = opts.baseUrl ?? DEFAULT_API_BASE;
|
|
10
|
+
const refreshMs = opts.refreshMs ?? 6e4;
|
|
11
|
+
const [data, setData] = useState(opts.initialData ?? null);
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
const [loading, setLoading] = useState(!opts.initialData);
|
|
14
|
+
const pollIdRef = useRef(pollId);
|
|
15
|
+
pollIdRef.current = pollId;
|
|
16
|
+
const fetchOnce = useCallback(async () => {
|
|
17
|
+
const pid = pollIdRef.current;
|
|
18
|
+
if (!POLL_ID_RE.test(pid)) {
|
|
19
|
+
setError(new Error("pollId must be 64 hex chars"));
|
|
20
|
+
setLoading(false);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch(`${baseUrl}/api/tally?poll=${pid}`);
|
|
25
|
+
const body = await res.json();
|
|
26
|
+
if (!res.ok) throw new Error(body.error ?? `http ${res.status}`);
|
|
27
|
+
setData(body);
|
|
28
|
+
setError(null);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
31
|
+
} finally {
|
|
32
|
+
setLoading(false);
|
|
33
|
+
}
|
|
34
|
+
}, [baseUrl]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
void fetchOnce();
|
|
37
|
+
if (refreshMs <= 0) return;
|
|
38
|
+
const timer = setInterval(() => void fetchOnce(), refreshMs);
|
|
39
|
+
return () => clearInterval(timer);
|
|
40
|
+
}, [fetchOnce, refreshMs]);
|
|
41
|
+
return { data, error, loading, refetch: fetchOnce };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { useTally };
|
|
45
|
+
//# sourceMappingURL=use-tally.mjs.map
|
|
46
|
+
//# sourceMappingURL=use-tally.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/use-tally.ts"],"names":[],"mappings":";;;AAqDO,IAAM,gBAAA,GAAmB,sBAAA;AAEzB,IAAM,UAAA,GAAa,gBAAA;;;ACtBnB,SAAS,QAAA,CACZ,MAAA,EACA,IAAA,GAAwB,EAAC,EACZ;AACb,EAAA,MAAM,OAAA,GAAU,KAAK,OAAA,IAAW,gBAAA;AAChC,EAAA,MAAM,SAAA,GAAY,KAAK,SAAA,IAAa,GAAA;AAEpC,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,IAAI,QAAA,CAA+B,IAAA,CAAK,eAAe,IAAI,CAAA;AAC/E,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,IAAI,QAAA,CAAS,CAAC,KAAK,WAAW,CAAA;AACxD,EAAA,MAAM,SAAA,GAAY,OAAO,MAAM,CAAA;AAC/B,EAAA,SAAA,CAAU,OAAA,GAAU,MAAA;AAEpB,EAAA,MAAM,SAAA,GAAY,YAAY,YAAY;AACtC,IAAA,MAAM,MAAM,SAAA,CAAU,OAAA;AACtB,IAAA,IAAI,CAAC,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,EAAG;AACvB,MAAA,QAAA,CAAS,IAAI,KAAA,CAAM,6BAA6B,CAAC,CAAA;AACjD,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACJ;AACA,IAAA,IAAI;AACA,MAAA,MAAM,MAAM,MAAM,KAAA,CAAM,GAAG,OAAO,CAAA,gBAAA,EAAmB,GAAG,CAAA,CAAE,CAAA;AAC1D,MAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,MAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,IAAA,CAAK,KAAA,IAAS,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAC/D,MAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACjB,SAAS,GAAA,EAAK;AACV,MAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAAA,IAChE,CAAA,SAAE;AACE,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA,IACpB;AAAA,EACJ,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,SAAA,CAAU,MAAM;AACZ,IAAA,KAAK,SAAA,EAAU;AACf,IAAA,IAAI,aAAa,CAAA,EAAG;AACpB,IAAA,MAAM,QAAQ,WAAA,CAAY,MAAM,KAAK,SAAA,IAAa,SAAS,CAAA;AAC3D,IAAA,OAAO,MAAM,cAAc,KAAK,CAAA;AAAA,EACpC,CAAA,EAAG,CAAC,SAAA,EAAW,SAAS,CAAC,CAAA;AAEzB,EAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,OAAA,EAAS,SAAS,SAAA,EAAU;AACtD","file":"use-tally.mjs","sourcesContent":["// Wire types for the /api/tally response.\n// Deliberately a subset of what vote.ochk.io returns — we don't re-export\n// every field, just what the components render.\n\nexport interface TallyTurnout {\n voters: number;\n weight: number;\n}\n\nexport interface TallyResponse {\n poll_id: string;\n state: 'tallied' | 'awaiting_reveal';\n question?: string;\n creator?: string;\n deadline?: string;\n weight_mode?: string;\n mode?: 'public' | 'secret';\n snapshot_block?: number;\n turnout?: TallyTurnout;\n tallies?: Record<string, number>;\n ballot_count?: number;\n reveal_published?: boolean;\n error?: string;\n}\n\nexport interface Theme {\n bg: string;\n text: string;\n muted: string;\n border: string;\n accent: string;\n accentSoft: string;\n}\n\nexport const THEMES: Record<'light' | 'dark', Theme> = {\n light: {\n bg: '#ffffff',\n text: '#0a0a0a',\n muted: '#666666',\n border: '#e5e5e5',\n accent: '#f97316',\n accentSoft: '#fff4ec',\n },\n dark: {\n bg: '#0a0a0a',\n text: '#fafafa',\n muted: '#a0a0a0',\n border: '#262626',\n accent: '#fb923c',\n accentSoft: '#2a1a0a',\n },\n};\n\nexport const DEFAULT_API_BASE = 'https://vote.ochk.io';\n\nexport const POLL_ID_RE = /^[0-9a-f]{64}$/;\n","'use client';\n\n// useTally(pollId) — React hook that fetches + polls /api/tally.\n//\n// Defaults: fetch once on mount; refetch every 60s (matches the /api/tally\n// edge cache TTL so we never get stale data from our own server-side cache\n// but don't thrash the relay set either). Pass { refreshMs: 0 } for no\n// auto-refresh, or a custom interval.\n//\n// Consumers that want independent verification should NOT use this hook —\n// use @orangecheck/vote-core + @orangecheck/vote-cli with their own\n// node/bitcoin source. This hook trusts vote.ochk.io (or whatever baseUrl\n// you pass).\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { DEFAULT_API_BASE, POLL_ID_RE } from './types.js';\nimport type { TallyResponse } from './types.js';\n\nexport interface UseTallyOptions {\n baseUrl?: string;\n refreshMs?: number;\n /** If set, provides initial data (SSR hydration). */\n initialData?: TallyResponse;\n}\n\nexport interface UseTallyState {\n data: TallyResponse | null;\n error: Error | null;\n loading: boolean;\n refetch: () => void;\n}\n\nexport function useTally(\n pollId: string,\n opts: UseTallyOptions = {}\n): UseTallyState {\n const baseUrl = opts.baseUrl ?? DEFAULT_API_BASE;\n const refreshMs = opts.refreshMs ?? 60_000;\n\n const [data, setData] = useState<TallyResponse | null>(opts.initialData ?? null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(!opts.initialData);\n const pollIdRef = useRef(pollId);\n pollIdRef.current = pollId;\n\n const fetchOnce = useCallback(async () => {\n const pid = pollIdRef.current;\n if (!POLL_ID_RE.test(pid)) {\n setError(new Error('pollId must be 64 hex chars'));\n setLoading(false);\n return;\n }\n try {\n const res = await fetch(`${baseUrl}/api/tally?poll=${pid}`);\n const body = (await res.json()) as TallyResponse;\n if (!res.ok) throw new Error(body.error ?? `http ${res.status}`);\n setData(body);\n setError(null);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setLoading(false);\n }\n }, [baseUrl]);\n\n useEffect(() => {\n void fetchOnce();\n if (refreshMs <= 0) return;\n const timer = setInterval(() => void fetchOnce(), refreshMs);\n return () => clearInterval(timer);\n }, [fetchOnce, refreshMs]);\n\n return { data, error, loading, refetch: fetchOnce };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orangecheck/vote-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React components for OC Vote — render live tallies as badges or full poll cards, drop into any browser app in one line.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"oc-vote",
|
|
7
|
+
"orangecheck",
|
|
8
|
+
"bitcoin",
|
|
9
|
+
"voting",
|
|
10
|
+
"react",
|
|
11
|
+
"components",
|
|
12
|
+
"bip322"
|
|
13
|
+
],
|
|
14
|
+
"author": "OrangeCheck",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/orangecheck/oc-packages.git",
|
|
19
|
+
"directory": "vote-react"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://vote.ochk.io",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/orangecheck/oc-vote-protocol/issues"
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"module": "./dist/index.mjs",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.mjs",
|
|
32
|
+
"require": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./badge": {
|
|
35
|
+
"types": "./dist/badge.d.ts",
|
|
36
|
+
"import": "./dist/badge.mjs",
|
|
37
|
+
"require": "./dist/badge.js"
|
|
38
|
+
},
|
|
39
|
+
"./poll": {
|
|
40
|
+
"types": "./dist/poll.d.ts",
|
|
41
|
+
"import": "./dist/poll.mjs",
|
|
42
|
+
"require": "./dist/poll.js"
|
|
43
|
+
},
|
|
44
|
+
"./use-tally": {
|
|
45
|
+
"types": "./dist/use-tally.d.ts",
|
|
46
|
+
"import": "./dist/use-tally.mjs",
|
|
47
|
+
"require": "./dist/use-tally.js"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
],
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"type-check": "tsc --noEmit",
|
|
59
|
+
"clean": "rm -rf dist",
|
|
60
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"react": ">=18"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/node": "^22.10.2",
|
|
67
|
+
"@types/react": "^19.0.1",
|
|
68
|
+
"react": "^19.1.0",
|
|
69
|
+
"tsup": "^8.3.5",
|
|
70
|
+
"typescript": "^5.7.2"
|
|
71
|
+
}
|
|
72
|
+
}
|