create-fedi-app 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.
Files changed (133) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +11113 -0
  3. package/dist/templates/base/.env.example +5 -0
  4. package/dist/templates/base/app/demo/page.tsx +25 -0
  5. package/dist/templates/base/app/globals.css +95 -0
  6. package/dist/templates/base/app/layout.tsx +39 -0
  7. package/dist/templates/base/app/page.tsx +83 -0
  8. package/dist/templates/base/components/FediDevToolbar/FediDevToolbar.tsx +170 -0
  9. package/dist/templates/base/components/providers.tsx +63 -0
  10. package/dist/templates/base/env.ts +10 -0
  11. package/dist/templates/base/hooks/useFediInternal.ts +41 -0
  12. package/dist/templates/base/lib/fedi-types.ts +96 -0
  13. package/dist/templates/base/lib/fedi.ts +18 -0
  14. package/dist/templates/base/lib/nostr/hooks.ts +52 -0
  15. package/dist/templates/base/lib/nostr/index.ts +9 -0
  16. package/dist/templates/base/lib/nostr/mock.ts +60 -0
  17. package/dist/templates/base/lib/nostr/provider.tsx +64 -0
  18. package/dist/templates/base/lib/utils.ts +3 -0
  19. package/dist/templates/base/lib/webln/hooks.ts +67 -0
  20. package/dist/templates/base/lib/webln/index.ts +12 -0
  21. package/dist/templates/base/lib/webln/mock.ts +96 -0
  22. package/dist/templates/base/lib/webln/provider.tsx +52 -0
  23. package/dist/templates/base/next.config.ts +3 -0
  24. package/dist/templates/base/package.json +40 -0
  25. package/dist/templates/base/proxy.ts +8 -0
  26. package/dist/templates/base/tsconfig.json +20 -0
  27. package/dist/templates/base/vitest.config.ts +6 -0
  28. package/dist/templates/base/vitest.setup.ts +40 -0
  29. package/dist/templates/modules/ai-assistant/app/api/assistant/route.ts +45 -0
  30. package/dist/templates/modules/ai-assistant/app/demo/assistant/AssistantDemoClient.tsx +70 -0
  31. package/dist/templates/modules/ai-assistant/app/demo/assistant/page.tsx +23 -0
  32. package/dist/templates/modules/ai-assistant/components/ai/Assistant.tsx +220 -0
  33. package/dist/templates/modules/ai-assistant/components/ai/AssistantProvider.tsx +71 -0
  34. package/dist/templates/modules/ai-assistant/lib/ai/providers.ts +49 -0
  35. package/dist/templates/modules/ai-assistant/module.json +48 -0
  36. package/dist/templates/modules/ai-chat-gated/app/api/chat/invoice/route.ts +15 -0
  37. package/dist/templates/modules/ai-chat-gated/app/api/chat/route.ts +57 -0
  38. package/dist/templates/modules/ai-chat-gated/app/demo/ai-chat/page.tsx +58 -0
  39. package/dist/templates/modules/ai-chat-gated/components/ai/ChatMessage.tsx +50 -0
  40. package/dist/templates/modules/ai-chat-gated/components/ai/GatedChat.tsx +181 -0
  41. package/dist/templates/modules/ai-chat-gated/components/ai/PaymentGate.tsx +168 -0
  42. package/dist/templates/modules/ai-chat-gated/lib/ai/providers.ts +49 -0
  43. package/dist/templates/modules/ai-chat-gated/lib/chat-payment.ts +161 -0
  44. package/dist/templates/modules/ai-chat-gated/module.json +62 -0
  45. package/dist/templates/modules/ai-rules/.cursorrules +8 -0
  46. package/dist/templates/modules/ai-rules/.github/copilot-instructions.md +8 -0
  47. package/dist/templates/modules/ai-rules/CLAUDE.md +8 -0
  48. package/dist/templates/modules/ai-rules/module.json +20 -0
  49. package/dist/templates/modules/ai-rules/rules/OVERVIEW.md +56 -0
  50. package/dist/templates/modules/ai-rules/rules/architecture.md +108 -0
  51. package/dist/templates/modules/ai-rules/rules/design-system.md +94 -0
  52. package/dist/templates/modules/ai-rules/rules/fedi-api.md +120 -0
  53. package/dist/templates/modules/ai-rules/rules/nostr.md +232 -0
  54. package/dist/templates/modules/ai-rules/rules/patterns.md +408 -0
  55. package/dist/templates/modules/ai-rules/rules/testing.md +238 -0
  56. package/dist/templates/modules/ai-rules/rules/webln.md +241 -0
  57. package/dist/templates/modules/database/drizzle/supabase/0000_initial.sql +7 -0
  58. package/dist/templates/modules/database/drizzle/supabase/meta/_journal.json +13 -0
  59. package/dist/templates/modules/database/drizzle/turso/0000_initial.sql +7 -0
  60. package/dist/templates/modules/database/drizzle/turso/meta/_journal.json +13 -0
  61. package/dist/templates/modules/database/drizzle.config.supabase.ts +10 -0
  62. package/dist/templates/modules/database/drizzle.config.turso.ts +11 -0
  63. package/dist/templates/modules/database/env.supabase.ts +24 -0
  64. package/dist/templates/modules/database/env.turso.ts +23 -0
  65. package/dist/templates/modules/database/lib/db/index.supabase.ts +19 -0
  66. package/dist/templates/modules/database/lib/db/index.turso.ts +20 -0
  67. package/dist/templates/modules/database/lib/db/schema.supabase.ts +13 -0
  68. package/dist/templates/modules/database/lib/db/schema.turso.ts +13 -0
  69. package/dist/templates/modules/database/module.json +110 -0
  70. package/dist/templates/modules/ecash-balance/app/demo/ecash/page.tsx +115 -0
  71. package/dist/templates/modules/ecash-balance/components/fedi/BalanceDisplay.tsx +162 -0
  72. package/dist/templates/modules/ecash-balance/components/fedi/FediVersionBadge.tsx +39 -0
  73. package/dist/templates/modules/ecash-balance/components/fedi/InstallMiniAppButton.tsx +74 -0
  74. package/dist/templates/modules/ecash-balance/hooks/useFediBalance.ts +65 -0
  75. package/dist/templates/modules/ecash-balance/module.json +14 -0
  76. package/dist/templates/modules/lnurl/app/api/lnurlauth/route.ts +118 -0
  77. package/dist/templates/modules/lnurl/app/api/lnurlp/[username]/route.ts +70 -0
  78. package/dist/templates/modules/lnurl/app/api/lnurlw/route.ts +57 -0
  79. package/dist/templates/modules/lnurl/app/demo/lnurl/page.tsx +136 -0
  80. package/dist/templates/modules/lnurl/components/lnurl/LnurlAuth.tsx +156 -0
  81. package/dist/templates/modules/lnurl/components/lnurl/LnurlPay.tsx +36 -0
  82. package/dist/templates/modules/lnurl/components/lnurl/LnurlQR.tsx +96 -0
  83. package/dist/templates/modules/lnurl/components/lnurl/LnurlWithdraw.tsx +141 -0
  84. package/dist/templates/modules/lnurl/lib/lnurl-auth-verify.ts +87 -0
  85. package/dist/templates/modules/lnurl/lib/lnurl-store.ts +112 -0
  86. package/dist/templates/modules/lnurl/lib/lnurl-utils.ts +56 -0
  87. package/dist/templates/modules/lnurl/module.json +27 -0
  88. package/dist/templates/modules/multispend-demo/app/demo/multispend/MultispendDemoClient.tsx +109 -0
  89. package/dist/templates/modules/multispend-demo/app/demo/multispend/page.tsx +23 -0
  90. package/dist/templates/modules/multispend-demo/components/multispend/ApprovalVote.tsx +122 -0
  91. package/dist/templates/modules/multispend-demo/components/multispend/MultispendDemo.tsx +220 -0
  92. package/dist/templates/modules/multispend-demo/components/multispend/MultispendProposal.tsx +213 -0
  93. package/dist/templates/modules/multispend-demo/components/multispend/ProposalList.tsx +49 -0
  94. package/dist/templates/modules/multispend-demo/hooks/useMultispendDemo.ts +127 -0
  95. package/dist/templates/modules/multispend-demo/lib/multispend-types.ts +33 -0
  96. package/dist/templates/modules/multispend-demo/lib/multispend-utils.ts +69 -0
  97. package/dist/templates/modules/multispend-demo/module.json +18 -0
  98. package/dist/templates/modules/nostr-feed/app/demo/nostr-feed/NostrFeedDemoClient.tsx +134 -0
  99. package/dist/templates/modules/nostr-feed/app/demo/nostr-feed/page.tsx +23 -0
  100. package/dist/templates/modules/nostr-feed/components/nostr/NostrFeedProvider.tsx +47 -0
  101. package/dist/templates/modules/nostr-feed/components/nostr/NoteCard.tsx +68 -0
  102. package/dist/templates/modules/nostr-feed/components/nostr/NoteFeed.tsx +109 -0
  103. package/dist/templates/modules/nostr-feed/components/nostr/PublishNote.tsx +104 -0
  104. package/dist/templates/modules/nostr-feed/components/nostr/ZapButton.tsx +140 -0
  105. package/dist/templates/modules/nostr-feed/lib/nostr/relay.ts +107 -0
  106. package/dist/templates/modules/nostr-feed/lib/nostr-zap.ts +159 -0
  107. package/dist/templates/modules/nostr-feed/module.json +25 -0
  108. package/dist/templates/modules/nostr-identity/app/demo/nostr/page.tsx +136 -0
  109. package/dist/templates/modules/nostr-identity/components/nostr/IdentityBadge.tsx +109 -0
  110. package/dist/templates/modules/nostr-identity/components/nostr/NostrLogin.tsx +107 -0
  111. package/dist/templates/modules/nostr-identity/components/nostr/SignedMessage.tsx +103 -0
  112. package/dist/templates/modules/nostr-identity/hooks/useIdentityFlow.ts +61 -0
  113. package/dist/templates/modules/nostr-identity/lib/nostr-utils.ts +30 -0
  114. package/dist/templates/modules/nostr-identity/module.json +15 -0
  115. package/dist/templates/modules/payment-gated-content/app/api/payment-gate/invoice/route.ts +25 -0
  116. package/dist/templates/modules/payment-gated-content/app/api/payment-gate/verify/route.ts +39 -0
  117. package/dist/templates/modules/payment-gated-content/app/demo/payment-gated/article/page.tsx +71 -0
  118. package/dist/templates/modules/payment-gated-content/app/demo/payment-gated/page.tsx +134 -0
  119. package/dist/templates/modules/payment-gated-content/components/payment-gated/PayGate.tsx +267 -0
  120. package/dist/templates/modules/payment-gated-content/lib/payment-gate.ts +195 -0
  121. package/dist/templates/modules/payment-gated-content/lib/payment-store.ts +104 -0
  122. package/dist/templates/modules/payment-gated-content/module.json +24 -0
  123. package/dist/templates/modules/payment-gated-content/proxy.ts +27 -0
  124. package/dist/templates/modules/webln-payments/app/demo/webln/page.tsx +176 -0
  125. package/dist/templates/modules/webln-payments/components/webln/InvoiceCard.tsx +170 -0
  126. package/dist/templates/modules/webln-payments/components/webln/PayButton.tsx +92 -0
  127. package/dist/templates/modules/webln-payments/components/webln/PaymentHistory.tsx +102 -0
  128. package/dist/templates/modules/webln-payments/hooks/__tests__/usePaymentFlow.test.tsx +182 -0
  129. package/dist/templates/modules/webln-payments/hooks/usePaymentFlow.ts +100 -0
  130. package/dist/templates/modules/webln-payments/lib/payment-history.ts +75 -0
  131. package/dist/templates/modules/webln-payments/module.json +17 -0
  132. package/dist/templates/modules/webln-payments/tests/e2e/webln-payment.spec.ts +41 -0
  133. package/package.json +29 -0
@@ -0,0 +1,5 @@
1
+ # App
2
+ NEXT_PUBLIC_APP_NAME="{{PROJECT_NAME}}"
3
+
4
+ # Add module-specific variables below
5
+ # (populated by CLI based on selected modules)
@@ -0,0 +1,25 @@
1
+ import Link from 'next/link';
2
+
3
+ export default function DemoPage() {
4
+ return (
5
+ <main
6
+ className="mx-auto min-h-dvh w-full max-w-[390px] px-4 pt-6 pb-20"
7
+ style={{ paddingBottom: 'max(5rem, env(safe-area-inset-bottom, 20px))' }}
8
+ >
9
+ <Link
10
+ href="/"
11
+ className="mb-6 inline-block text-xs text-[var(--color-text-muted)] transition-opacity duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] hover:opacity-80"
12
+ >
13
+ ← back
14
+ </Link>
15
+ <div className="space-y-4">
16
+ <h1 className="font-[family-name:var(--font-display)] text-2xl font-bold leading-tight text-[var(--color-text)]">
17
+ Demos
18
+ </h1>
19
+ <p className="max-w-[75ch] text-sm leading-[1.65] text-[var(--color-text-muted)]">
20
+ Module demos appear here after selection during project creation.
21
+ </p>
22
+ </div>
23
+ </main>
24
+ );
25
+ }
@@ -0,0 +1,95 @@
1
+ @import "tailwindcss";
2
+ @import "@tailwindcss/typography";
3
+
4
+ @theme {
5
+ /* Fedi palette */
6
+ --color-bg: #0a0a0a;
7
+ --color-surface: #141414;
8
+ --color-surface-2: #1c1c1c;
9
+ --color-border: rgba(255, 255, 255, 0.08);
10
+ --color-accent: #ff6b35;
11
+ --color-accent-dim: rgba(255, 107, 53, 0.15);
12
+ --color-text: #f0eee9;
13
+ --color-text-muted: #8a8880;
14
+ --color-text-subtle: #4a4845;
15
+ --color-success: #3d8b5f;
16
+ --color-destructive: #dc2626;
17
+
18
+ /* shadcn semantic aliases → Fedi tokens */
19
+ --color-background: var(--color-bg);
20
+ --color-foreground: var(--color-text);
21
+ --color-primary: var(--color-accent);
22
+ --color-primary-foreground: var(--color-text);
23
+ --color-secondary: var(--color-surface-2);
24
+ --color-secondary-foreground: var(--color-text);
25
+ --color-muted: var(--color-surface);
26
+ --color-muted-foreground: var(--color-text-muted);
27
+ --color-input: var(--color-border);
28
+ --color-ring: var(--color-accent);
29
+ --color-card: var(--color-surface);
30
+ --color-card-foreground: var(--color-text);
31
+ --color-popover: var(--color-surface);
32
+ --color-popover-foreground: var(--color-text);
33
+
34
+ --radius-sm: 4px;
35
+ --radius-md: 8px;
36
+ --radius-lg: 12px;
37
+ --radius: 8px;
38
+ }
39
+
40
+ @theme inline {
41
+ --font-display: var(--font-display, 'Bricolage Grotesque', system-ui, sans-serif);
42
+ --font-body: var(--font-body, 'DM Sans', system-ui, sans-serif);
43
+ --font-mono: var(--font-mono, 'JetBrains Mono', ui-monospace, monospace);
44
+ }
45
+
46
+ @layer base {
47
+ *,
48
+ *::before,
49
+ *::after {
50
+ box-sizing: border-box;
51
+ }
52
+
53
+ html {
54
+ background-color: var(--color-bg);
55
+ color: var(--color-text);
56
+ font-family: var(--font-body);
57
+ font-size: 16px;
58
+ line-height: 1.65;
59
+ -webkit-font-smoothing: antialiased;
60
+ -moz-osx-font-smoothing: grayscale;
61
+ }
62
+
63
+ h1,
64
+ h2,
65
+ h3,
66
+ h4,
67
+ h5,
68
+ h6 {
69
+ font-family: var(--font-display);
70
+ color: var(--color-text);
71
+ line-height: 1.25;
72
+ }
73
+
74
+ code,
75
+ kbd,
76
+ samp,
77
+ pre {
78
+ font-family: var(--font-mono);
79
+ }
80
+
81
+ :focus-visible {
82
+ outline: 2px solid var(--color-accent);
83
+ outline-offset: 2px;
84
+ }
85
+ }
86
+
87
+ @keyframes fedi-spin {
88
+ to {
89
+ transform: rotate(360deg);
90
+ }
91
+ }
92
+
93
+ @utility animate-fedi-spin {
94
+ animation: fedi-spin 0.75s linear infinite;
95
+ }
@@ -0,0 +1,39 @@
1
+ import type { Metadata } from 'next';
2
+ import { Bricolage_Grotesque, DM_Sans, JetBrains_Mono } from 'next/font/google';
3
+ import { Providers } from '../components/providers';
4
+ import { FediDevToolbar } from '../components/FediDevToolbar/FediDevToolbar';
5
+ import './globals.css';
6
+
7
+ const display = Bricolage_Grotesque({
8
+ subsets: ['latin'],
9
+ variable: '--font-display',
10
+ display: 'swap',
11
+ });
12
+ const body = DM_Sans({
13
+ subsets: ['latin'],
14
+ variable: '--font-body',
15
+ display: 'swap',
16
+ });
17
+ const mono = JetBrains_Mono({
18
+ subsets: ['latin'],
19
+ variable: '--font-mono',
20
+ display: 'swap',
21
+ });
22
+
23
+ export const metadata: Metadata = {
24
+ title: '{{PROJECT_NAME}}',
25
+ description: 'A Fedi Mini App',
26
+ };
27
+
28
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
29
+ return (
30
+ <html lang="en" className={`${display.variable} ${body.variable} ${mono.variable}`}>
31
+ <body className={`${body.className} antialiased`}>
32
+ <Providers>
33
+ {children}
34
+ {process.env.NODE_ENV === 'development' && <FediDevToolbar />}
35
+ </Providers>
36
+ </body>
37
+ </html>
38
+ );
39
+ }
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+
3
+ import Link from 'next/link';
4
+ import { useWebLN } from '../lib/webln';
5
+ import { useNostr } from '../lib/nostr';
6
+ import { cn } from '../lib/utils';
7
+
8
+ function ConnectionStatus() {
9
+ const { provider: weblnProvider, isLoading: weblnLoading } = useWebLN();
10
+ const { provider: nostrProvider, isLoading: nostrLoading } = useNostr();
11
+
12
+ return (
13
+ <div className="flex flex-wrap gap-2">
14
+ <StatusPill label="WebLN" active={!!weblnProvider} loading={weblnLoading} />
15
+ <StatusPill label="Nostr" active={!!nostrProvider} loading={nostrLoading} />
16
+ </div>
17
+ );
18
+ }
19
+
20
+ function StatusPill({
21
+ label,
22
+ active,
23
+ loading,
24
+ }: {
25
+ label: string;
26
+ active: boolean;
27
+ loading: boolean;
28
+ }) {
29
+ return (
30
+ <span
31
+ className={cn(
32
+ 'inline-flex items-center gap-1.5 rounded-sm px-2 py-0.5 text-xs font-mono',
33
+ loading
34
+ ? 'bg-[var(--color-surface-2)] text-[var(--color-text-subtle)]'
35
+ : active
36
+ ? 'bg-[var(--color-accent-dim)] text-[var(--color-accent)]'
37
+ : 'bg-[var(--color-surface-2)] text-[var(--color-text-subtle)]',
38
+ )}
39
+ >
40
+ <span
41
+ className={cn(
42
+ 'size-1.5 rounded-full',
43
+ loading
44
+ ? 'bg-current opacity-40'
45
+ : active
46
+ ? 'bg-[var(--color-accent)]'
47
+ : 'bg-[var(--color-text-subtle)]',
48
+ )}
49
+ />
50
+ {label}
51
+ </span>
52
+ );
53
+ }
54
+
55
+ export default function HomePage() {
56
+ return (
57
+ <main
58
+ className="mx-auto min-h-dvh w-full max-w-[390px] px-4 pt-6 pb-20"
59
+ style={{ paddingBottom: 'max(5rem, env(safe-area-inset-bottom, 20px))' }}
60
+ >
61
+ <div className="flex flex-col gap-6">
62
+ <div className="space-y-2">
63
+ <h1 className="font-[family-name:var(--font-display)] text-3xl font-bold leading-tight text-[var(--color-text)]">
64
+ {{PROJECT_NAME}}
65
+ </h1>
66
+ <p className="max-w-[75ch] text-sm leading-[1.65] text-[var(--color-text-muted)]">
67
+ Your mini app is running. Explore the demo pages to see WebLN payments and Nostr
68
+ identity in action.
69
+ </p>
70
+ </div>
71
+
72
+ <ConnectionStatus />
73
+
74
+ <Link
75
+ href="/demo"
76
+ className="inline-flex h-10 items-center justify-center rounded-md bg-[var(--color-accent)] px-5 text-sm font-semibold text-[var(--color-text)] transition-opacity duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] hover:opacity-90 active:opacity-80"
77
+ >
78
+ Explore demos →
79
+ </Link>
80
+ </div>
81
+ </main>
82
+ );
83
+ }
@@ -0,0 +1,170 @@
1
+ 'use client';
2
+ import { useContext, useState } from 'react';
3
+ import { FediDevContext } from '../providers';
4
+
5
+ export function FediDevToolbar() {
6
+ const [expanded, setExpanded] = useState(false);
7
+ const dev = useContext(FediDevContext);
8
+
9
+ if (!dev) return null;
10
+
11
+ return (
12
+ <div
13
+ style={{
14
+ position: 'fixed',
15
+ bottom: '16px',
16
+ right: '16px',
17
+ zIndex: 9999,
18
+ display: 'flex',
19
+ flexDirection: 'column',
20
+ alignItems: 'flex-end',
21
+ gap: '8px',
22
+ }}
23
+ >
24
+ {expanded && (
25
+ <div
26
+ style={{
27
+ background: 'var(--color-surface)',
28
+ border: '1px solid var(--color-border)',
29
+ borderRadius: 'var(--radius-lg)',
30
+ padding: '12px',
31
+ minWidth: '200px',
32
+ display: 'flex',
33
+ flexDirection: 'column',
34
+ gap: '10px',
35
+ }}
36
+ >
37
+ <Toggle
38
+ label="Mock WebLN"
39
+ value={dev.mockWebLNEnabled}
40
+ onChange={dev.setMockWebLNEnabled}
41
+ />
42
+ <Toggle
43
+ label="Mock Nostr"
44
+ value={dev.mockNostrEnabled}
45
+ onChange={dev.setMockNostrEnabled}
46
+ />
47
+ <button
48
+ onClick={() => dev.setPaymentShouldFail(!dev.paymentShouldFail)}
49
+ style={{
50
+ background: dev.paymentShouldFail ? 'var(--color-accent)' : 'var(--color-surface-2)',
51
+ color: dev.paymentShouldFail ? 'var(--color-primary-foreground)' : 'var(--color-text-muted)',
52
+ border: '1px solid var(--color-border)',
53
+ borderRadius: 'var(--radius-sm)',
54
+ padding: '4px 8px',
55
+ fontSize: '11px',
56
+ fontFamily: 'var(--font-mono)',
57
+ cursor: 'pointer',
58
+ textAlign: 'left',
59
+ }}
60
+ >
61
+ {dev.paymentShouldFail ? '⚡ fail: ON' : '⚡ fail: OFF'}
62
+ </button>
63
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
64
+ <label style={{ fontSize: '10px', color: 'var(--color-text-subtle)', fontFamily: 'var(--font-mono)' }}>
65
+ mock npub
66
+ </label>
67
+ <input
68
+ type="text"
69
+ placeholder="npub1..."
70
+ value={dev.mockNpub}
71
+ onChange={(e) => dev.setMockNpub(e.target.value)}
72
+ style={{
73
+ background: 'var(--color-surface-2)',
74
+ border: '1px solid var(--color-border)',
75
+ borderRadius: 'var(--radius-sm)',
76
+ padding: '4px 6px',
77
+ fontSize: '11px',
78
+ fontFamily: 'var(--font-mono)',
79
+ color: 'var(--color-text)',
80
+ width: '100%',
81
+ outline: 'none',
82
+ }}
83
+ />
84
+ </div>
85
+ </div>
86
+ )}
87
+
88
+ <button
89
+ onClick={() => setExpanded((v) => !v)}
90
+ title="Fedi Dev"
91
+ style={{
92
+ background: expanded ? 'var(--color-accent)' : 'var(--color-surface)',
93
+ border: '1px solid var(--color-border)',
94
+ borderRadius: 'var(--radius-md)',
95
+ padding: '6px 10px',
96
+ cursor: 'pointer',
97
+ display: 'flex',
98
+ alignItems: 'center',
99
+ gap: '6px',
100
+ }}
101
+ >
102
+ <span style={{ fontSize: '14px' }}>⚙</span>
103
+ <span
104
+ style={{
105
+ fontSize: '10px',
106
+ fontFamily: 'var(--font-mono)',
107
+ color: expanded ? 'var(--color-primary-foreground)' : 'var(--color-text-muted)',
108
+ letterSpacing: '0.05em',
109
+ }}
110
+ >
111
+ Fedi Dev
112
+ </span>
113
+ </button>
114
+ </div>
115
+ );
116
+ }
117
+
118
+ function Toggle({
119
+ label,
120
+ value,
121
+ onChange,
122
+ }: {
123
+ label: string;
124
+ value: boolean;
125
+ onChange: (v: boolean) => void;
126
+ }) {
127
+ return (
128
+ <div
129
+ style={{
130
+ display: 'flex',
131
+ justifyContent: 'space-between',
132
+ alignItems: 'center',
133
+ gap: '8px',
134
+ }}
135
+ >
136
+ <span style={{ fontSize: '11px', fontFamily: 'var(--font-mono)', color: 'var(--color-text-muted)' }}>
137
+ {label}
138
+ </span>
139
+ <button
140
+ onClick={() => onChange(!value)}
141
+ style={{
142
+ background: value ? 'var(--color-accent)' : 'var(--color-surface-2)',
143
+ border: '1px solid var(--color-border)',
144
+ borderRadius: '999px',
145
+ width: '32px',
146
+ height: '18px',
147
+ cursor: 'pointer',
148
+ position: 'relative',
149
+ transition: 'background 0.15s',
150
+ flexShrink: 0,
151
+ }}
152
+ aria-checked={value}
153
+ role="switch"
154
+ >
155
+ <span
156
+ style={{
157
+ position: 'absolute',
158
+ top: '2px',
159
+ left: value ? '14px' : '2px',
160
+ width: '12px',
161
+ height: '12px',
162
+ background: 'var(--color-text)',
163
+ borderRadius: '50%',
164
+ transition: 'left 0.15s',
165
+ }}
166
+ />
167
+ </button>
168
+ </div>
169
+ );
170
+ }
@@ -0,0 +1,63 @@
1
+ 'use client';
2
+ import { createContext, useContext, useMemo, useState, type ReactNode } from 'react';
3
+ import { WebLNProvider, MockWebLNProvider } from '../lib/webln';
4
+ import { NostrProvider, MockNostrProvider } from '../lib/nostr';
5
+
6
+ interface FediDevState {
7
+ mockWebLNEnabled: boolean;
8
+ setMockWebLNEnabled: (v: boolean) => void;
9
+ mockNostrEnabled: boolean;
10
+ setMockNostrEnabled: (v: boolean) => void;
11
+ paymentShouldFail: boolean;
12
+ setPaymentShouldFail: (v: boolean) => void;
13
+ mockNpub: string;
14
+ setMockNpub: (v: string) => void;
15
+ }
16
+
17
+ export const FediDevContext = createContext<FediDevState | null>(null);
18
+
19
+ export function useFediDev(): FediDevState {
20
+ const ctx = useContext(FediDevContext);
21
+ if (!ctx) throw new Error('useFediDev must be used within <Providers>');
22
+ return ctx;
23
+ }
24
+
25
+ export function Providers({ children }: { children: ReactNode }) {
26
+ const isDev = process.env.NODE_ENV === 'development';
27
+
28
+ const [mockWebLNEnabled, setMockWebLNEnabled] = useState(true);
29
+ const [mockNostrEnabled, setMockNostrEnabled] = useState(true);
30
+ const [paymentShouldFail, setPaymentShouldFail] = useState(false);
31
+ const [mockNpub, setMockNpub] = useState('');
32
+
33
+ const mockWebLN = useMemo(() => {
34
+ if (!isDev || !mockWebLNEnabled) return undefined;
35
+ return new MockWebLNProvider({ shouldFail: paymentShouldFail });
36
+ }, [isDev, mockWebLNEnabled, paymentShouldFail]);
37
+
38
+ const mockNostr = useMemo(() => {
39
+ if (!isDev || !mockNostrEnabled) return undefined;
40
+ const provider = new MockNostrProvider();
41
+ if (mockNpub) {
42
+ provider.getPublicKey = async () => mockNpub;
43
+ }
44
+ return provider;
45
+ }, [isDev, mockNostrEnabled, mockNpub]);
46
+
47
+ const devState: FediDevState = {
48
+ mockWebLNEnabled, setMockWebLNEnabled,
49
+ mockNostrEnabled, setMockNostrEnabled,
50
+ paymentShouldFail, setPaymentShouldFail,
51
+ mockNpub, setMockNpub,
52
+ };
53
+
54
+ return (
55
+ <FediDevContext.Provider value={devState}>
56
+ <WebLNProvider mockProvider={mockWebLN}>
57
+ <NostrProvider mockProvider={mockNostr}>
58
+ {children}
59
+ </NostrProvider>
60
+ </WebLNProvider>
61
+ </FediDevContext.Provider>
62
+ );
63
+ }
@@ -0,0 +1,10 @@
1
+ import { createEnv } from '@t3-oss/env-nextjs';
2
+ import { z } from 'zod';
3
+ export const env = createEnv({
4
+ server: { NODE_ENV: z.enum(['development', 'production', 'test']) },
5
+ client: { NEXT_PUBLIC_APP_NAME: z.string().default('My Fedi App') },
6
+ runtimeEnv: {
7
+ NODE_ENV: process.env.NODE_ENV,
8
+ NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME,
9
+ },
10
+ });
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+ import { useEffect, useState } from 'react';
3
+ import type { FediInternal } from '../lib/fedi-types';
4
+
5
+ type V2 = Extract<FediInternal, { version: 2 }>;
6
+
7
+ interface FediInternalHook {
8
+ isAvailable: boolean;
9
+ version: 0 | 1 | 2 | null;
10
+ getInstalledMiniApps: V2['getInstalledMiniApps'] | null;
11
+ installMiniApp: V2['installMiniApp'] | null;
12
+ }
13
+
14
+ const NULL_STATE: FediInternalHook = {
15
+ isAvailable: false,
16
+ version: null,
17
+ getInstalledMiniApps: null,
18
+ installMiniApp: null,
19
+ };
20
+
21
+ export function useFediInternal(): FediInternalHook {
22
+ const [state, setState] = useState<FediInternalHook>(NULL_STATE);
23
+
24
+ useEffect(() => {
25
+ const fi = window.fediInternal;
26
+ if (!fi) return;
27
+
28
+ if (fi.version === 2) {
29
+ setState({
30
+ isAvailable: true,
31
+ version: 2,
32
+ getInstalledMiniApps: fi.getInstalledMiniApps.bind(fi),
33
+ installMiniApp: fi.installMiniApp.bind(fi),
34
+ });
35
+ } else {
36
+ setState({ isAvailable: true, version: fi.version, getInstalledMiniApps: null, installMiniApp: null });
37
+ }
38
+ }, []);
39
+
40
+ return state;
41
+ }
@@ -0,0 +1,96 @@
1
+ // WebLN types
2
+ export interface RequestInvoiceArgs {
3
+ amount?: string | number;
4
+ defaultAmount?: string | number;
5
+ minimumAmount?: string | number;
6
+ maximumAmount?: string | number;
7
+ defaultMemo?: string;
8
+ }
9
+
10
+ export interface RequestInvoiceResponse {
11
+ paymentRequest: string;
12
+ }
13
+
14
+ export interface SendPaymentResponse {
15
+ preimage: string;
16
+ }
17
+
18
+ export interface KeysendArgs {
19
+ destination: string;
20
+ amount: string | number;
21
+ customRecords?: Record<string, string>;
22
+ }
23
+
24
+ export interface SignMessageResponse {
25
+ message: string;
26
+ signature: string;
27
+ }
28
+
29
+ export interface GetInfoResponse {
30
+ node: {
31
+ alias: string;
32
+ pubkey: string;
33
+ color: string;
34
+ };
35
+ methods: string[];
36
+ }
37
+
38
+ export interface WebLNProvider {
39
+ enable(): Promise<void>;
40
+ getInfo(): Promise<GetInfoResponse>;
41
+ sendPayment(paymentRequest: string): Promise<SendPaymentResponse>;
42
+ makeInvoice(args: RequestInvoiceArgs | string | number): Promise<RequestInvoiceResponse>;
43
+ signMessage(message: string): Promise<SignMessageResponse>;
44
+ verifyMessage(signature: string, message: string): Promise<void>;
45
+ sendKeysend(args: KeysendArgs): Promise<SendPaymentResponse>;
46
+ }
47
+
48
+ // Nostr types
49
+ export interface NostrEvent {
50
+ id: string;
51
+ pubkey: string;
52
+ created_at: number;
53
+ kind: number;
54
+ tags: string[][];
55
+ content: string;
56
+ sig: string;
57
+ }
58
+
59
+ export type UnsignedNostrEvent = Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>;
60
+
61
+ export interface Nip04 {
62
+ encrypt(pubkey: string, plaintext: string): Promise<string>;
63
+ decrypt(pubkey: string, ciphertext: string): Promise<string>;
64
+ }
65
+
66
+ export interface NostrProvider {
67
+ getPublicKey(): Promise<string>;
68
+ signEvent(event: UnsignedNostrEvent): Promise<NostrEvent>;
69
+ getRelays(): Promise<Record<string, { read: boolean; write: boolean }>>;
70
+ nip04: Nip04;
71
+ }
72
+
73
+ // Fedi internal types
74
+ type FediInternalV0 = { version: 0 };
75
+ type FediInternalV1 = { version: 1 };
76
+ type FediInternalV2 = {
77
+ version: 2;
78
+ getInstalledMiniApps(): Promise<Array<{ url: string }>>;
79
+ installMiniApp(miniApp: {
80
+ id: string;
81
+ title: string;
82
+ url: string;
83
+ imageUrl?: string | null;
84
+ description?: string;
85
+ }): Promise<void>;
86
+ };
87
+
88
+ export type FediInternal = FediInternalV0 | FediInternalV1 | FediInternalV2;
89
+
90
+ declare global {
91
+ interface Window {
92
+ webln?: WebLNProvider;
93
+ nostr?: NostrProvider;
94
+ fediInternal?: FediInternal;
95
+ }
96
+ }
@@ -0,0 +1,18 @@
1
+ export function isInFedi(): boolean {
2
+ return typeof window !== 'undefined' && typeof window.webln !== 'undefined';
3
+ }
4
+
5
+ export function getFediInternalVersion(): 0 | 1 | 2 | null {
6
+ if (typeof window === 'undefined' || !window.fediInternal) return null;
7
+ return window.fediInternal.version as 0 | 1 | 2;
8
+ }
9
+
10
+ export function formatSats(sats: number): string {
11
+ if (sats >= 100_000) return `${(sats / 100_000_000).toFixed(6)} BTC`;
12
+ return `${sats.toLocaleString()} sats`;
13
+ }
14
+
15
+ export function shortenNpub(npub: string): string {
16
+ if (npub.length < 16) return npub;
17
+ return `${npub.slice(0, 8)}...${npub.slice(-4)}`;
18
+ }