dev-api-ui 0.1.7

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.
Files changed (34) hide show
  1. package/README.md +36 -0
  2. package/package.json +40 -0
  3. package/ui/ThemeProvider.tsx +16 -0
  4. package/ui/components/ArticlePage.tsx +673 -0
  5. package/ui/components/AuthPage.tsx +109 -0
  6. package/ui/components/Callout.tsx +79 -0
  7. package/ui/components/Chart.tsx +100 -0
  8. package/ui/components/CodeTabs.tsx +145 -0
  9. package/ui/components/ComparePage.tsx +364 -0
  10. package/ui/components/DashboardPage.tsx +773 -0
  11. package/ui/components/DocsNav.tsx +80 -0
  12. package/ui/components/Footer.tsx +136 -0
  13. package/ui/components/HubPage.tsx +529 -0
  14. package/ui/components/Modal.tsx +412 -0
  15. package/ui/components/Nav.tsx +162 -0
  16. package/ui/components/OnThisPage.tsx +56 -0
  17. package/ui/components/ParamsTable.tsx +86 -0
  18. package/ui/components/RangeBar.tsx +68 -0
  19. package/ui/components/SeriesChart.tsx +218 -0
  20. package/ui/components/SeriesPage.tsx +461 -0
  21. package/ui/components/StatusMark.tsx +48 -0
  22. package/ui/components/publictrades/ExternalLink.tsx +37 -0
  23. package/ui/components/publictrades/FactsCard.tsx +40 -0
  24. package/ui/components/publictrades/LedgerFooter.tsx +22 -0
  25. package/ui/components/publictrades/LedgerNav.tsx +78 -0
  26. package/ui/components/publictrades/Mark.tsx +40 -0
  27. package/ui/components/publictrades/SourceBlock.tsx +91 -0
  28. package/ui/components/publictrades/Tape.tsx +164 -0
  29. package/ui/components/publictrades/ThreeStamps.tsx +47 -0
  30. package/ui/components/publictrades/types.ts +19 -0
  31. package/ui/data/series.ts +851 -0
  32. package/ui/index.ts +50 -0
  33. package/ui/publictrades.ts +16 -0
  34. package/ui/themes.css +81 -0
@@ -0,0 +1,109 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+
5
+ const GoogleSVG = () => (
6
+ <svg width="16" height="16" viewBox="0 0 48 48" aria-hidden="true">
7
+ <path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" />
8
+ <path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z" />
9
+ <path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z" />
10
+ <path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" />
11
+ </svg>
12
+ );
13
+
14
+ const GitHubSVG = () => (
15
+ <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" aria-hidden="true">
16
+ <path d="M12 2a10 10 0 0 0-3.16 19.49c.5.09.68-.22.68-.48l-.01-1.7c-2.78.6-3.37-1.34-3.37-1.34-.45-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.89 1.53 2.34 1.09 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.94 0-1.09.39-1.98 1.03-2.68-.1-.25-.45-1.27.1-2.65 0 0 .84-.27 2.75 1.02a9.5 9.5 0 0 1 5 0c1.91-1.29 2.75-1.02 2.75-1.02.55 1.38.2 2.4.1 2.65.64.7 1.03 1.59 1.03 2.68 0 3.84-2.34 4.69-4.57 4.94.36.31.68.92.68 1.85l-.01 2.75c0 .27.18.58.69.48A10 10 0 0 0 12 2z" />
17
+ </svg>
18
+ );
19
+
20
+ const oauthBtn: React.CSSProperties = {
21
+ width: '100%',
22
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 10,
23
+ fontFamily: 'inherit', fontSize: 14, fontWeight: 500,
24
+ padding: '11px 15px', borderRadius: 4,
25
+ border: '0.5px solid var(--hairline-2)',
26
+ background: 'var(--surface)', color: 'var(--ink)', cursor: 'pointer',
27
+ };
28
+
29
+ interface AuthPageProps {
30
+ mode: 'login' | 'signup';
31
+ wordmark?: string;
32
+ }
33
+
34
+ export default function AuthPage({ mode, wordmark = 'Console' }: AuthPageProps) {
35
+ const isLogin = mode === 'login';
36
+
37
+ return (
38
+ <div style={{
39
+ minHeight: '100vh', display: 'flex', alignItems: 'center',
40
+ justifyContent: 'center', padding: 32, background: 'var(--paper)',
41
+ }}>
42
+ <div style={{ width: '100%', maxWidth: 380 }}>
43
+ {/* Wordmark */}
44
+ <div style={{
45
+ display: 'flex', alignItems: 'center', gap: 9, justifyContent: 'center',
46
+ fontFamily: 'var(--mono)', fontWeight: 600, fontSize: 17, letterSpacing: '-.01em',
47
+ }}>
48
+ <span style={{ width: 10, height: 10, background: 'var(--accent)', transform: 'rotate(45deg)', display: 'inline-block' }} />
49
+ {wordmark}
50
+ </div>
51
+
52
+ {/* Card */}
53
+ <div style={{
54
+ background: 'var(--surface)', border: '0.5px solid var(--hairline-2)',
55
+ borderRadius: 10, padding: '28px', marginTop: 22,
56
+ }}>
57
+ <h1 style={{
58
+ fontFamily: 'var(--mono)', fontSize: 20, fontWeight: 600,
59
+ letterSpacing: '-.01em', textAlign: 'center',
60
+ }}>
61
+ {isLogin ? 'Sign in' : 'Create your account'}
62
+ </h1>
63
+ <p style={{ fontSize: 13, color: 'var(--tertiary)', textAlign: 'center', marginTop: 5 }}>
64
+ {isLogin
65
+ ? 'Continue with Google or GitHub'
66
+ : 'No password to manage — continue with Google or GitHub'}
67
+ </p>
68
+
69
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginTop: 22 }}>
70
+ <button
71
+ style={oauthBtn}
72
+ onMouseEnter={(e) => { e.currentTarget.style.borderColor = 'var(--secondary)'; }}
73
+ onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'var(--hairline-2)'; }}
74
+ >
75
+ <GoogleSVG />
76
+ Continue with Google
77
+ </button>
78
+ <button
79
+ style={oauthBtn}
80
+ onMouseEnter={(e) => { e.currentTarget.style.borderColor = 'var(--secondary)'; }}
81
+ onMouseLeave={(e) => { e.currentTarget.style.borderColor = 'var(--hairline-2)'; }}
82
+ >
83
+ <GitHubSVG />
84
+ Continue with GitHub
85
+ </button>
86
+ </div>
87
+
88
+ {!isLogin && (
89
+ <p style={{ fontSize: 12, color: 'var(--tertiary)', textAlign: 'center', marginTop: 16, lineHeight: 1.5 }}>
90
+ By continuing you agree to the{' '}
91
+ <a href="#" style={{ fontWeight: 500, color: 'var(--accent)', textDecoration: 'none' }}>Terms</a>
92
+ {' '}and{' '}
93
+ <a href="#" style={{ fontWeight: 500, color: 'var(--accent)', textDecoration: 'none' }}>Privacy Policy</a>.
94
+ </p>
95
+ )}
96
+ </div>
97
+
98
+ {/* Footer link */}
99
+ <div style={{ textAlign: 'center', fontSize: 13, color: 'var(--tertiary)', marginTop: 16 }}>
100
+ {isLogin ? (
101
+ <>New to {wordmark}? <Link href="/signup" style={{ fontWeight: 500, color: 'var(--accent)' }}>Create an account</Link></>
102
+ ) : (
103
+ <>Already have an account? <Link href="/login" style={{ fontWeight: 500, color: 'var(--accent)' }}>Sign in</Link></>
104
+ )}
105
+ </div>
106
+ </div>
107
+ </div>
108
+ );
109
+ }
@@ -0,0 +1,79 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ type CalloutVariant = 'warn' | 'info' | 'tip';
4
+
5
+ interface CalloutProps {
6
+ variant?: CalloutVariant;
7
+ title: string;
8
+ children: ReactNode;
9
+ }
10
+
11
+ const ICONS: Record<CalloutVariant, ReactNode> = {
12
+ warn: (
13
+ <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor"
14
+ strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
15
+ <path d="M12 9v4" />
16
+ <path d="M12 17h.01" />
17
+ <path d="M10.3 4.3 2.5 18a2 2 0 0 0 1.7 3h15.6a2 2 0 0 0 1.7-3L13.7 4.3a2 2 0 0 0-3.4 0z" />
18
+ </svg>
19
+ ),
20
+ info: (
21
+ <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor"
22
+ strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
23
+ <circle cx="12" cy="12" r="10" />
24
+ <path d="M12 8v4" />
25
+ <path d="M12 16h.01" />
26
+ </svg>
27
+ ),
28
+ tip: (
29
+ <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor"
30
+ strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
31
+ <path d="M12 2a7 7 0 0 1 7 7c0 3.18-2.08 5.89-5 6.71V17a2 2 0 0 1-2 2h0a2 2 0 0 1-2-2v-1.29C7.08 14.89 5 12.18 5 9a7 7 0 0 1 7-7z" />
32
+ <path d="M9 21h6" />
33
+ </svg>
34
+ ),
35
+ };
36
+
37
+ const ICON_COLORS: Record<CalloutVariant, string> = {
38
+ warn: 'var(--warn)',
39
+ info: 'var(--info)',
40
+ tip: 'var(--accent)',
41
+ };
42
+
43
+ export default function Callout({ variant = 'warn', title, children }: CalloutProps) {
44
+ return (
45
+ <div
46
+ style={{
47
+ display: 'flex',
48
+ alignItems: 'flex-start',
49
+ gap: 12,
50
+ marginTop: 24,
51
+ background: '#F4F4F1',
52
+ border: '0.5px solid var(--hairline-2)',
53
+ borderRadius: 8,
54
+ padding: '16px 18px',
55
+ }}
56
+ >
57
+ <span
58
+ style={{
59
+ flex: 'none',
60
+ display: 'inline-flex',
61
+ alignItems: 'center',
62
+ justifyContent: 'center',
63
+ width: 20,
64
+ height: 20,
65
+ color: ICON_COLORS[variant],
66
+ marginTop: 1,
67
+ }}
68
+ >
69
+ {ICONS[variant]}
70
+ </span>
71
+ <div>
72
+ <div style={{ fontSize: 14, fontWeight: 500, color: 'var(--ink)' }}>{title}</div>
73
+ <div style={{ fontSize: 14, color: 'var(--secondary)', marginTop: 3, lineHeight: 1.55 }}>
74
+ {children}
75
+ </div>
76
+ </div>
77
+ </div>
78
+ );
79
+ }
@@ -0,0 +1,100 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef } from 'react';
4
+ import { createChart, AreaSeries, ColorType, LineStyle } from 'lightweight-charts';
5
+
6
+ const DATA = [
7
+ { time: '2024-01-02', value: 182.4 },
8
+ { time: '2024-01-05', value: 184.1 },
9
+ { time: '2024-01-08', value: 181.9 },
10
+ { time: '2024-01-11', value: 185.7 },
11
+ { time: '2024-01-14', value: 188.3 },
12
+ { time: '2024-01-17', value: 186.2 },
13
+ { time: '2024-01-22', value: 190.5 },
14
+ { time: '2024-01-25', value: 193.1 },
15
+ { time: '2024-01-29', value: 191.8 },
16
+ { time: '2024-02-01', value: 195.4 },
17
+ { time: '2024-02-05', value: 198.2 },
18
+ { time: '2024-02-08', value: 196.7 },
19
+ { time: '2024-02-12', value: 201.3 },
20
+ { time: '2024-02-15', value: 204.8 },
21
+ { time: '2024-02-20', value: 202.1 },
22
+ { time: '2024-02-23', value: 207.6 },
23
+ ];
24
+
25
+ const ACCENT = '#1F8A5B';
26
+
27
+ export default function Chart() {
28
+ const containerRef = useRef<HTMLDivElement>(null);
29
+
30
+ useEffect(() => {
31
+ const el = containerRef.current;
32
+ if (!el) return;
33
+
34
+ const chart = createChart(el, {
35
+ width: el.clientWidth,
36
+ height: 240,
37
+ layout: {
38
+ background: { type: ColorType.Solid, color: 'transparent' },
39
+ textColor: '#8A8F98',
40
+ fontFamily: 'IBM Plex Sans, sans-serif',
41
+ fontSize: 11,
42
+ },
43
+ grid: {
44
+ vertLines: { visible: false },
45
+ horzLines: { color: '#E6E6E2', style: LineStyle.Solid },
46
+ },
47
+ rightPriceScale: {
48
+ borderVisible: false,
49
+ ticksVisible: false,
50
+ },
51
+ timeScale: {
52
+ borderVisible: false,
53
+ ticksVisible: false,
54
+ },
55
+ crosshair: {
56
+ vertLine: { visible: false },
57
+ horzLine: { visible: false },
58
+ },
59
+ handleScroll: false,
60
+ handleScale: false,
61
+ });
62
+
63
+ const series = chart.addSeries(AreaSeries, {
64
+ lineColor: ACCENT,
65
+ lineWidth: 2,
66
+ topColor: 'rgba(31,138,91,0.12)',
67
+ bottomColor: 'rgba(31,138,91,0)',
68
+ priceLineVisible: false,
69
+ lastValueVisible: false,
70
+ crosshairMarkerVisible: false,
71
+ });
72
+
73
+ series.setData(DATA);
74
+ chart.timeScale().fitContent();
75
+
76
+ const ro = new ResizeObserver(() => {
77
+ chart.applyOptions({ width: el.clientWidth });
78
+ });
79
+ ro.observe(el);
80
+
81
+ return () => {
82
+ ro.disconnect();
83
+ chart.remove();
84
+ };
85
+ }, []);
86
+
87
+ return (
88
+ <div
89
+ style={{
90
+ border: '0.5px solid var(--hairline-2)',
91
+ borderRadius: '8px',
92
+ background: 'var(--surface)',
93
+ overflow: 'hidden',
94
+ padding: '18px 18px 10px',
95
+ }}
96
+ >
97
+ <div ref={containerRef} />
98
+ </div>
99
+ );
100
+ }
@@ -0,0 +1,145 @@
1
+ 'use client';
2
+
3
+ import { useState, useRef, useEffect, ReactNode } from 'react';
4
+
5
+ type Tab = 'curl' | 'node' | 'python';
6
+
7
+ interface CodeTabsProps {
8
+ group: string;
9
+ id: string;
10
+ apiUrl?: string;
11
+ }
12
+
13
+ function cy(t: string): ReactNode {
14
+ return <span style={{ color: 'var(--cyan)' }}>{t}</span>;
15
+ }
16
+ function am(t: string): ReactNode {
17
+ return <span style={{ color: 'var(--amber)' }}>{t}</span>;
18
+ }
19
+ function pu(t: string): ReactNode {
20
+ return <span style={{ color: 'var(--purple)' }}>{t}</span>;
21
+ }
22
+ function dm(t: string): ReactNode {
23
+ return <span style={{ color: 'var(--code-dim)' }}>{t}</span>;
24
+ }
25
+
26
+ function codeText(tab: Tab, group: string, id: string, apiUrl: string): string {
27
+ if (tab === 'node') {
28
+ return `const r = await c.${group}.get("${id}")\nconsole.log(r.value, r.ts, r.source)`;
29
+ }
30
+ if (tab === 'python') {
31
+ return `r = c.${group}.get("${id}")\nprint(r["value"], r["ts"], r["source"])`;
32
+ }
33
+ return `$ curl ${apiUrl}/v1/${group}/${id} \\\n -H "Authorization: Bearer $API_KEY"\n# → value, ts, source, point_in_time`;
34
+ }
35
+
36
+ function codeFor(tab: Tab, group: string, id: string, apiUrl: string): ReactNode {
37
+ if (tab === 'node') {
38
+ return (
39
+ <>
40
+ {pu('const')} r = {pu('await')} c.{cy(group)}.{cy('get')}({am(`"${id}"`)}{')'}
41
+ {'\n'}
42
+ {cy('console')}.log(r.value, r.ts, r.source)
43
+ </>
44
+ );
45
+ }
46
+ if (tab === 'python') {
47
+ return (
48
+ <>
49
+ r = c.{cy(group)}.{cy('get')}({am(`"${id}"`)}{')'}
50
+ {'\n'}
51
+ {pu('print')}(r[{am('"value"')}], r[{am('"ts"')}], r[{am('"source"')}])
52
+ </>
53
+ );
54
+ }
55
+ return (
56
+ <>
57
+ {cy('$')} curl {apiUrl}/v1/{group}/{id} \{'\n'}
58
+ {' '}-H {am('"Authorization: Bearer $API_KEY"')}{'\n'}
59
+ {dm('# → value, ts, source, point_in_time')}
60
+ </>
61
+ );
62
+ }
63
+
64
+ export default function CodeTabs({ group, id, apiUrl = 'https://api.console.dev' }: CodeTabsProps) {
65
+ const [tab, setTab] = useState<Tab>('curl');
66
+ const [copied, setCopied] = useState(false);
67
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
68
+
69
+ useEffect(() => () => { if (timerRef.current) clearTimeout(timerRef.current); }, []);
70
+
71
+ function handleCopy() {
72
+ navigator.clipboard.writeText(codeText(tab, group, id, apiUrl)).then(() => {
73
+ setCopied(true);
74
+ if (timerRef.current) clearTimeout(timerRef.current);
75
+ timerRef.current = setTimeout(() => setCopied(false), 1500);
76
+ });
77
+ }
78
+
79
+ const mkTab = (t: Tab, label: string) => ({
80
+ label,
81
+ active: tab === t,
82
+ onClick: () => setTab(t),
83
+ });
84
+
85
+ const tabs = [mkTab('curl', 'curl'), mkTab('node', 'Node'), mkTab('python', 'Python')];
86
+
87
+ return (
88
+ <div
89
+ style={{
90
+ borderRadius: 8,
91
+ overflow: 'hidden',
92
+ border: '0.5px solid #23262B',
93
+ background: 'var(--code-bg)',
94
+ }}
95
+ >
96
+ {/* Tab bar */}
97
+ <div
98
+ style={{
99
+ display: 'flex',
100
+ alignItems: 'center',
101
+ justifyContent: 'space-between',
102
+ borderBottom: '0.5px solid #23262B',
103
+ padding: '0 12px',
104
+ }}
105
+ >
106
+ <div style={{ display: 'flex' }}>
107
+ {tabs.map((t) => (
108
+ <button
109
+ key={t.label}
110
+ onClick={t.onClick}
111
+ style={{
112
+ fontFamily: 'var(--mono)',
113
+ fontSize: 13,
114
+ padding: '11px 13px',
115
+ background: 'transparent',
116
+ border: 'none',
117
+ borderBottom: `2px solid ${t.active ? 'var(--cyan)' : 'transparent'}`,
118
+ color: t.active ? 'var(--code-fg)' : 'var(--code-dim)',
119
+ cursor: 'pointer',
120
+ }}
121
+ >
122
+ {t.label}
123
+ </button>
124
+ ))}
125
+ </div>
126
+ <button onClick={handleCopy} style={{ fontSize: 11, color: 'var(--code-dim)', background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}>{copied ? 'Copied!' : 'copy'}</button>
127
+ </div>
128
+
129
+ {/* Code panel */}
130
+ <pre
131
+ style={{
132
+ margin: 0,
133
+ padding: '18px 16px',
134
+ fontFamily: 'var(--mono)',
135
+ fontSize: 13,
136
+ lineHeight: 1.7,
137
+ color: 'var(--code-fg)',
138
+ overflowX: 'auto',
139
+ }}
140
+ >
141
+ {codeFor(tab, group, id, apiUrl)}
142
+ </pre>
143
+ </div>
144
+ );
145
+ }