@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 OrangeCheck
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# @orangecheck/vote-react
|
|
2
|
+
|
|
3
|
+
Drop-in React components for [OC Vote](https://github.com/orangecheck/oc-vote-protocol). Embed a live tally anywhere in three lines.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm i @orangecheck/vote-react react
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Components
|
|
10
|
+
|
|
11
|
+
### `<OcPoll pollId="…" />`
|
|
12
|
+
|
|
13
|
+
A full poll card — question, option bars, turnout, status pill, "cast a ballot" CTA that opens `vote.ochk.io/p/<id>` in a new tab. Live-refreshes every 60s by default.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { OcPoll } from '@orangecheck/vote-react';
|
|
17
|
+
|
|
18
|
+
<OcPoll pollId="3054390f047f2703186943a41178bc15931500b5139229517f26e56282026ee5" />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### `<OcTallyBadge pollId="…" />`
|
|
22
|
+
|
|
23
|
+
A compact inline pill showing the top option + its percentage. Use in sidebars, profile cards, feed rows.
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { OcTallyBadge } from '@orangecheck/vote-react';
|
|
27
|
+
|
|
28
|
+
<OcTallyBadge pollId="3054390f…" theme="dark" />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### `useTally(pollId, opts?)`
|
|
32
|
+
|
|
33
|
+
The underlying hook, for custom rendering.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { useTally } from '@orangecheck/vote-react';
|
|
37
|
+
|
|
38
|
+
function MyPoll({ id }: { id: string }) {
|
|
39
|
+
const { data, error, loading, refetch } = useTally(id, { refreshMs: 30_000 });
|
|
40
|
+
if (loading) return <Spinner />;
|
|
41
|
+
if (error) return <Error msg={error.message} />;
|
|
42
|
+
if (data?.state === 'awaiting_reveal') return <Pending />;
|
|
43
|
+
return <TallyView tallies={data!.tallies!} />;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Props (shared)
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Default | Meaning |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| `pollId` | `string` | — | 64-hex poll id |
|
|
52
|
+
| `theme` | `'light' \| 'dark'` | `'light'` | Colour theme |
|
|
53
|
+
| `baseUrl` | `string` | `https://vote.ochk.io` | Override for self-hosted `/api/tally` |
|
|
54
|
+
| `refreshMs` | `number` | `60000` | Auto-refresh interval; `0` to disable |
|
|
55
|
+
| `initialData` | `TallyResponse` | — | SSR hydration payload (skip initial fetch) |
|
|
56
|
+
| `className` | `string` | — | Outer element class |
|
|
57
|
+
| `style` | `CSSProperties` | — | Inline style override |
|
|
58
|
+
|
|
59
|
+
`<OcPoll>` additionally accepts `hideCta` (boolean, default false) to render as read-only without the "cast a ballot" link.
|
|
60
|
+
|
|
61
|
+
## Why it's read-only
|
|
62
|
+
|
|
63
|
+
Voting requires a BIP-322 signature from the user's Bitcoin wallet. That's handled by `vote.ochk.io/p/<id>` (or your own fork of `oc-vote-web`) which knows how to talk to UniSat, Xverse, Leather, OKX, Phantom, etc. The CTA in `<OcPoll>` links there.
|
|
64
|
+
|
|
65
|
+
For **headless** poll creation / voting / revealing, use [`@orangecheck/vote-cli`](https://npmjs.com/package/@orangecheck/vote-cli) instead.
|
|
66
|
+
|
|
67
|
+
## Styling
|
|
68
|
+
|
|
69
|
+
Zero CSS dependencies — every component uses inline styles. The built-in themes match the OC family's palette (near-black + bitcoin-orange in dark; near-white + orange in light). Override with `className` + your own CSS, or pass `style` for per-instance tweaks.
|
|
70
|
+
|
|
71
|
+
## SSR
|
|
72
|
+
|
|
73
|
+
Works under Next.js App Router (both packages are `'use client'`-marked) and Pages Router. If you want to skip the initial fetch for faster first paint, pre-fetch on the server and pass `initialData`:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
// Next.js server component / getServerSideProps
|
|
77
|
+
const res = await fetch(`https://vote.ochk.io/api/tally?poll=${id}`);
|
|
78
|
+
const initialData = await res.json();
|
|
79
|
+
|
|
80
|
+
// render:
|
|
81
|
+
<OcPoll pollId={id} initialData={initialData} />
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Verification
|
|
85
|
+
|
|
86
|
+
This package trusts the `baseUrl` endpoint. For high-value decisions, pair it with an independent run of `@orangecheck/vote-cli tally <id>` — the CLI re-verifies every BIP-322 signature and re-runs the pure tally function. If the two disagree, the CLI is authoritative.
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT. See [LICENSE](./LICENSE).
|
|
91
|
+
|
|
92
|
+
## Related
|
|
93
|
+
|
|
94
|
+
- Protocol: [`orangecheck/oc-vote-protocol`](https://github.com/orangecheck/oc-vote-protocol)
|
|
95
|
+
- Web client: [`vote.ochk.io`](https://vote.ochk.io)
|
|
96
|
+
- Core library: [`@orangecheck/vote-core`](https://npmjs.com/package/@orangecheck/vote-core)
|
|
97
|
+
- CLI: [`@orangecheck/vote-cli`](https://npmjs.com/package/@orangecheck/vote-cli)
|
|
98
|
+
- Examples: [`orangecheck/oc-vote-examples`](https://github.com/orangecheck/oc-vote-examples)
|
package/dist/badge.d.mts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import { a as TallyResponse } from './types-Czxkdz7H.mjs';
|
|
5
|
+
|
|
6
|
+
interface OcTallyBadgeProps {
|
|
7
|
+
pollId: string;
|
|
8
|
+
theme?: 'light' | 'dark';
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
refreshMs?: number;
|
|
11
|
+
initialData?: TallyResponse;
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: CSSProperties;
|
|
14
|
+
target?: '_blank' | '_self';
|
|
15
|
+
}
|
|
16
|
+
declare const OcTallyBadge: react.MemoExoticComponent<({ pollId, theme, baseUrl, refreshMs, initialData, className, style, target, }: OcTallyBadgeProps) => react_jsx_runtime.JSX.Element>;
|
|
17
|
+
|
|
18
|
+
export { OcTallyBadge, type OcTallyBadgeProps };
|
package/dist/badge.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import { a as TallyResponse } from './types-Czxkdz7H.js';
|
|
5
|
+
|
|
6
|
+
interface OcTallyBadgeProps {
|
|
7
|
+
pollId: string;
|
|
8
|
+
theme?: 'light' | 'dark';
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
refreshMs?: number;
|
|
11
|
+
initialData?: TallyResponse;
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: CSSProperties;
|
|
14
|
+
target?: '_blank' | '_self';
|
|
15
|
+
}
|
|
16
|
+
declare const OcTallyBadge: react.MemoExoticComponent<({ pollId, theme, baseUrl, refreshMs, initialData, className, style, target, }: OcTallyBadgeProps) => react_jsx_runtime.JSX.Element>;
|
|
17
|
+
|
|
18
|
+
export { OcTallyBadge, type OcTallyBadgeProps };
|
package/dist/badge.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/types.ts
|
|
7
|
+
var THEMES = {
|
|
8
|
+
light: {
|
|
9
|
+
bg: "#ffffff",
|
|
10
|
+
text: "#0a0a0a",
|
|
11
|
+
muted: "#666666",
|
|
12
|
+
border: "#e5e5e5",
|
|
13
|
+
accent: "#f97316",
|
|
14
|
+
accentSoft: "#fff4ec"
|
|
15
|
+
},
|
|
16
|
+
dark: {
|
|
17
|
+
bg: "#0a0a0a",
|
|
18
|
+
text: "#fafafa",
|
|
19
|
+
muted: "#a0a0a0",
|
|
20
|
+
border: "#262626",
|
|
21
|
+
accent: "#fb923c",
|
|
22
|
+
accentSoft: "#2a1a0a"
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var DEFAULT_API_BASE = "https://vote.ochk.io";
|
|
26
|
+
var POLL_ID_RE = /^[0-9a-f]{64}$/;
|
|
27
|
+
function useTally(pollId, opts = {}) {
|
|
28
|
+
const baseUrl = opts.baseUrl ?? DEFAULT_API_BASE;
|
|
29
|
+
const refreshMs = opts.refreshMs ?? 6e4;
|
|
30
|
+
const [data, setData] = react.useState(opts.initialData ?? null);
|
|
31
|
+
const [error, setError] = react.useState(null);
|
|
32
|
+
const [loading, setLoading] = react.useState(!opts.initialData);
|
|
33
|
+
const pollIdRef = react.useRef(pollId);
|
|
34
|
+
pollIdRef.current = pollId;
|
|
35
|
+
const fetchOnce = react.useCallback(async () => {
|
|
36
|
+
const pid = pollIdRef.current;
|
|
37
|
+
if (!POLL_ID_RE.test(pid)) {
|
|
38
|
+
setError(new Error("pollId must be 64 hex chars"));
|
|
39
|
+
setLoading(false);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch(`${baseUrl}/api/tally?poll=${pid}`);
|
|
44
|
+
const body = await res.json();
|
|
45
|
+
if (!res.ok) throw new Error(body.error ?? `http ${res.status}`);
|
|
46
|
+
setData(body);
|
|
47
|
+
setError(null);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
50
|
+
} finally {
|
|
51
|
+
setLoading(false);
|
|
52
|
+
}
|
|
53
|
+
}, [baseUrl]);
|
|
54
|
+
react.useEffect(() => {
|
|
55
|
+
void fetchOnce();
|
|
56
|
+
if (refreshMs <= 0) return;
|
|
57
|
+
const timer = setInterval(() => void fetchOnce(), refreshMs);
|
|
58
|
+
return () => clearInterval(timer);
|
|
59
|
+
}, [fetchOnce, refreshMs]);
|
|
60
|
+
return { data, error, loading, refetch: fetchOnce };
|
|
61
|
+
}
|
|
62
|
+
var OcTallyBadgeImpl = ({
|
|
63
|
+
pollId,
|
|
64
|
+
theme = "light",
|
|
65
|
+
baseUrl,
|
|
66
|
+
refreshMs,
|
|
67
|
+
initialData,
|
|
68
|
+
className,
|
|
69
|
+
style,
|
|
70
|
+
target = "_blank"
|
|
71
|
+
}) => {
|
|
72
|
+
const { data, error, loading } = useTally(pollId, {
|
|
73
|
+
...baseUrl !== void 0 ? { baseUrl } : {},
|
|
74
|
+
...refreshMs !== void 0 ? { refreshMs } : {},
|
|
75
|
+
...initialData !== void 0 ? { initialData } : {}
|
|
76
|
+
});
|
|
77
|
+
const t = THEMES[theme];
|
|
78
|
+
const { dotColor, label } = react.useMemo(() => renderState(data, error, loading, t), [data, error, loading, t]);
|
|
79
|
+
const href = `${baseUrl ?? "https://vote.ochk.io"}/p/${pollId}`;
|
|
80
|
+
const outerStyle = {
|
|
81
|
+
display: "inline-flex",
|
|
82
|
+
alignItems: "center",
|
|
83
|
+
gap: 8,
|
|
84
|
+
padding: "4px 10px",
|
|
85
|
+
border: `1px solid ${t.border}`,
|
|
86
|
+
borderRadius: 6,
|
|
87
|
+
background: t.bg,
|
|
88
|
+
color: t.text,
|
|
89
|
+
fontFamily: "ui-monospace, Menlo, monospace",
|
|
90
|
+
fontSize: 12,
|
|
91
|
+
textDecoration: "none",
|
|
92
|
+
lineHeight: 1.4,
|
|
93
|
+
...style
|
|
94
|
+
};
|
|
95
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
96
|
+
"a",
|
|
97
|
+
{
|
|
98
|
+
href,
|
|
99
|
+
target,
|
|
100
|
+
rel: target === "_blank" ? "noopener noreferrer" : void 0,
|
|
101
|
+
className,
|
|
102
|
+
style: outerStyle,
|
|
103
|
+
"data-slot": "oc-tally-badge",
|
|
104
|
+
children: [
|
|
105
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
106
|
+
"span",
|
|
107
|
+
{
|
|
108
|
+
"aria-hidden": true,
|
|
109
|
+
style: {
|
|
110
|
+
width: 8,
|
|
111
|
+
height: 8,
|
|
112
|
+
borderRadius: "50%",
|
|
113
|
+
background: dotColor,
|
|
114
|
+
flexShrink: 0
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600 }, children: "oc vote" }),
|
|
119
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: t.muted }, children: "\xB7" }),
|
|
120
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
function renderState(data, error, loading, t) {
|
|
126
|
+
if (error) return { dotColor: "#ef4444", label: error.message };
|
|
127
|
+
if (loading && !data) return { dotColor: t.muted, label: "loading\u2026" };
|
|
128
|
+
if (!data) return { dotColor: t.muted, label: "no data" };
|
|
129
|
+
if (data.state === "awaiting_reveal")
|
|
130
|
+
return { dotColor: t.accent, label: `awaiting reveal \xB7 ${data.ballot_count ?? 0} ballots` };
|
|
131
|
+
if (data.state !== "tallied") return { dotColor: t.muted, label: String(data.state) };
|
|
132
|
+
const total = data.turnout?.weight ?? 0;
|
|
133
|
+
const tallies = data.tallies ?? {};
|
|
134
|
+
const sorted = Object.entries(tallies).sort((a, b) => b[1] - a[1]);
|
|
135
|
+
if (sorted.length === 0 || total === 0) return { dotColor: t.muted, label: "no ballots yet" };
|
|
136
|
+
const [topId, topW] = sorted[0];
|
|
137
|
+
const pct = topW / total * 100;
|
|
138
|
+
return {
|
|
139
|
+
dotColor: "#10b981",
|
|
140
|
+
label: `${topId} \xB7 ${pct.toFixed(0)}% of ${fmt(total)}`
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function fmt(n) {
|
|
144
|
+
if (n >= 1e9) return (n / 1e9).toFixed(1) + "B";
|
|
145
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
|
|
146
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
|
|
147
|
+
return String(n);
|
|
148
|
+
}
|
|
149
|
+
var OcTallyBadge = react.memo(OcTallyBadgeImpl);
|
|
150
|
+
|
|
151
|
+
exports.OcTallyBadge = OcTallyBadge;
|
|
152
|
+
//# sourceMappingURL=badge.js.map
|
|
153
|
+
//# sourceMappingURL=badge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/use-tally.ts","../src/badge.tsx"],"names":["useState","useRef","useCallback","useEffect","useMemo","jsxs","jsx","memo"],"mappings":"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","file":"badge.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","'use client';\n\n/**\n * <OcTallyBadge pollId=\"…\" />\n *\n * Compact inline pill. Shows:\n * - a colored dot (green=tallied, orange=awaiting_reveal, gray=loading, red=error)\n * - the top option + its percentage (tallied), or the state label (otherwise)\n * - a subtle link to vote.ochk.io/p/<id>\n *\n * Use on a feed row / profile card / sidebar. For the full poll card, use <OcPoll />.\n */\n\nimport type { CSSProperties } from 'react';\nimport { memo, useMemo } from 'react';\n\nimport { THEMES, type Theme, type TallyResponse } from './types.js';\nimport { useTally } from './use-tally.js';\n\nexport interface OcTallyBadgeProps {\n /** 64-hex poll id. */\n pollId: string;\n /** Theme. Default `light`. */\n theme?: 'light' | 'dark';\n /** Override /api/tally origin. Default `https://vote.ochk.io`. */\n baseUrl?: string;\n /** Auto-refresh interval in ms. Default 60000. Set to 0 to disable. */\n refreshMs?: number;\n /** Supply SSR-hydrated data to skip the initial fetch. */\n initialData?: TallyResponse;\n /** Extra class on the outer element. */\n className?: string;\n /** Inline style override. */\n style?: CSSProperties;\n /** Target for the link. Default `_blank`. */\n target?: '_blank' | '_self';\n}\n\nconst OcTallyBadgeImpl = ({\n pollId,\n theme = 'light',\n baseUrl,\n refreshMs,\n initialData,\n className,\n style,\n target = '_blank',\n}: OcTallyBadgeProps) => {\n const { data, error, loading } = useTally(pollId, {\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n ...(refreshMs !== undefined ? { refreshMs } : {}),\n ...(initialData !== undefined ? { initialData } : {}),\n });\n const t = THEMES[theme];\n\n const { dotColor, label } = useMemo(() => renderState(data, error, loading, t), [data, error, loading, t]);\n\n const href = `${baseUrl ?? 'https://vote.ochk.io'}/p/${pollId}`;\n\n const outerStyle: CSSProperties = {\n display: 'inline-flex',\n alignItems: 'center',\n gap: 8,\n padding: '4px 10px',\n border: `1px solid ${t.border}`,\n borderRadius: 6,\n background: t.bg,\n color: t.text,\n fontFamily: 'ui-monospace, Menlo, monospace',\n fontSize: 12,\n textDecoration: 'none',\n lineHeight: 1.4,\n ...style,\n };\n\n return (\n <a\n href={href}\n target={target}\n rel={target === '_blank' ? 'noopener noreferrer' : undefined}\n className={className}\n style={outerStyle}\n data-slot=\"oc-tally-badge\"\n >\n <span\n aria-hidden\n style={{\n width: 8,\n height: 8,\n borderRadius: '50%',\n background: dotColor,\n flexShrink: 0,\n }}\n />\n <span style={{ fontWeight: 600 }}>oc vote</span>\n <span style={{ color: t.muted }}>·</span>\n <span>{label}</span>\n </a>\n );\n};\n\nfunction renderState(\n data: TallyResponse | null,\n error: Error | null,\n loading: boolean,\n t: Theme\n): { dotColor: string; label: string } {\n if (error) return { dotColor: '#ef4444', label: error.message };\n if (loading && !data) return { dotColor: t.muted, label: 'loading…' };\n if (!data) return { dotColor: t.muted, label: 'no data' };\n if (data.state === 'awaiting_reveal')\n return { dotColor: t.accent, label: `awaiting reveal · ${data.ballot_count ?? 0} ballots` };\n if (data.state !== 'tallied') return { dotColor: t.muted, label: String(data.state) };\n\n const total = data.turnout?.weight ?? 0;\n const tallies = data.tallies ?? {};\n const sorted = Object.entries(tallies).sort((a, b) => b[1] - a[1]);\n if (sorted.length === 0 || total === 0) return { dotColor: t.muted, label: 'no ballots yet' };\n\n const [topId, topW] = sorted[0]!;\n const pct = (topW / total) * 100;\n return {\n dotColor: '#10b981',\n label: `${topId} · ${pct.toFixed(0)}% of ${fmt(total)}`,\n };\n}\n\nfunction fmt(n: number): string {\n if (n >= 1_000_000_000) return (n / 1_000_000_000).toFixed(1) + 'B';\n if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';\n if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k';\n return String(n);\n}\n\nexport const OcTallyBadge = memo(OcTallyBadgeImpl);\n"]}
|
package/dist/badge.mjs
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { memo, useMemo, useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/types.ts
|
|
5
|
+
var THEMES = {
|
|
6
|
+
light: {
|
|
7
|
+
bg: "#ffffff",
|
|
8
|
+
text: "#0a0a0a",
|
|
9
|
+
muted: "#666666",
|
|
10
|
+
border: "#e5e5e5",
|
|
11
|
+
accent: "#f97316",
|
|
12
|
+
accentSoft: "#fff4ec"
|
|
13
|
+
},
|
|
14
|
+
dark: {
|
|
15
|
+
bg: "#0a0a0a",
|
|
16
|
+
text: "#fafafa",
|
|
17
|
+
muted: "#a0a0a0",
|
|
18
|
+
border: "#262626",
|
|
19
|
+
accent: "#fb923c",
|
|
20
|
+
accentSoft: "#2a1a0a"
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var DEFAULT_API_BASE = "https://vote.ochk.io";
|
|
24
|
+
var POLL_ID_RE = /^[0-9a-f]{64}$/;
|
|
25
|
+
function useTally(pollId, opts = {}) {
|
|
26
|
+
const baseUrl = opts.baseUrl ?? DEFAULT_API_BASE;
|
|
27
|
+
const refreshMs = opts.refreshMs ?? 6e4;
|
|
28
|
+
const [data, setData] = useState(opts.initialData ?? null);
|
|
29
|
+
const [error, setError] = useState(null);
|
|
30
|
+
const [loading, setLoading] = useState(!opts.initialData);
|
|
31
|
+
const pollIdRef = useRef(pollId);
|
|
32
|
+
pollIdRef.current = pollId;
|
|
33
|
+
const fetchOnce = useCallback(async () => {
|
|
34
|
+
const pid = pollIdRef.current;
|
|
35
|
+
if (!POLL_ID_RE.test(pid)) {
|
|
36
|
+
setError(new Error("pollId must be 64 hex chars"));
|
|
37
|
+
setLoading(false);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch(`${baseUrl}/api/tally?poll=${pid}`);
|
|
42
|
+
const body = await res.json();
|
|
43
|
+
if (!res.ok) throw new Error(body.error ?? `http ${res.status}`);
|
|
44
|
+
setData(body);
|
|
45
|
+
setError(null);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
48
|
+
} finally {
|
|
49
|
+
setLoading(false);
|
|
50
|
+
}
|
|
51
|
+
}, [baseUrl]);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
void fetchOnce();
|
|
54
|
+
if (refreshMs <= 0) return;
|
|
55
|
+
const timer = setInterval(() => void fetchOnce(), refreshMs);
|
|
56
|
+
return () => clearInterval(timer);
|
|
57
|
+
}, [fetchOnce, refreshMs]);
|
|
58
|
+
return { data, error, loading, refetch: fetchOnce };
|
|
59
|
+
}
|
|
60
|
+
var OcTallyBadgeImpl = ({
|
|
61
|
+
pollId,
|
|
62
|
+
theme = "light",
|
|
63
|
+
baseUrl,
|
|
64
|
+
refreshMs,
|
|
65
|
+
initialData,
|
|
66
|
+
className,
|
|
67
|
+
style,
|
|
68
|
+
target = "_blank"
|
|
69
|
+
}) => {
|
|
70
|
+
const { data, error, loading } = useTally(pollId, {
|
|
71
|
+
...baseUrl !== void 0 ? { baseUrl } : {},
|
|
72
|
+
...refreshMs !== void 0 ? { refreshMs } : {},
|
|
73
|
+
...initialData !== void 0 ? { initialData } : {}
|
|
74
|
+
});
|
|
75
|
+
const t = THEMES[theme];
|
|
76
|
+
const { dotColor, label } = useMemo(() => renderState(data, error, loading, t), [data, error, loading, t]);
|
|
77
|
+
const href = `${baseUrl ?? "https://vote.ochk.io"}/p/${pollId}`;
|
|
78
|
+
const outerStyle = {
|
|
79
|
+
display: "inline-flex",
|
|
80
|
+
alignItems: "center",
|
|
81
|
+
gap: 8,
|
|
82
|
+
padding: "4px 10px",
|
|
83
|
+
border: `1px solid ${t.border}`,
|
|
84
|
+
borderRadius: 6,
|
|
85
|
+
background: t.bg,
|
|
86
|
+
color: t.text,
|
|
87
|
+
fontFamily: "ui-monospace, Menlo, monospace",
|
|
88
|
+
fontSize: 12,
|
|
89
|
+
textDecoration: "none",
|
|
90
|
+
lineHeight: 1.4,
|
|
91
|
+
...style
|
|
92
|
+
};
|
|
93
|
+
return /* @__PURE__ */ jsxs(
|
|
94
|
+
"a",
|
|
95
|
+
{
|
|
96
|
+
href,
|
|
97
|
+
target,
|
|
98
|
+
rel: target === "_blank" ? "noopener noreferrer" : void 0,
|
|
99
|
+
className,
|
|
100
|
+
style: outerStyle,
|
|
101
|
+
"data-slot": "oc-tally-badge",
|
|
102
|
+
children: [
|
|
103
|
+
/* @__PURE__ */ jsx(
|
|
104
|
+
"span",
|
|
105
|
+
{
|
|
106
|
+
"aria-hidden": true,
|
|
107
|
+
style: {
|
|
108
|
+
width: 8,
|
|
109
|
+
height: 8,
|
|
110
|
+
borderRadius: "50%",
|
|
111
|
+
background: dotColor,
|
|
112
|
+
flexShrink: 0
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: "oc vote" }),
|
|
117
|
+
/* @__PURE__ */ jsx("span", { style: { color: t.muted }, children: "\xB7" }),
|
|
118
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
function renderState(data, error, loading, t) {
|
|
124
|
+
if (error) return { dotColor: "#ef4444", label: error.message };
|
|
125
|
+
if (loading && !data) return { dotColor: t.muted, label: "loading\u2026" };
|
|
126
|
+
if (!data) return { dotColor: t.muted, label: "no data" };
|
|
127
|
+
if (data.state === "awaiting_reveal")
|
|
128
|
+
return { dotColor: t.accent, label: `awaiting reveal \xB7 ${data.ballot_count ?? 0} ballots` };
|
|
129
|
+
if (data.state !== "tallied") return { dotColor: t.muted, label: String(data.state) };
|
|
130
|
+
const total = data.turnout?.weight ?? 0;
|
|
131
|
+
const tallies = data.tallies ?? {};
|
|
132
|
+
const sorted = Object.entries(tallies).sort((a, b) => b[1] - a[1]);
|
|
133
|
+
if (sorted.length === 0 || total === 0) return { dotColor: t.muted, label: "no ballots yet" };
|
|
134
|
+
const [topId, topW] = sorted[0];
|
|
135
|
+
const pct = topW / total * 100;
|
|
136
|
+
return {
|
|
137
|
+
dotColor: "#10b981",
|
|
138
|
+
label: `${topId} \xB7 ${pct.toFixed(0)}% of ${fmt(total)}`
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function fmt(n) {
|
|
142
|
+
if (n >= 1e9) return (n / 1e9).toFixed(1) + "B";
|
|
143
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
|
|
144
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
|
|
145
|
+
return String(n);
|
|
146
|
+
}
|
|
147
|
+
var OcTallyBadge = memo(OcTallyBadgeImpl);
|
|
148
|
+
|
|
149
|
+
export { OcTallyBadge };
|
|
150
|
+
//# sourceMappingURL=badge.mjs.map
|
|
151
|
+
//# sourceMappingURL=badge.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/use-tally.ts","../src/badge.tsx"],"names":[],"mappings":"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","file":"badge.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","'use client';\n\n/**\n * <OcTallyBadge pollId=\"…\" />\n *\n * Compact inline pill. Shows:\n * - a colored dot (green=tallied, orange=awaiting_reveal, gray=loading, red=error)\n * - the top option + its percentage (tallied), or the state label (otherwise)\n * - a subtle link to vote.ochk.io/p/<id>\n *\n * Use on a feed row / profile card / sidebar. For the full poll card, use <OcPoll />.\n */\n\nimport type { CSSProperties } from 'react';\nimport { memo, useMemo } from 'react';\n\nimport { THEMES, type Theme, type TallyResponse } from './types.js';\nimport { useTally } from './use-tally.js';\n\nexport interface OcTallyBadgeProps {\n /** 64-hex poll id. */\n pollId: string;\n /** Theme. Default `light`. */\n theme?: 'light' | 'dark';\n /** Override /api/tally origin. Default `https://vote.ochk.io`. */\n baseUrl?: string;\n /** Auto-refresh interval in ms. Default 60000. Set to 0 to disable. */\n refreshMs?: number;\n /** Supply SSR-hydrated data to skip the initial fetch. */\n initialData?: TallyResponse;\n /** Extra class on the outer element. */\n className?: string;\n /** Inline style override. */\n style?: CSSProperties;\n /** Target for the link. Default `_blank`. */\n target?: '_blank' | '_self';\n}\n\nconst OcTallyBadgeImpl = ({\n pollId,\n theme = 'light',\n baseUrl,\n refreshMs,\n initialData,\n className,\n style,\n target = '_blank',\n}: OcTallyBadgeProps) => {\n const { data, error, loading } = useTally(pollId, {\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n ...(refreshMs !== undefined ? { refreshMs } : {}),\n ...(initialData !== undefined ? { initialData } : {}),\n });\n const t = THEMES[theme];\n\n const { dotColor, label } = useMemo(() => renderState(data, error, loading, t), [data, error, loading, t]);\n\n const href = `${baseUrl ?? 'https://vote.ochk.io'}/p/${pollId}`;\n\n const outerStyle: CSSProperties = {\n display: 'inline-flex',\n alignItems: 'center',\n gap: 8,\n padding: '4px 10px',\n border: `1px solid ${t.border}`,\n borderRadius: 6,\n background: t.bg,\n color: t.text,\n fontFamily: 'ui-monospace, Menlo, monospace',\n fontSize: 12,\n textDecoration: 'none',\n lineHeight: 1.4,\n ...style,\n };\n\n return (\n <a\n href={href}\n target={target}\n rel={target === '_blank' ? 'noopener noreferrer' : undefined}\n className={className}\n style={outerStyle}\n data-slot=\"oc-tally-badge\"\n >\n <span\n aria-hidden\n style={{\n width: 8,\n height: 8,\n borderRadius: '50%',\n background: dotColor,\n flexShrink: 0,\n }}\n />\n <span style={{ fontWeight: 600 }}>oc vote</span>\n <span style={{ color: t.muted }}>·</span>\n <span>{label}</span>\n </a>\n );\n};\n\nfunction renderState(\n data: TallyResponse | null,\n error: Error | null,\n loading: boolean,\n t: Theme\n): { dotColor: string; label: string } {\n if (error) return { dotColor: '#ef4444', label: error.message };\n if (loading && !data) return { dotColor: t.muted, label: 'loading…' };\n if (!data) return { dotColor: t.muted, label: 'no data' };\n if (data.state === 'awaiting_reveal')\n return { dotColor: t.accent, label: `awaiting reveal · ${data.ballot_count ?? 0} ballots` };\n if (data.state !== 'tallied') return { dotColor: t.muted, label: String(data.state) };\n\n const total = data.turnout?.weight ?? 0;\n const tallies = data.tallies ?? {};\n const sorted = Object.entries(tallies).sort((a, b) => b[1] - a[1]);\n if (sorted.length === 0 || total === 0) return { dotColor: t.muted, label: 'no ballots yet' };\n\n const [topId, topW] = sorted[0]!;\n const pct = (topW / total) * 100;\n return {\n dotColor: '#10b981',\n label: `${topId} · ${pct.toFixed(0)}% of ${fmt(total)}`,\n };\n}\n\nfunction fmt(n: number): string {\n if (n >= 1_000_000_000) return (n / 1_000_000_000).toFixed(1) + 'B';\n if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';\n if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k';\n return String(n);\n}\n\nexport const OcTallyBadge = memo(OcTallyBadgeImpl);\n"]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { OcPoll, OcPollProps } from './poll.mjs';
|
|
2
|
+
export { OcTallyBadge, OcTallyBadgeProps } from './badge.mjs';
|
|
3
|
+
export { UseTallyOptions, UseTallyState, useTally } from './use-tally.mjs';
|
|
4
|
+
export { D as DEFAULT_API_BASE, P as POLL_ID_RE, T as THEMES, a as TallyResponse, b as TallyTurnout, c as Theme } from './types-Czxkdz7H.mjs';
|
|
5
|
+
import 'react';
|
|
6
|
+
import 'react/jsx-runtime';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { OcPoll, OcPollProps } from './poll.js';
|
|
2
|
+
export { OcTallyBadge, OcTallyBadgeProps } from './badge.js';
|
|
3
|
+
export { UseTallyOptions, UseTallyState, useTally } from './use-tally.js';
|
|
4
|
+
export { D as DEFAULT_API_BASE, P as POLL_ID_RE, T as THEMES, a as TallyResponse, b as TallyTurnout, c as Theme } from './types-Czxkdz7H.js';
|
|
5
|
+
import 'react';
|
|
6
|
+
import 'react/jsx-runtime';
|