@geenius/adapters 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 (151) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
  5. package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  6. package/.github/dependabot.yml +11 -0
  7. package/.github/workflows/ci.yml +23 -0
  8. package/.github/workflows/release.yml +29 -0
  9. package/.nvmrc +1 -0
  10. package/.project/ACCOUNT.yaml +4 -0
  11. package/.project/IDEAS.yaml +7 -0
  12. package/.project/PROJECT.yaml +11 -0
  13. package/.project/ROADMAP.yaml +15 -0
  14. package/CHANGELOG.md +11 -0
  15. package/CODE_OF_CONDUCT.md +16 -0
  16. package/CONTRIBUTING.md +26 -0
  17. package/LICENSE +21 -0
  18. package/README.md +202 -0
  19. package/SECURITY.md +15 -0
  20. package/SUPPORT.md +8 -0
  21. package/package.json +51 -0
  22. package/packages/convex/README.md +64 -0
  23. package/packages/convex/package.json +42 -0
  24. package/packages/convex/src/adapter.ts +39 -0
  25. package/packages/convex/src/index.ts +19 -0
  26. package/packages/convex/src/mutations.ts +142 -0
  27. package/packages/convex/src/queries.ts +106 -0
  28. package/packages/convex/src/schema.ts +54 -0
  29. package/packages/convex/src/types.ts +20 -0
  30. package/packages/convex/tsconfig.json +11 -0
  31. package/packages/convex/tsup.config.ts +10 -0
  32. package/packages/react/README.md +1 -0
  33. package/packages/react/package.json +45 -0
  34. package/packages/react/src/components/AdapterCard.tsx +49 -0
  35. package/packages/react/src/components/AdapterConfigForm.tsx +118 -0
  36. package/packages/react/src/components/AdapterList.tsx +84 -0
  37. package/packages/react/src/components/AdapterStatusBadge.tsx +30 -0
  38. package/packages/react/src/components/index.ts +4 -0
  39. package/packages/react/src/hooks/index.ts +75 -0
  40. package/packages/react/src/index.tsx +44 -0
  41. package/packages/react/src/pages/AdapterDetailPage.tsx +133 -0
  42. package/packages/react/src/pages/AdaptersPage.tsx +111 -0
  43. package/packages/react/src/pages/index.ts +2 -0
  44. package/packages/react/src/provider/AdapterProvider.tsx +115 -0
  45. package/packages/react/src/provider/index.ts +2 -0
  46. package/packages/react/tsconfig.json +18 -0
  47. package/packages/react/tsup.config.ts +10 -0
  48. package/packages/react-css/README.md +1 -0
  49. package/packages/react-css/package.json +44 -0
  50. package/packages/react-css/src/adapters.css +1576 -0
  51. package/packages/react-css/src/components/AdapterCard.tsx +34 -0
  52. package/packages/react-css/src/components/AdapterConfigForm.tsx +63 -0
  53. package/packages/react-css/src/components/AdapterList.tsx +40 -0
  54. package/packages/react-css/src/components/AdapterStatusBadge.tsx +21 -0
  55. package/packages/react-css/src/components/index.ts +4 -0
  56. package/packages/react-css/src/hooks/index.ts +75 -0
  57. package/packages/react-css/src/index.tsx +25 -0
  58. package/packages/react-css/src/pages/AdapterDetailPage.tsx +133 -0
  59. package/packages/react-css/src/pages/AdaptersPage.tsx +111 -0
  60. package/packages/react-css/src/pages/index.ts +2 -0
  61. package/packages/react-css/src/provider/AdapterProvider.tsx +115 -0
  62. package/packages/react-css/src/provider/index.ts +2 -0
  63. package/packages/react-css/src/styles.css +494 -0
  64. package/packages/react-css/tsconfig.json +19 -0
  65. package/packages/react-css/tsup.config.ts +2 -0
  66. package/packages/shared/README.md +1 -0
  67. package/packages/shared/package.json +39 -0
  68. package/packages/shared/src/__tests__/adapters.test.ts +545 -0
  69. package/packages/shared/src/admin/index.ts +2 -0
  70. package/packages/shared/src/admin/interface.ts +34 -0
  71. package/packages/shared/src/admin/localStorage.ts +109 -0
  72. package/packages/shared/src/ai/anthropic.ts +123 -0
  73. package/packages/shared/src/ai/cloudflare-gateway.ts +130 -0
  74. package/packages/shared/src/ai/gemini.ts +181 -0
  75. package/packages/shared/src/ai/index.ts +14 -0
  76. package/packages/shared/src/ai/interface.ts +11 -0
  77. package/packages/shared/src/ai/localStorage.ts +78 -0
  78. package/packages/shared/src/ai/ollama.ts +143 -0
  79. package/packages/shared/src/ai/openai.ts +120 -0
  80. package/packages/shared/src/ai/vercel-ai.ts +101 -0
  81. package/packages/shared/src/auth/better-auth.ts +118 -0
  82. package/packages/shared/src/auth/clerk.ts +151 -0
  83. package/packages/shared/src/auth/convex-auth.ts +125 -0
  84. package/packages/shared/src/auth/index.ts +10 -0
  85. package/packages/shared/src/auth/interface.ts +17 -0
  86. package/packages/shared/src/auth/localStorage.ts +125 -0
  87. package/packages/shared/src/auth/supabase-auth.ts +136 -0
  88. package/packages/shared/src/config.ts +57 -0
  89. package/packages/shared/src/constants.ts +122 -0
  90. package/packages/shared/src/db/convex.ts +146 -0
  91. package/packages/shared/src/db/index.ts +10 -0
  92. package/packages/shared/src/db/interface.ts +13 -0
  93. package/packages/shared/src/db/localStorage.ts +91 -0
  94. package/packages/shared/src/db/mongodb.ts +125 -0
  95. package/packages/shared/src/db/neon.ts +171 -0
  96. package/packages/shared/src/db/supabase.ts +158 -0
  97. package/packages/shared/src/index.ts +117 -0
  98. package/packages/shared/src/payments/index.ts +4 -0
  99. package/packages/shared/src/payments/interface.ts +11 -0
  100. package/packages/shared/src/payments/localStorage.ts +81 -0
  101. package/packages/shared/src/payments/stripe.ts +177 -0
  102. package/packages/shared/src/storage/convex.ts +113 -0
  103. package/packages/shared/src/storage/index.ts +14 -0
  104. package/packages/shared/src/storage/interface.ts +11 -0
  105. package/packages/shared/src/storage/localStorage.ts +95 -0
  106. package/packages/shared/src/storage/minio.ts +47 -0
  107. package/packages/shared/src/storage/r2.ts +123 -0
  108. package/packages/shared/src/storage/s3.ts +128 -0
  109. package/packages/shared/src/storage/supabase-storage.ts +116 -0
  110. package/packages/shared/src/storage/uploadthing.ts +126 -0
  111. package/packages/shared/src/styles/adapters.css +494 -0
  112. package/packages/shared/src/tier-gate.ts +119 -0
  113. package/packages/shared/src/types.ts +162 -0
  114. package/packages/shared/tsconfig.json +18 -0
  115. package/packages/shared/tsup.config.ts +9 -0
  116. package/packages/shared/vitest.config.ts +14 -0
  117. package/packages/solidjs/README.md +1 -0
  118. package/packages/solidjs/package.json +44 -0
  119. package/packages/solidjs/src/components/AdapterCard.tsx +24 -0
  120. package/packages/solidjs/src/components/AdapterConfigForm.tsx +54 -0
  121. package/packages/solidjs/src/components/AdapterList.tsx +28 -0
  122. package/packages/solidjs/src/components/AdapterStatusBadge.tsx +20 -0
  123. package/packages/solidjs/src/components/index.ts +4 -0
  124. package/packages/solidjs/src/index.tsx +17 -0
  125. package/packages/solidjs/src/pages/AdapterDetailPage.tsx +38 -0
  126. package/packages/solidjs/src/pages/AdaptersPage.tsx +39 -0
  127. package/packages/solidjs/src/pages/index.ts +2 -0
  128. package/packages/solidjs/src/primitives/index.ts +78 -0
  129. package/packages/solidjs/src/provider/AdapterProvider.tsx +62 -0
  130. package/packages/solidjs/src/provider/index.ts +2 -0
  131. package/packages/solidjs/tsconfig.json +20 -0
  132. package/packages/solidjs/tsup.config.ts +10 -0
  133. package/packages/solidjs-css/README.md +1 -0
  134. package/packages/solidjs-css/package.json +43 -0
  135. package/packages/solidjs-css/src/adapters.css +1576 -0
  136. package/packages/solidjs-css/src/components/AdapterCard.tsx +43 -0
  137. package/packages/solidjs-css/src/components/AdapterConfigForm.tsx +119 -0
  138. package/packages/solidjs-css/src/components/AdapterList.tsx +68 -0
  139. package/packages/solidjs-css/src/components/AdapterStatusBadge.tsx +24 -0
  140. package/packages/solidjs-css/src/components/index.ts +8 -0
  141. package/packages/solidjs-css/src/index.tsx +30 -0
  142. package/packages/solidjs-css/src/pages/AdapterDetailPage.tsx +107 -0
  143. package/packages/solidjs-css/src/pages/AdaptersPage.tsx +94 -0
  144. package/packages/solidjs-css/src/pages/index.ts +4 -0
  145. package/packages/solidjs-css/src/primitives/index.ts +1 -0
  146. package/packages/solidjs-css/src/provider/AdapterProvider.tsx +61 -0
  147. package/packages/solidjs-css/src/provider/index.ts +2 -0
  148. package/packages/solidjs-css/tsconfig.json +20 -0
  149. package/packages/solidjs-css/tsup.config.ts +2 -0
  150. package/pnpm-workspace.yaml +2 -0
  151. package/tsconfig.json +17 -0
@@ -0,0 +1,30 @@
1
+ // @geenius-adapters/react — AdapterStatusBadge
2
+
3
+ import type { AdapterStatusType } from '@geenius-adapters/shared'
4
+
5
+ const STATUS_STYLES: Record<AdapterStatusType, { bg: string; text: string; dot: string; label: string }> = {
6
+ connected: { bg: 'bg-emerald-500/10', text: 'text-emerald-400', dot: 'bg-emerald-400', label: 'Connected' },
7
+ disconnected: { bg: 'bg-white/5', text: 'text-white/40', dot: 'bg-white/30', label: 'Disconnected' },
8
+ error: { bg: 'bg-red-500/10', text: 'text-red-400', dot: 'bg-red-400', label: 'Error' },
9
+ initializing: { bg: 'bg-amber-500/10', text: 'text-amber-400', dot: 'bg-amber-400', label: 'Initializing' },
10
+ }
11
+
12
+ export interface AdapterStatusBadgeProps {
13
+ status: AdapterStatusType
14
+ showLabel?: boolean
15
+ size?: 'sm' | 'md'
16
+ className?: string
17
+ }
18
+
19
+ export function AdapterStatusBadge({ status, showLabel = true, size = 'sm', className = '' }: AdapterStatusBadgeProps) {
20
+ const s = STATUS_STYLES[status]
21
+ const dotSize = size === 'sm' ? 'h-1.5 w-1.5' : 'h-2 w-2'
22
+ const textSize = size === 'sm' ? 'text-[0.625rem]' : 'text-xs'
23
+
24
+ return (
25
+ <span className={`inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 ${s.bg} ${className}`}>
26
+ <span className={`${dotSize} rounded-full ${s.dot} ${status === 'initializing' ? 'animate-pulse' : ''}`} />
27
+ {showLabel && <span className={`${textSize} font-semibold uppercase tracking-wider ${s.text}`}>{s.label}</span>}
28
+ </span>
29
+ )
30
+ }
@@ -0,0 +1,4 @@
1
+ export { AdapterStatusBadge, type AdapterStatusBadgeProps } from './AdapterStatusBadge'
2
+ export { AdapterCard, type AdapterCardProps } from './AdapterCard'
3
+ export { AdapterList, type AdapterListProps } from './AdapterList'
4
+ export { AdapterConfigForm, type AdapterConfigFormProps } from './AdapterConfigForm'
@@ -0,0 +1,75 @@
1
+ // @geenius-adapters/react — Hooks
2
+
3
+ import { useMemo } from 'react'
4
+ import { useAdapterContext } from '../provider/AdapterProvider'
5
+ import type {
6
+ AuthAdapter, DbAdapter, PaymentsAdapter, AiAdapter, FileStorageAdapter, AdminAdapter,
7
+ AdapterDomain, AdapterStatusInfo,
8
+ } from '@geenius-adapters/shared'
9
+
10
+ // ─── Domain-specific hooks ───────────────────────────────────────────────────
11
+
12
+ /** Access the database adapter */
13
+ export function useDb(): DbAdapter {
14
+ return useAdapterContext().getAdapter('db')
15
+ }
16
+
17
+ /** Access the auth adapter */
18
+ export function useAuth(): AuthAdapter {
19
+ return useAdapterContext().getAdapter('auth')
20
+ }
21
+
22
+ /** Access the payments adapter */
23
+ export function usePayments(): PaymentsAdapter {
24
+ return useAdapterContext().getAdapter('payments')
25
+ }
26
+
27
+ /** Access the AI adapter */
28
+ export function useAi(): AiAdapter {
29
+ return useAdapterContext().getAdapter('ai')
30
+ }
31
+
32
+ /** Access the file storage adapter */
33
+ export function useStorage(): FileStorageAdapter {
34
+ return useAdapterContext().getAdapter('storage')
35
+ }
36
+
37
+ /** Access the admin adapter */
38
+ export function useAdmin(): AdminAdapter {
39
+ return useAdapterContext().getAdapter('admin')
40
+ }
41
+
42
+ // ─── Status hooks ────────────────────────────────────────────────────────────
43
+
44
+ /** Get all adapter statuses */
45
+ export function useAdapterStatuses(): Record<AdapterDomain, AdapterStatusInfo> {
46
+ return useAdapterContext().statuses
47
+ }
48
+
49
+ /** Get status for a specific adapter domain */
50
+ export function useAdapterStatus(domain: AdapterDomain): AdapterStatusInfo {
51
+ return useAdapterContext().statuses[domain]
52
+ }
53
+
54
+ /** Check if adapter is ready */
55
+ export function useIsAdapterReady(domain: AdapterDomain): boolean {
56
+ return useAdapterContext().isReady(domain)
57
+ }
58
+
59
+ // ─── Full adapters hook ──────────────────────────────────────────────────────
60
+
61
+ /** Access all adapters at once */
62
+ export function useAdapters() {
63
+ const ctx = useAdapterContext()
64
+ return useMemo(() => ({
65
+ db: ctx.adapters.db,
66
+ auth: ctx.adapters.auth,
67
+ payments: ctx.adapters.payments,
68
+ ai: ctx.adapters.ai,
69
+ storage: ctx.adapters.storage,
70
+ admin: ctx.adapters.admin,
71
+ statuses: ctx.statuses,
72
+ isLoading: ctx.isLoading,
73
+ isReady: ctx.isReady,
74
+ }), [ctx])
75
+ }
@@ -0,0 +1,44 @@
1
+ // @geenius-adapters/react — Barrel export
2
+
3
+ /**
4
+ * @geenius-adapters/react
5
+ *
6
+ * React hooks, components, and pages for the Geenius adapter ecosystem.
7
+ * Wraps all 6 adapter domains (DB, Auth, AI, Storage, Payments, Admin)
8
+ * behind a unified provider/hook API.
9
+ */
10
+
11
+ // Re-export shared types for convenience
12
+ export type {
13
+ AuthUser, AuthSession, OAuthProvider, ListOptions, QueryFilter,
14
+ Plan, Subscription, CheckoutParams, CheckoutResult,
15
+ ChatMessage, AiOptions, ChatResponse, StoredFile,
16
+ AdapterDomain, DomainAdapterConfig, AdapterConfig,
17
+ AdapterStatusType, AdapterStatusInfo, AdapterRegistryEntry,
18
+ AuthAdapter, DbAdapter, PaymentsAdapter, AiAdapter, FileStorageAdapter,
19
+ AdminAdapter, AdminMetrics, ManagedUser,
20
+ } from '@geenius-adapters/shared'
21
+
22
+ export {
23
+ ADAPTER_DOMAINS, DOMAIN_LABELS, DOMAIN_ICONS, DOMAIN_DESCRIPTIONS,
24
+ getProvidersForDomain, getProviderMeta,
25
+ configureAdapters, getAdapterConfig, isAdaptersConfigured,
26
+ } from '@geenius-adapters/shared'
27
+
28
+ // Provider
29
+ export { AdapterProvider, useAdapterContext } from './provider'
30
+ export type { AdapterProviderProps, AdapterSet, AdapterContextValue } from './provider'
31
+
32
+ // Hooks
33
+ export {
34
+ useDb, useAuth, usePayments, useAi, useStorage, useAdmin,
35
+ useAdapters, useAdapterStatuses, useAdapterStatus, useIsAdapterReady,
36
+ } from './hooks'
37
+
38
+ // Components
39
+ export { AdapterStatusBadge, AdapterCard, AdapterList, AdapterConfigForm } from './components'
40
+ export type { AdapterStatusBadgeProps, AdapterCardProps, AdapterListProps, AdapterConfigFormProps } from './components'
41
+
42
+ // Pages
43
+ export { AdaptersPage, AdapterDetailPage } from './pages'
44
+ export type { AdaptersPageProps, AdapterDetailPageProps } from './pages'
@@ -0,0 +1,133 @@
1
+ // @geenius-adapters/react — AdapterDetailPage
2
+
3
+ import { useState } from 'react'
4
+ import { useAdapters, useAdapterStatus } from '../hooks'
5
+ import { AdapterStatusBadge } from '../components/AdapterStatusBadge'
6
+ import { AdapterConfigForm } from '../components/AdapterConfigForm'
7
+ import type { AdapterDomain } from '@geenius-adapters/shared'
8
+ import { DOMAIN_LABELS, DOMAIN_ICONS, DOMAIN_DESCRIPTIONS, getProvidersForDomain } from '@geenius-adapters/shared'
9
+
10
+ export interface AdapterDetailPageProps {
11
+ domain: AdapterDomain
12
+ onBack?: () => void
13
+ className?: string
14
+ }
15
+
16
+ export function AdapterDetailPage({ domain, onBack, className = '' }: AdapterDetailPageProps) {
17
+ const { isLoading } = useAdapters()
18
+ const status = useAdapterStatus(domain)
19
+ const [isEditing, setIsEditing] = useState(false)
20
+ const providers = getProvidersForDomain(domain)
21
+
22
+ const label = DOMAIN_LABELS[domain]
23
+ const icon = DOMAIN_ICONS[domain]
24
+ const desc = DOMAIN_DESCRIPTIONS[domain]
25
+
26
+ if (isLoading) {
27
+ return (
28
+ <div className={`max-w-3xl mx-auto space-y-6 ${className}`}>
29
+ <div className="animate-pulse space-y-4">
30
+ <div className="h-6 w-32 rounded bg-white/10" />
31
+ <div className="h-40 rounded-2xl bg-white/[0.03]" />
32
+ <div className="h-60 rounded-2xl bg-white/[0.03]" />
33
+ </div>
34
+ </div>
35
+ )
36
+ }
37
+
38
+ return (
39
+ <div className={`max-w-3xl mx-auto space-y-8 ${className}`}>
40
+ {/* Back navigation */}
41
+ {onBack && (
42
+ <button onClick={onBack} className="flex items-center gap-1.5 text-sm text-white/30 hover:text-white/60 transition-colors">
43
+ <span>←</span> Back to Adapters
44
+ </button>
45
+ )}
46
+
47
+ {/* Header */}
48
+ <div className="flex items-start gap-5">
49
+ <div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-white/5 text-3xl">{icon}</div>
50
+ <div className="flex-1">
51
+ <div className="flex items-center gap-3">
52
+ <h1 className="text-2xl font-extrabold text-white/95 tracking-tight">{label}</h1>
53
+ <AdapterStatusBadge status={status.status} size="md" />
54
+ </div>
55
+ <p className="mt-1 text-sm text-white/40">{desc}</p>
56
+ </div>
57
+ </div>
58
+
59
+ {/* Status Card */}
60
+ <div className="rounded-2xl border border-white/10 bg-white/[0.03] p-6 backdrop-blur-sm">
61
+ <h2 className="text-xs font-semibold uppercase tracking-wider text-white/30 mb-4">Current Status</h2>
62
+ <div className="grid gap-4 sm:grid-cols-3">
63
+ <div>
64
+ <p className="text-[0.625rem] text-white/30 uppercase">Provider</p>
65
+ <p className="mt-0.5 text-sm font-semibold text-white/80">{status.provider !== 'none' ? status.provider : '—'}</p>
66
+ </div>
67
+ <div>
68
+ <p className="text-[0.625rem] text-white/30 uppercase">Status</p>
69
+ <p className="mt-0.5"><AdapterStatusBadge status={status.status} /></p>
70
+ </div>
71
+ <div>
72
+ <p className="text-[0.625rem] text-white/30 uppercase">Last Checked</p>
73
+ <p className="mt-0.5 text-sm text-white/60">{status.lastCheckedAt ? new Date(status.lastCheckedAt).toLocaleTimeString() : '—'}</p>
74
+ </div>
75
+ </div>
76
+ {status.error && (
77
+ <div className="mt-4 rounded-lg border border-red-500/20 bg-red-500/10 px-4 py-3 text-sm text-red-400">
78
+ <span className="font-semibold">Error:</span> {status.error}
79
+ </div>
80
+ )}
81
+ {status.latency && (
82
+ <div className="mt-4">
83
+ <p className="text-[0.625rem] text-white/30 uppercase mb-1">Latency</p>
84
+ <div className="h-2 w-full rounded-full bg-white/5 overflow-hidden">
85
+ <div className={`h-full rounded-full transition-all ${status.latency < 100 ? 'bg-emerald-400' : status.latency < 500 ? 'bg-amber-400' : 'bg-red-400'}`}
86
+ style={{ width: `${Math.min(100, (status.latency / 1000) * 100)}%` }} />
87
+ </div>
88
+ <p className="mt-1 text-xs text-white/40">{status.latency}ms</p>
89
+ </div>
90
+ )}
91
+ </div>
92
+
93
+ {/* Available Providers */}
94
+ <div className="rounded-2xl border border-white/10 bg-white/[0.03] p-6 backdrop-blur-sm">
95
+ <div className="flex items-center justify-between mb-4">
96
+ <h2 className="text-xs font-semibold uppercase tracking-wider text-white/30">Available Providers</h2>
97
+ <span className="text-xs text-white/20">{providers.length} providers</span>
98
+ </div>
99
+ <div className="divide-y divide-white/5">
100
+ {providers.map(p => (
101
+ <div key={p.id} className="flex items-center justify-between py-3 first:pt-0 last:pb-0">
102
+ <div>
103
+ <p className="text-sm font-semibold text-white/80">{p.name}</p>
104
+ <p className="text-xs text-white/30">{p.description}</p>
105
+ </div>
106
+ <span className={`self-start rounded px-2 py-0.5 text-[0.625rem] font-bold uppercase ${
107
+ p.tier === 'pronto' ? 'bg-emerald-500/10 text-emerald-400' : p.tier === 'mvp' ? 'bg-blue-500/10 text-blue-400' : 'bg-purple-500/10 text-purple-400'
108
+ }`}>{p.tier}</span>
109
+ </div>
110
+ ))}
111
+ </div>
112
+ </div>
113
+
114
+ {/* Configuration */}
115
+ <div>
116
+ {isEditing ? (
117
+ <AdapterConfigForm
118
+ domain={domain}
119
+ onSave={async () => setIsEditing(false)}
120
+ onCancel={() => setIsEditing(false)}
121
+ />
122
+ ) : (
123
+ <button
124
+ onClick={() => setIsEditing(true)}
125
+ className="w-full rounded-2xl border border-dashed border-white/10 bg-white/[0.01] p-6 text-center text-sm text-white/30 hover:border-white/20 hover:text-white/50 transition-all"
126
+ >
127
+ ⚙️ Configure {label} Adapter
128
+ </button>
129
+ )}
130
+ </div>
131
+ </div>
132
+ )
133
+ }
@@ -0,0 +1,111 @@
1
+ // @geenius-adapters/react — AdaptersPage
2
+
3
+ import { useState } from 'react'
4
+ import { useAdapters } from '../hooks'
5
+ import { AdapterList } from '../components/AdapterList'
6
+ import { AdapterConfigForm } from '../components/AdapterConfigForm'
7
+ import { AdapterStatusBadge } from '../components/AdapterStatusBadge'
8
+ import { ADAPTER_DOMAINS, ADAPTER_STATUSES, DOMAIN_LABELS } from '@geenius-adapters/shared'
9
+ import type { AdapterDomain, AdapterStatusType } from '@geenius-adapters/shared'
10
+
11
+ export interface AdaptersPageProps {
12
+ onNavigateDetail?: (domain: AdapterDomain) => void
13
+ className?: string
14
+ }
15
+
16
+ export function AdaptersPage({ onNavigateDetail, className = '' }: AdaptersPageProps) {
17
+ const { statuses, isLoading } = useAdapters()
18
+ const [selectedDomain, setSelectedDomain] = useState<AdapterDomain | null>(null)
19
+ const [filterStatus, setFilterStatus] = useState<AdapterStatusType | undefined>()
20
+
21
+ // Status summary counts
22
+ const counts = {
23
+ connected: ADAPTER_DOMAINS.filter(d => statuses[d]?.status === 'connected').length,
24
+ error: ADAPTER_DOMAINS.filter(d => statuses[d]?.status === 'error').length,
25
+ total: ADAPTER_DOMAINS.length,
26
+ }
27
+
28
+ if (isLoading) {
29
+ return (
30
+ <div className={`max-w-4xl mx-auto space-y-6 ${className}`}>
31
+ <div className="animate-pulse space-y-4">
32
+ <div className="h-8 w-48 rounded bg-white/10" />
33
+ <div className="h-4 w-72 rounded bg-white/5" />
34
+ <div className="grid gap-3 sm:grid-cols-3">{[1,2,3].map(i => <div key={i} className="h-20 rounded-2xl bg-white/[0.03]" />)}</div>
35
+ <div className="grid gap-3 sm:grid-cols-2">{[1,2,3,4].map(i => <div key={i} className="h-28 rounded-2xl bg-white/[0.03]" />)}</div>
36
+ </div>
37
+ </div>
38
+ )
39
+ }
40
+
41
+ return (
42
+ <div className={`max-w-4xl mx-auto space-y-8 ${className}`}>
43
+ {/* Header */}
44
+ <div>
45
+ <h1 className="text-2xl font-extrabold text-white/95 tracking-tight">Adapters</h1>
46
+ <p className="mt-1 text-sm text-white/40">Manage your infrastructure adapters — database, auth, AI, storage, payments, and admin.</p>
47
+ </div>
48
+
49
+ {/* Status Summary */}
50
+ <div className="grid gap-3 sm:grid-cols-3">
51
+ <div className="rounded-2xl border border-white/10 bg-white/[0.03] p-4 backdrop-blur-sm">
52
+ <p className="text-xs font-semibold uppercase tracking-wider text-white/30">Total</p>
53
+ <p className="mt-1 text-2xl font-extrabold text-white/90">{counts.total}</p>
54
+ <p className="text-[0.625rem] text-white/30">adapter domains</p>
55
+ </div>
56
+ <div className="rounded-2xl border border-emerald-500/10 bg-emerald-500/[0.03] p-4 backdrop-blur-sm">
57
+ <p className="text-xs font-semibold uppercase tracking-wider text-emerald-400/60">Connected</p>
58
+ <p className="mt-1 text-2xl font-extrabold text-emerald-400">{counts.connected}</p>
59
+ <p className="text-[0.625rem] text-emerald-400/40">active adapters</p>
60
+ </div>
61
+ {counts.error > 0 && (
62
+ <div className="rounded-2xl border border-red-500/10 bg-red-500/[0.03] p-4 backdrop-blur-sm">
63
+ <p className="text-xs font-semibold uppercase tracking-wider text-red-400/60">Errors</p>
64
+ <p className="mt-1 text-2xl font-extrabold text-red-400">{counts.error}</p>
65
+ <p className="text-[0.625rem] text-red-400/40">need attention</p>
66
+ </div>
67
+ )}
68
+ </div>
69
+
70
+ {/* Filters */}
71
+ <div className="flex items-center gap-2">
72
+ <button
73
+ onClick={() => setFilterStatus(undefined)}
74
+ className={`rounded-lg px-3 py-1.5 text-xs font-semibold transition-all ${!filterStatus ? 'bg-white/10 text-white/80' : 'text-white/30 hover:text-white/50'}`}
75
+ >All</button>
76
+ {ADAPTER_STATUSES.map(s => (
77
+ <button
78
+ key={s}
79
+ onClick={() => setFilterStatus(s)}
80
+ className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-semibold transition-all ${filterStatus === s ? 'bg-white/10 text-white/80' : 'text-white/30 hover:text-white/50'}`}
81
+ >
82
+ <AdapterStatusBadge status={s} showLabel={false} size="sm" />
83
+ {s}
84
+ </button>
85
+ ))}
86
+ </div>
87
+
88
+ {/* Adapter Grid */}
89
+ <AdapterList
90
+ onSelect={(domain) => {
91
+ if (onNavigateDetail) { onNavigateDetail(domain) }
92
+ else { setSelectedDomain(domain) }
93
+ }}
94
+ filterStatus={filterStatus}
95
+ />
96
+
97
+ {/* Inline Config Modal */}
98
+ {selectedDomain && (
99
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" onClick={() => setSelectedDomain(null)}>
100
+ <div className="max-w-lg w-full mx-4" onClick={e => e.stopPropagation()}>
101
+ <AdapterConfigForm
102
+ domain={selectedDomain}
103
+ onSave={async () => { setSelectedDomain(null) }}
104
+ onCancel={() => setSelectedDomain(null)}
105
+ />
106
+ </div>
107
+ </div>
108
+ )}
109
+ </div>
110
+ )
111
+ }
@@ -0,0 +1,2 @@
1
+ export { AdaptersPage, type AdaptersPageProps } from './AdaptersPage'
2
+ export { AdapterDetailPage, type AdapterDetailPageProps } from './AdapterDetailPage'
@@ -0,0 +1,115 @@
1
+ // @geenius-adapters/react — Provider
2
+
3
+ 'use client'
4
+
5
+ import { createContext, useContext, useState, useCallback, useEffect, useMemo, type ReactNode } from 'react'
6
+ import type {
7
+ AuthAdapter, DbAdapter, PaymentsAdapter, AiAdapter, FileStorageAdapter, AdminAdapter,
8
+ AdapterDomain, AdapterStatusType, AdapterStatusInfo,
9
+ } from '@geenius-adapters/shared'
10
+ import { ADAPTER_DOMAINS, DOMAIN_LABELS } from '@geenius-adapters/shared'
11
+
12
+ // ─── Adapter Set ─────────────────────────────────────────────────────────────
13
+
14
+ export interface AdapterSet {
15
+ db?: DbAdapter
16
+ auth?: AuthAdapter
17
+ payments?: PaymentsAdapter
18
+ ai?: AiAdapter
19
+ storage?: FileStorageAdapter
20
+ admin?: AdminAdapter
21
+ }
22
+
23
+ // ─── Context ─────────────────────────────────────────────────────────────────
24
+
25
+ export interface AdapterContextValue {
26
+ adapters: AdapterSet
27
+ statuses: Record<AdapterDomain, AdapterStatusInfo>
28
+ getAdapter: <K extends keyof AdapterSet>(domain: K) => NonNullable<AdapterSet[K]>
29
+ isReady: (domain: AdapterDomain) => boolean
30
+ isLoading: boolean
31
+ }
32
+
33
+ const AdapterContext = createContext<AdapterContextValue | null>(null)
34
+
35
+ // ─── Provider ────────────────────────────────────────────────────────────────
36
+
37
+ export interface AdapterProviderProps {
38
+ adapters: AdapterSet
39
+ children: ReactNode
40
+ /** Check adapter health on mount */
41
+ healthCheck?: boolean
42
+ }
43
+
44
+ export function AdapterProvider({ adapters, children, healthCheck = false }: AdapterProviderProps) {
45
+ const [isLoading, setIsLoading] = useState(healthCheck)
46
+ const [statuses, setStatuses] = useState<Record<AdapterDomain, AdapterStatusInfo>>(() => {
47
+ const initial = {} as Record<AdapterDomain, AdapterStatusInfo>
48
+ for (const domain of ADAPTER_DOMAINS) {
49
+ const adapter = adapters[domain as keyof AdapterSet]
50
+ initial[domain] = {
51
+ domain,
52
+ provider: adapter ? 'configured' : 'none',
53
+ status: adapter ? 'connected' : 'disconnected',
54
+ lastCheckedAt: Date.now(),
55
+ }
56
+ }
57
+ return initial
58
+ })
59
+
60
+ useEffect(() => {
61
+ if (!healthCheck) return
62
+ setIsLoading(true)
63
+ // Simple health check — verify adapters are callable
64
+ const checks = ADAPTER_DOMAINS.map(async (domain) => {
65
+ const adapter = adapters[domain as keyof AdapterSet]
66
+ if (!adapter) return
67
+ try {
68
+ setStatuses(prev => ({ ...prev, [domain]: { ...prev[domain], status: 'initializing' } }))
69
+ // Basic connectivity test for each domain
70
+ if (domain === 'auth' && (adapter as AuthAdapter).getSession) {
71
+ await (adapter as AuthAdapter).getSession()
72
+ }
73
+ setStatuses(prev => ({
74
+ ...prev,
75
+ [domain]: { ...prev[domain], status: 'connected' as AdapterStatusType, lastCheckedAt: Date.now() },
76
+ }))
77
+ } catch (e) {
78
+ setStatuses(prev => ({
79
+ ...prev,
80
+ [domain]: {
81
+ ...prev[domain],
82
+ status: 'error' as AdapterStatusType,
83
+ error: e instanceof Error ? e.message : 'Health check failed',
84
+ lastCheckedAt: Date.now(),
85
+ },
86
+ }))
87
+ }
88
+ })
89
+ Promise.allSettled(checks).finally(() => setIsLoading(false))
90
+ }, [adapters, healthCheck])
91
+
92
+ const getAdapter = useCallback(<K extends keyof AdapterSet>(domain: K): NonNullable<AdapterSet[K]> => {
93
+ const adapter = adapters[domain]
94
+ if (!adapter) throw new Error(`${DOMAIN_LABELS[domain as AdapterDomain] ?? domain} adapter not configured in <AdapterProvider>`)
95
+ return adapter as NonNullable<AdapterSet[K]>
96
+ }, [adapters])
97
+
98
+ const isReady = useCallback((domain: AdapterDomain) => {
99
+ return !!adapters[domain as keyof AdapterSet] && statuses[domain]?.status === 'connected'
100
+ }, [adapters, statuses])
101
+
102
+ const value = useMemo<AdapterContextValue>(() => ({
103
+ adapters, statuses, getAdapter, isReady, isLoading,
104
+ }), [adapters, statuses, getAdapter, isReady, isLoading])
105
+
106
+ return <AdapterContext.Provider value={value}>{children}</AdapterContext.Provider>
107
+ }
108
+
109
+ // ─── Context Hook ────────────────────────────────────────────────────────────
110
+
111
+ export function useAdapterContext(): AdapterContextValue {
112
+ const ctx = useContext(AdapterContext)
113
+ if (!ctx) throw new Error('useAdapterContext must be used within <AdapterProvider>')
114
+ return ctx
115
+ }
@@ -0,0 +1,2 @@
1
+ export { AdapterProvider, useAdapterContext } from './AdapterProvider'
2
+ export type { AdapterProviderProps, AdapterSet, AdapterContextValue } from './AdapterProvider'
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "resolveJsonModule": true,
10
+ "isolatedModules": true,
11
+ "target": "ES2022",
12
+ "module": "ESNext",
13
+ "moduleResolution": "bundler"
14
+ },
15
+ "include": [
16
+ "src"
17
+ ]
18
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.tsx'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ clean: true,
8
+ sourcemap: true,
9
+ external: ['react'],
10
+ })
@@ -0,0 +1 @@
1
+ # ✦ @geenius-adapters/react-css\n\n> Geenius Adapters — React hooks and providers (vanilla CSS variant)\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius-adapters/react-css\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-adapters/react-css';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@geenius-adapters/react-css",
3
+ "version": "0.1.0",
4
+ "description": "Geenius Adapters — React hooks and providers (vanilla CSS variant)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "lint": "tsc --noEmit",
19
+ "clean": "rm -rf dist"
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18.0.0"
29
+ },
30
+ "dependencies": {
31
+ "@geenius-adapters/shared": "workspace:*"
32
+ },
33
+ "devDependencies": {
34
+ "@types/react": "^19.0.0",
35
+ "react": "^19.2.4",
36
+ "tsup": "^8.5.1",
37
+ "typescript": "~6.0.2"
38
+ },
39
+ "author": "Antigravity HQ",
40
+ "license": "MIT",
41
+ "engines": {
42
+ "node": ">=20.0.0"
43
+ }
44
+ }