@ezcoder.dev/sdk 1.1.0 → 1.2.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/dist/analytics/index.js +7 -5
- package/dist/analytics/index.js.map +1 -1
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +25 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/chunk-CQKYANAW.js +44 -0
- package/dist/chunk-CQKYANAW.js.map +1 -0
- package/dist/{chunk-GPF4AYNG.js → chunk-HJ2EIZ4S.js} +2 -2
- package/dist/{chunk-2WG4O4J2.js → chunk-I2YGB7Z6.js} +14 -50
- package/dist/chunk-I2YGB7Z6.js.map +1 -0
- package/dist/chunk-LIUE7M7K.js +72 -0
- package/dist/chunk-LIUE7M7K.js.map +1 -0
- package/dist/{chunk-AWU47M6N.js → chunk-QHB7LGCA.js} +91 -8
- package/dist/chunk-QHB7LGCA.js.map +1 -0
- package/dist/{chunk-7VGYFCQC.js → chunk-R4MDAYFO.js} +7 -5
- package/dist/{chunk-7VGYFCQC.js.map → chunk-R4MDAYFO.js.map} +1 -1
- package/dist/cms/index.js +4 -2
- package/dist/cms/index.js.map +1 -1
- package/dist/cron/index.d.ts +17 -0
- package/dist/cron/index.js +26 -12
- package/dist/cron/index.js.map +1 -1
- package/dist/database/index.js +4 -3
- package/dist/email/index.d.ts +18 -0
- package/dist/email/index.js +27 -12
- package/dist/email/index.js.map +1 -1
- package/dist/index.d.ts +167 -1
- package/dist/index.js +263 -7
- package/dist/index.js.map +1 -1
- package/dist/notifications/index.d.ts +27 -2
- package/dist/notifications/index.js +53 -18
- package/dist/notifications/index.js.map +1 -1
- package/dist/payments/index.js +6 -4
- package/dist/payments/index.js.map +1 -1
- package/dist/roles/index.js +6 -4
- package/dist/roles/index.js.map +1 -1
- package/dist/storage/index.js +7 -5
- package/dist/storage/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2WG4O4J2.js.map +0 -1
- package/dist/chunk-AWU47M6N.js.map +0 -1
- /package/dist/{chunk-GPF4AYNG.js.map → chunk-HJ2EIZ4S.js.map} +0 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/manifest-consumer.ts","../src/useEzCoderManifest.ts","../src/index.ts"],"sourcesContent":["/**\r\n * @module manifest-consumer\r\n * @description Runtime + build-time consumer of the EzCoder Capability Manifest.\r\n *\r\n * Track B (B7) — the user-app SDK reads the project's Capability Manifest so\r\n * it knows which feature modules are actually required (auth, storage,\r\n * payments, email, cron, notifications, database) and which env refs to\r\n * expect. The SDK historically auto-registered everything regardless of\r\n * project needs; this consumer adds *opt-in* awareness without breaking\r\n * existing consumers.\r\n *\r\n * Failure mode is deliberately permissive: any fetch/parse error falls\r\n * through to \"all modules considered required\" so a flaky network blip on\r\n * the user's deployed site never silently disables a feature.\r\n */\r\n\r\nimport { env } from './core/config';\r\n\r\n// ─── Public surface ───────────────────────────────────────────────────────\r\n\r\n/**\r\n * The seven modules the SDK can register. Mirrors the keys of\r\n * `service_requirements` in lib/manifest/schema.ts plus `database` derived\r\n * from the `database` requirement block.\r\n */\r\nexport interface RequiredModules {\r\n auth: boolean;\r\n storage: boolean;\r\n payments: boolean;\r\n email: boolean;\r\n cron: boolean;\r\n database: boolean;\r\n notifications: boolean;\r\n}\r\n\r\n/**\r\n * Public + (publicly-visible names of) secret env refs. The server-side\r\n * endpoint deliberately strips `secret` before returning, but we keep the\r\n * field on the consumer type as an empty array for forward-compat — once a\r\n * privileged consumer path exists it can populate it.\r\n */\r\nexport interface EnvRefs {\r\n public: ReadonlyArray<string>;\r\n secret: ReadonlyArray<string>;\r\n}\r\n\r\n/**\r\n * The shape the SDK actually consumes. A trimmed projection of the full\r\n * Capability Manifest; the server endpoint enforces the projection so the\r\n * SDK can never see secret-ref names even if it asks for them.\r\n */\r\nexport interface SanitizedManifest {\r\n manifest_version: number;\r\n project_id: string;\r\n service_requirements: {\r\n database?: { provider: string; rls_required: boolean; tables: ReadonlyArray<string> };\r\n auth?: { provider: string; flows: ReadonlyArray<string> };\r\n storage?: { buckets: ReadonlyArray<string> };\r\n payments?: { mode: string };\r\n email?: { provider: string };\r\n cron?: { jobs: ReadonlyArray<{ name: string; schedule: string }> };\r\n };\r\n env_refs: { public: ReadonlyArray<string> };\r\n validation_gates: ReadonlyArray<string>;\r\n}\r\n\r\n// ─── Module-level cache (keyed by projectId) ──────────────────────────────\r\n\r\nconst manifestCache: Map<string, EzCoderManifest> = new Map();\r\nconst inflightLoads: Map<string, Promise<EzCoderManifest>> = new Map();\r\n\r\n/**\r\n * Default-on RequiredModules — used as the safe fallback when no manifest\r\n * is available. Preserves the SDK's historical behaviour (everything wired)\r\n * so a missing/failed manifest never silently disables a feature in\r\n * already-deployed user apps.\r\n */\r\nconst PERMISSIVE_DEFAULT_MODULES: Readonly<RequiredModules> = Object.freeze({\r\n auth: true,\r\n storage: true,\r\n payments: true,\r\n email: true,\r\n cron: true,\r\n database: true,\r\n notifications: true,\r\n});\r\n\r\n// ─── EzCoderManifest class ────────────────────────────────────────────────\r\n\r\n/**\r\n * Wraps a sanitized manifest payload and exposes module/env-ref queries.\r\n * Instances are immutable — `manifest` is captured at construction time and\r\n * never mutated. Use the static `load()` / `loadFromBuildOutput()` factories\r\n * rather than calling `new` directly so the page-lifetime cache is honoured.\r\n */\r\nexport class EzCoderManifest {\r\n private readonly manifest: SanitizedManifest | null;\r\n private readonly source: 'runtime' | 'build' | 'fallback';\r\n\r\n constructor(manifest: SanitizedManifest | null, source: 'runtime' | 'build' | 'fallback') {\r\n this.manifest = manifest;\r\n this.source = source;\r\n }\r\n\r\n /**\r\n * Fetch the manifest at runtime from the platform API. Returns a\r\n * cached instance for the lifetime of the page; concurrent callers share\r\n * a single in-flight fetch. Falls back to a permissive (all-modules-on)\r\n * instance on any error.\r\n */\r\n static async load(): Promise<EzCoderManifest> {\r\n const projectId = env.EZC_PROJECT_ID;\r\n const apiUrl = env.EZCODER_API_URL;\r\n const publicToken = readPublicToken();\r\n\r\n if (!projectId || !apiUrl) {\r\n return new EzCoderManifest(null, 'fallback');\r\n }\r\n\r\n const cacheKey = `${projectId}::${apiUrl}`;\r\n const cached = manifestCache.get(cacheKey);\r\n if (cached) return cached;\r\n\r\n const inflight = inflightLoads.get(cacheKey);\r\n if (inflight) return inflight;\r\n\r\n const loadPromise = fetchManifest(apiUrl, publicToken)\r\n .then((m) => {\r\n const instance = new EzCoderManifest(m, 'runtime');\r\n manifestCache.set(cacheKey, instance);\r\n return instance;\r\n })\r\n .catch((err: unknown) => {\r\n const message = err instanceof Error ? err.message : 'unknown';\r\n if (typeof console !== 'undefined') {\r\n console.warn(`[EzCoder SDK] manifest fetch failed (${message}); falling back to permissive mode.`);\r\n }\r\n const fallback = new EzCoderManifest(null, 'fallback');\r\n manifestCache.set(cacheKey, fallback);\r\n return fallback;\r\n })\r\n .finally(() => {\r\n inflightLoads.delete(cacheKey);\r\n });\r\n\r\n inflightLoads.set(cacheKey, loadPromise);\r\n return loadPromise;\r\n }\r\n\r\n /**\r\n * Read a manifest written by the build-time codegen step (see\r\n * scripts/fetch-build-manifest.js). Used by SSG callers that want to\r\n * avoid a runtime network round-trip. Returns null when no build-time\r\n * manifest is present — callers should fall back to `load()`.\r\n *\r\n * Implementation note: we deliberately avoid a static `import` because\r\n * (a) the file may not exist, which would be a hard module-resolution\r\n * error, and (b) browser bundles must not pull in the JSON. We probe\r\n * `process` to detect Node, then read the file from disk via the eval'd\r\n * dynamic require — the eval indirection keeps bundlers from following\r\n * the path at build time.\r\n */\r\n static loadFromBuildOutput(): EzCoderManifest | null {\r\n try {\r\n if (typeof process === 'undefined' || !process.versions?.node) {\r\n return null;\r\n }\r\n // Avoid a static `import('fs')` because (a) the file may not exist,\r\n // (b) bundlers would attempt to resolve it for browser targets, and\r\n // (c) we want a Node-only branch the bundler can drop. Construct the\r\n // require lookup via the Function constructor so the literal \"require\"\r\n // identifier never appears in any statically-analyzable position.\r\n const getRequire = new Function(\r\n 'return typeof require === \"function\" ? require : null;',\r\n ) as () => ((id: string) => unknown) | null;\r\n const dynamicRequire = getRequire();\r\n if (!dynamicRequire) return null;\r\n const fs = dynamicRequire('fs') as { readFileSync: (p: string, enc: string) => string };\r\n const path = dynamicRequire('path') as { resolve: (...segments: string[]) => string };\r\n const cwd = process.cwd();\r\n const full = path.resolve(cwd, 'node_modules', '.ezcoder', 'manifest.json');\r\n const raw = fs.readFileSync(full, 'utf8');\r\n const payload: unknown = JSON.parse(raw);\r\n const parsed = parseManifestPayload(payload);\r\n if (!parsed) return null;\r\n return new EzCoderManifest(parsed, 'build');\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Returns the boolean enablement map for each SDK module. When no\r\n * manifest is loaded, returns the permissive default (everything on) so\r\n * legacy behaviour is preserved.\r\n */\r\n getRequiredModules(): RequiredModules {\r\n if (!this.manifest) {\r\n return { ...PERMISSIVE_DEFAULT_MODULES };\r\n }\r\n const sr = this.manifest.service_requirements;\r\n return {\r\n auth: Boolean(sr.auth),\r\n storage: Boolean(sr.storage),\r\n payments: Boolean(sr.payments),\r\n email: Boolean(sr.email),\r\n cron: Boolean(sr.cron),\r\n database: Boolean(sr.database),\r\n // notifications is not in service_requirements v0; derive from email\r\n // OR auth so the existing notifications module behaves sensibly.\r\n notifications: Boolean(sr.email || sr.auth),\r\n };\r\n }\r\n\r\n /**\r\n * Returns the env refs declared by the manifest. The `secret` array is\r\n * always empty when the manifest came from the public SDK endpoint —\r\n * server-only refs are stripped before transit (see pages/api/sdk/manifest.js).\r\n */\r\n getEnvRefs(): EnvRefs {\r\n if (!this.manifest) {\r\n return { public: [], secret: [] };\r\n }\r\n return {\r\n public: this.manifest.env_refs.public,\r\n secret: [],\r\n };\r\n }\r\n\r\n /**\r\n * Convenience: is a given module required by this app?\r\n */\r\n has(module: keyof RequiredModules): boolean {\r\n return this.getRequiredModules()[module];\r\n }\r\n\r\n /**\r\n * Where the manifest was loaded from. Useful for debugging and for\r\n * surface-level telemetry (`fallback` means we never got the real one).\r\n */\r\n getSource(): 'runtime' | 'build' | 'fallback' {\r\n return this.source;\r\n }\r\n\r\n /**\r\n * Test-only helper: wipe the page-lifetime cache. Never call this in\r\n * production code.\r\n */\r\n static __resetCacheForTests(): void {\r\n manifestCache.clear();\r\n inflightLoads.clear();\r\n }\r\n}\r\n\r\n// ─── Internals ────────────────────────────────────────────────────────────\r\n\r\nfunction readPublicToken(): string {\r\n // The PUBLIC token is intentionally distinct from the server-only secret\r\n // key. We accept both Vite and Next env shapes; both must be safe to\r\n // expose to the browser (i.e. namespaced as PUBLIC).\r\n const fromVite = safeEnv('VITE_EZC_PROJECT_TOKEN_PUBLIC');\r\n if (fromVite) return fromVite;\r\n const fromNext = safeEnv('NEXT_PUBLIC_EZC_PROJECT_TOKEN_PUBLIC');\r\n if (fromNext) return fromNext;\r\n return safeEnv('EZC_PROJECT_TOKEN_PUBLIC');\r\n}\r\n\r\nfunction safeEnv(key: string): string {\r\n try {\r\n if (typeof import.meta !== 'undefined' && import.meta.env) {\r\n const v = (import.meta.env as Record<string, unknown>)[key];\r\n if (typeof v === 'string') return v;\r\n }\r\n } catch {\r\n // import.meta unavailable in this runtime — fall through.\r\n }\r\n try {\r\n if (typeof process !== 'undefined' && process.env) {\r\n return process.env[key] || '';\r\n }\r\n } catch {\r\n // process unavailable — fall through.\r\n }\r\n return '';\r\n}\r\n\r\nasync function fetchManifest(apiUrl: string, publicToken: string): Promise<SanitizedManifest> {\r\n const headers: Record<string, string> = {\r\n 'Accept': 'application/json',\r\n };\r\n if (publicToken) {\r\n headers['X-EzCoder-Public-Token'] = publicToken;\r\n }\r\n const res = await fetch(`${apiUrl}/api/sdk/manifest`, {\r\n method: 'GET',\r\n headers,\r\n // Browsers will fold the 5-minute cache header automatically; this hint\r\n // is here for non-browser runtimes that respect it.\r\n cache: 'default',\r\n });\r\n if (!res.ok) {\r\n throw new Error(`HTTP ${res.status}`);\r\n }\r\n const body: unknown = await res.json();\r\n const parsed = parseManifestPayload(body);\r\n if (!parsed) {\r\n throw new Error('manifest payload failed shape validation');\r\n }\r\n return parsed;\r\n}\r\n\r\n/**\r\n * Permissive boundary parser. We deliberately do NOT pull Zod into the SDK\r\n * bundle — instead we run a lightweight shape check and let unknown fields\r\n * pass through. The server has already validated the manifest with Zod\r\n * before sending; this is just defense-in-depth against a malformed\r\n * response (e.g. an HTML error page).\r\n */\r\nfunction parseManifestPayload(input: unknown): SanitizedManifest | null {\r\n if (!isRecord(input)) return null;\r\n const projectId = input.project_id;\r\n const serviceReqs = input.service_requirements;\r\n const envRefs = input.env_refs;\r\n const gates = input.validation_gates;\r\n if (typeof projectId !== 'string' || projectId.length === 0) return null;\r\n if (!isRecord(serviceReqs)) return null;\r\n if (!isRecord(envRefs) || !Array.isArray(envRefs.public)) return null;\r\n if (!Array.isArray(gates)) return null;\r\n const manifestVersion =\r\n typeof input.manifest_version === 'number' ? input.manifest_version : 0;\r\n return {\r\n manifest_version: manifestVersion,\r\n project_id: projectId,\r\n service_requirements: serviceReqs as SanitizedManifest['service_requirements'],\r\n env_refs: { public: envRefs.public.filter((v): v is string => typeof v === 'string') },\r\n validation_gates: gates.filter((v): v is string => typeof v === 'string'),\r\n };\r\n}\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value);\r\n}\r\n","/**\r\n * @module useEzCoderManifest\r\n * @description React hook that exposes the loaded Capability Manifest to\r\n * components so they can branch on whether a feature module is required by\r\n * the current project.\r\n *\r\n * Importing this file pulls `react` as a peer dep — kept in a dedicated\r\n * file so SSR / non-React entries can consume `manifest-consumer.ts`\r\n * without touching React.\r\n */\r\n\r\nimport { useEffect, useState } from 'react';\r\nimport { EzCoderManifest, type RequiredModules } from './manifest-consumer';\r\n\r\nexport interface UseEzCoderManifestResult {\r\n manifest: EzCoderManifest | null;\r\n modules: RequiredModules | null;\r\n loading: boolean;\r\n source: 'runtime' | 'build' | 'fallback' | null;\r\n}\r\n\r\n/**\r\n * Synchronously prefers a build-time manifest (avoids a render-blocking\r\n * fetch on SSG/SSR); otherwise kicks off the async runtime load. Until the\r\n * load resolves, `modules` is null and callers should treat features as\r\n * unknown (recommended: render the historical default, then re-render once\r\n * `modules` settles).\r\n */\r\nexport function useEzCoderManifest(): UseEzCoderManifestResult {\r\n // Lazy initializer — `loadFromBuildOutput` touches the filesystem on Node\r\n // and must NOT run on every render. Memoized once across the mount.\r\n const [manifest, setManifest] = useState<EzCoderManifest | null>(\r\n () => EzCoderManifest.loadFromBuildOutput(),\r\n );\r\n const [loading, setLoading] = useState<boolean>(manifest === null);\r\n\r\n useEffect(() => {\r\n // If we already have a build-time manifest, no need to fetch.\r\n if (manifest) return;\r\n\r\n let cancelled = false;\r\n EzCoderManifest.load()\r\n .then((m) => {\r\n if (cancelled) return;\r\n setManifest(m);\r\n setLoading(false);\r\n })\r\n .catch(() => {\r\n if (cancelled) return;\r\n // load() already handles errors and returns a fallback, but be\r\n // defensive: ensure we never leave the UI stuck in loading state.\r\n setLoading(false);\r\n });\r\n\r\n return () => {\r\n cancelled = true;\r\n };\r\n // We only want this effect to run on mount; once `manifest` settles,\r\n // re-running would re-fetch unnecessarily.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, []);\r\n\r\n return {\r\n manifest,\r\n modules: manifest ? manifest.getRequiredModules() : null,\r\n loading,\r\n source: manifest ? manifest.getSource() : null,\r\n };\r\n}\r\n","export { env, features, isFeatureConfigured } from './core/config';\r\nexport { supabase, isSupabaseConfigured } from './core/supabase';\r\nexport { ezcoder, ezcoderAuthIntegration } from './core/platform';\r\nexport { DatabaseProvider, useDatabase, DatabaseClient } from './database';\r\nexport type {\r\n AuthUser,\r\n UserProfile,\r\n SubscriptionTier,\r\n SubscriptionStatus,\r\n StorageResult,\r\n StorageFile,\r\n CheckoutOptions,\r\n EzcoderClient,\r\n AnalyticsResult,\r\n AuthIntegration,\r\n} from './core/types';\r\n\r\n// ─── Capability Manifest (B7) ────────────────────────────────────────────\r\n//\r\n// Manifest awareness is OPT-IN. The legacy module auto-registration is\r\n// preserved, so existing consumers see no behavioural change. New code can\r\n// import these to gate features:\r\n//\r\n// import { EzCoderManifest, useEzCoderManifest } from '@ezcoder.dev/sdk';\r\n// const { modules, loading } = useEzCoderManifest();\r\n// if (!loading && modules?.payments) renderCheckout();\r\n//\r\nexport { EzCoderManifest } from './manifest-consumer';\r\nexport type { RequiredModules, EnvRefs, SanitizedManifest } from './manifest-consumer';\r\nexport { useEzCoderManifest } from './useEzCoderManifest';\r\nexport type { UseEzCoderManifestResult } from './useEzCoderManifest';\r\n\r\n// Kick off an opportunistic load on import so the cache is warm by the\r\n// time components call useEzCoderManifest(). Errors are swallowed inside\r\n// load() and produce a fallback instance — never throws.\r\nimport { EzCoderManifest as _EzCoderManifestForBoot } from './manifest-consumer';\r\nif (typeof window !== 'undefined') {\r\n // Browser only. SSR/SSG paths that want the manifest should call\r\n // loadFromBuildOutput() or load() explicitly to control timing.\r\n void _EzCoderManifestForBoot.load();\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -27,13 +27,38 @@ interface NotificationCenterProps {
|
|
|
27
27
|
}
|
|
28
28
|
declare function NotificationCenter({ className }: NotificationCenterProps): react_jsx_runtime.JSX.Element;
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* SDK sendNotification (B1).
|
|
32
|
+
*
|
|
33
|
+
* Routes to /api/project-notifications/send using the SERVER-class project
|
|
34
|
+
* token.
|
|
35
|
+
*
|
|
36
|
+
* BEFORE B1, this function called a Supabase RPC (`sdk_create_notification`)
|
|
37
|
+
* directly. That bypassed the platform auth surface entirely, so quota and
|
|
38
|
+
* per-tier limits could not be enforced.
|
|
39
|
+
*
|
|
40
|
+
* AFTER B1:
|
|
41
|
+
* - Reads EZC_PROJECT_TOKEN_SERVER from server-side env.
|
|
42
|
+
* - Sends via `X-EzCoder-Server-Token` header to the new
|
|
43
|
+
* /api/project-notifications/send endpoint.
|
|
44
|
+
* - Refuses to run from browser bundles with a clear, actionable error.
|
|
45
|
+
* - Per-project rate limits, audit logging, and the credential class check
|
|
46
|
+
* are enforced server-side.
|
|
47
|
+
*
|
|
48
|
+
* For READING notifications (browser-friendly), see `useNotifications`,
|
|
49
|
+
* which targets /api/project-notifications/list using the PUBLIC-class
|
|
50
|
+
* token via `resolvePublicToken()`.
|
|
51
|
+
*/
|
|
30
52
|
interface SendNotificationOptions {
|
|
31
53
|
type?: 'info' | 'success' | 'warning' | 'error' | 'system';
|
|
32
54
|
data?: Record<string, unknown>;
|
|
33
55
|
}
|
|
34
|
-
|
|
56
|
+
interface SendNotificationResult {
|
|
35
57
|
success: boolean;
|
|
58
|
+
id?: string;
|
|
36
59
|
error?: string;
|
|
37
|
-
|
|
60
|
+
code?: string;
|
|
61
|
+
}
|
|
62
|
+
declare function sendNotification(userId: string, title: string, message: string, options?: SendNotificationOptions): Promise<SendNotificationResult>;
|
|
38
63
|
|
|
39
64
|
export { NotificationCenter, sendNotification, useNotifications };
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveServerToken
|
|
3
|
+
} from "../chunk-CQKYANAW.js";
|
|
1
4
|
import {
|
|
2
5
|
AuthContext
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
6
|
+
} from "../chunk-QHB7LGCA.js";
|
|
7
|
+
import "../chunk-HJ2EIZ4S.js";
|
|
5
8
|
import {
|
|
6
|
-
env,
|
|
7
|
-
features,
|
|
8
9
|
supabase
|
|
9
|
-
} from "../chunk-
|
|
10
|
+
} from "../chunk-I2YGB7Z6.js";
|
|
11
|
+
import {
|
|
12
|
+
env,
|
|
13
|
+
features
|
|
14
|
+
} from "../chunk-LIUE7M7K.js";
|
|
10
15
|
|
|
11
16
|
// src/notifications/useNotifications.ts
|
|
12
17
|
import { useState, useEffect, useCallback, useContext } from "react";
|
|
@@ -188,22 +193,52 @@ function NotificationCenter({ className = "" }) {
|
|
|
188
193
|
|
|
189
194
|
// src/notifications/sendNotification.ts
|
|
190
195
|
async function sendNotification(userId, title, message, options = {}) {
|
|
196
|
+
const apiUrl = env.EZCODER_API_URL;
|
|
191
197
|
const projectId = env.EZC_PROJECT_ID;
|
|
192
|
-
if (!projectId) {
|
|
193
|
-
return {
|
|
198
|
+
if (!apiUrl || !projectId) {
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
error: "EZCODER_API_URL and EZC_PROJECT_ID are required",
|
|
202
|
+
code: "sdk_misconfigured"
|
|
203
|
+
};
|
|
194
204
|
}
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
+
const tokenResolution = resolveServerToken();
|
|
206
|
+
if (!tokenResolution.ok) {
|
|
207
|
+
return { success: false, error: tokenResolution.reason, code: "no_server_token" };
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
const res = await fetch(`${apiUrl}/api/project-notifications/send`, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: {
|
|
213
|
+
"Content-Type": "application/json",
|
|
214
|
+
"X-EzCoder-Server-Token": tokenResolution.token
|
|
215
|
+
},
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
projectId,
|
|
218
|
+
userId,
|
|
219
|
+
title,
|
|
220
|
+
message,
|
|
221
|
+
type: options.type ?? "info",
|
|
222
|
+
data: options.data ?? {}
|
|
223
|
+
})
|
|
224
|
+
});
|
|
225
|
+
const raw = await res.json().catch(() => ({}));
|
|
226
|
+
const data = raw && typeof raw === "object" ? raw : {};
|
|
227
|
+
if (!res.ok) {
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
error: data.error ?? `HTTP ${res.status}`,
|
|
231
|
+
code: data.code
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return { success: true, id: data.id };
|
|
235
|
+
} catch (err) {
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
error: err instanceof Error ? err.message : "Network error",
|
|
239
|
+
code: "network_error"
|
|
240
|
+
};
|
|
205
241
|
}
|
|
206
|
-
return data || { success: true };
|
|
207
242
|
}
|
|
208
243
|
export {
|
|
209
244
|
NotificationCenter,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/notifications/useNotifications.ts","../../src/notifications/NotificationCenter.tsx","../../src/notifications/sendNotification.ts"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\r\nimport { supabase } from '../core/supabase';\r\nimport { features } from '../core/config';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\n\r\ninterface Notification {\r\n id: string;\r\n type: string;\r\n title: string;\r\n message: string;\r\n data?: Record<string, unknown>;\r\n read: boolean;\r\n read_at?: string;\r\n created_at: string;\r\n}\r\n\r\nconst DEMO_NOTIFICATIONS: Notification[] = [\r\n {\r\n id: 'demo-1',\r\n type: 'info',\r\n title: 'Welcome!',\r\n message: 'Connect a database to enable real-time notifications.',\r\n read: false,\r\n created_at: new Date().toISOString(),\r\n },\r\n];\r\n\r\ninterface UseNotificationsReturn {\r\n notifications: Notification[];\r\n unreadCount: number;\r\n loading: boolean;\r\n isConfigured: boolean;\r\n markAsRead: (notificationId: string) => Promise<void>;\r\n markAllAsRead: () => Promise<void>;\r\n deleteNotification: (notificationId: string) => Promise<void>;\r\n refetch: () => Promise<void>;\r\n}\r\n\r\nexport function useNotifications(): UseNotificationsReturn {\r\n const auth = useContext(AuthContext);\r\n const [notifications, setNotifications] = useState<Notification[]>([]);\r\n const [loading, setLoading] = useState(true);\r\n\r\n const userId = auth?.user?.id;\r\n\r\n const fetchNotifications = useCallback(async () => {\r\n if (!features.auth || !userId) {\r\n setNotifications(DEMO_NOTIFICATIONS);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n try {\r\n const { data, error } = await supabase\r\n .from('notifications')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .order('created_at', { ascending: false })\r\n .limit(50);\r\n\r\n if (error) {\r\n setNotifications(DEMO_NOTIFICATIONS);\r\n } else {\r\n setNotifications((data as Notification[]) || []);\r\n }\r\n } catch {\r\n setNotifications(DEMO_NOTIFICATIONS);\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [userId]);\r\n\r\n useEffect(() => {\r\n fetchNotifications();\r\n }, [fetchNotifications]);\r\n\r\n useEffect(() => {\r\n if (!features.auth || !userId) return;\r\n\r\n const channel = supabase\r\n .channel(`notifications_${userId}`)\r\n .on(\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n 'postgres_changes' as any,\r\n {\r\n event: 'INSERT',\r\n schema: 'public',\r\n table: 'notifications',\r\n filter: `user_id=eq.${userId}`,\r\n },\r\n (payload: { new: Notification }) => {\r\n setNotifications((prev) => [payload.new, ...prev]);\r\n }\r\n )\r\n .subscribe();\r\n\r\n return () => {\r\n supabase.removeChannel(channel);\r\n };\r\n }, [userId]);\r\n\r\n const markAsRead = useCallback(async (notificationId: string) => {\r\n if (!features.auth) return;\r\n\r\n await supabase\r\n .from('notifications')\r\n .update({ read: true, read_at: new Date().toISOString() })\r\n .eq('id', notificationId);\r\n\r\n setNotifications((prev) =>\r\n prev.map((n) => (n.id === notificationId ? { ...n, read: true } : n))\r\n );\r\n }, []);\r\n\r\n const markAllAsRead = useCallback(async () => {\r\n if (!features.auth || !userId) return;\r\n\r\n await supabase\r\n .from('notifications')\r\n .update({ read: true, read_at: new Date().toISOString() })\r\n .eq('user_id', userId)\r\n .eq('read', false);\r\n\r\n setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));\r\n }, [userId]);\r\n\r\n const deleteNotification = useCallback(async (notificationId: string) => {\r\n if (!features.auth) return;\r\n\r\n await supabase.from('notifications').delete().eq('id', notificationId);\r\n setNotifications((prev) => prev.filter((n) => n.id !== notificationId));\r\n }, []);\r\n\r\n const unreadCount = notifications.filter((n) => !n.read).length;\r\n\r\n return {\r\n notifications,\r\n unreadCount,\r\n loading,\r\n isConfigured: features.auth,\r\n markAsRead,\r\n markAllAsRead,\r\n deleteNotification,\r\n refetch: fetchNotifications,\r\n };\r\n}\r\n","import { useState } from 'react';\r\nimport { useNotifications } from './useNotifications';\r\n\r\ninterface NotificationCenterProps {\r\n className?: string;\r\n}\r\n\r\nexport function NotificationCenter({ className = '' }: NotificationCenterProps) {\r\n const { notifications, unreadCount, markAsRead, markAllAsRead, deleteNotification } = useNotifications();\r\n const [open, setOpen] = useState(false);\r\n\r\n return (\r\n <div className={className} style={{ position: 'relative', display: 'inline-block' }}>\r\n <button\r\n onClick={() => setOpen(!open)}\r\n style={{\r\n background: 'none', border: 'none', cursor: 'pointer',\r\n position: 'relative', padding: '8px', fontSize: '20px',\r\n }}\r\n >\r\n 🔔\r\n {unreadCount > 0 && (\r\n <span style={{\r\n position: 'absolute', top: '2px', right: '2px',\r\n backgroundColor: '#dc2626', color: 'white', fontSize: '10px',\r\n borderRadius: '50%', width: '18px', height: '18px',\r\n display: 'flex', alignItems: 'center', justifyContent: 'center',\r\n }}>\r\n {unreadCount > 9 ? '9+' : unreadCount}\r\n </span>\r\n )}\r\n </button>\r\n\r\n {open && (\r\n <div style={{\r\n position: 'absolute', right: 0, top: '100%', width: '320px',\r\n backgroundColor: 'white', border: '1px solid #e5e7eb',\r\n borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)',\r\n zIndex: 50, maxHeight: '400px', overflow: 'auto',\r\n }}>\r\n <div style={{ padding: '12px 16px', borderBottom: '1px solid #e5e7eb', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>\r\n <span style={{ fontWeight: 600 }}>Notifications</span>\r\n {unreadCount > 0 && (\r\n <button onClick={() => markAllAsRead()} style={{ background: 'none', border: 'none', color: '#3b82f6', cursor: 'pointer', fontSize: '12px' }}>\r\n Mark all read\r\n </button>\r\n )}\r\n </div>\r\n\r\n {notifications.length === 0 ? (\r\n <div style={{ padding: '24px', textAlign: 'center', color: '#9ca3af' }}>\r\n No notifications\r\n </div>\r\n ) : (\r\n notifications.map((n) => (\r\n <div\r\n key={n.id}\r\n style={{\r\n padding: '12px 16px', borderBottom: '1px solid #f3f4f6',\r\n backgroundColor: n.read ? 'transparent' : '#eff6ff',\r\n cursor: 'pointer',\r\n }}\r\n onClick={() => !n.read && markAsRead(n.id)}\r\n >\r\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>\r\n <div>\r\n <p style={{ fontWeight: 500, fontSize: '14px', marginBottom: '2px' }}>{n.title}</p>\r\n <p style={{ color: '#6b7280', fontSize: '13px' }}>{n.message}</p>\r\n <p style={{ color: '#9ca3af', fontSize: '11px', marginTop: '4px' }}>\r\n {new Date(n.created_at).toLocaleDateString()}\r\n </p>\r\n </div>\r\n <button\r\n onClick={(e) => { e.stopPropagation(); deleteNotification(n.id); }}\r\n style={{ background: 'none', border: 'none', color: '#9ca3af', cursor: 'pointer', fontSize: '16px' }}\r\n >\r\n ×\r\n </button>\r\n </div>\r\n </div>\r\n ))\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { supabase } from '../core/supabase';\r\nimport { env } from '../core/config';\r\n\r\ninterface SendNotificationOptions {\r\n type?: 'info' | 'success' | 'warning' | 'error' | 'system';\r\n data?: Record<string, unknown>;\r\n}\r\n\r\nexport async function sendNotification(\r\n userId: string,\r\n title: string,\r\n message: string,\r\n options: SendNotificationOptions = {},\r\n): Promise<{ success: boolean; error?: string }> {\r\n const projectId = env.EZC_PROJECT_ID;\r\n if (!projectId) {\r\n return { success: false, error: 'EZC_PROJECT_ID not configured' };\r\n }\r\n\r\n const { data, error } = await supabase.rpc('sdk_create_notification', {\r\n p_project_id: projectId,\r\n p_user_id: userId,\r\n p_title: title,\r\n p_message: message,\r\n p_type: options.type || 'info',\r\n p_data: options.data || {},\r\n });\r\n\r\n if (error) {\r\n return { success: false, error: error.message };\r\n }\r\n\r\n return (data as { success: boolean; error?: string }) || { success: true };\r\n}\r\n"],"mappings":"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","names":["useState","useState"]}
|
|
1
|
+
{"version":3,"sources":["../../src/notifications/useNotifications.ts","../../src/notifications/NotificationCenter.tsx","../../src/notifications/sendNotification.ts"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\r\nimport { supabase } from '../core/supabase';\r\nimport { features } from '../core/config';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\n\r\ninterface Notification {\r\n id: string;\r\n type: string;\r\n title: string;\r\n message: string;\r\n data?: Record<string, unknown>;\r\n read: boolean;\r\n read_at?: string;\r\n created_at: string;\r\n}\r\n\r\nconst DEMO_NOTIFICATIONS: Notification[] = [\r\n {\r\n id: 'demo-1',\r\n type: 'info',\r\n title: 'Welcome!',\r\n message: 'Connect a database to enable real-time notifications.',\r\n read: false,\r\n created_at: new Date().toISOString(),\r\n },\r\n];\r\n\r\ninterface UseNotificationsReturn {\r\n notifications: Notification[];\r\n unreadCount: number;\r\n loading: boolean;\r\n isConfigured: boolean;\r\n markAsRead: (notificationId: string) => Promise<void>;\r\n markAllAsRead: () => Promise<void>;\r\n deleteNotification: (notificationId: string) => Promise<void>;\r\n refetch: () => Promise<void>;\r\n}\r\n\r\nexport function useNotifications(): UseNotificationsReturn {\r\n const auth = useContext(AuthContext);\r\n const [notifications, setNotifications] = useState<Notification[]>([]);\r\n const [loading, setLoading] = useState(true);\r\n\r\n const userId = auth?.user?.id;\r\n\r\n const fetchNotifications = useCallback(async () => {\r\n if (!features.auth || !userId) {\r\n setNotifications(DEMO_NOTIFICATIONS);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n try {\r\n const { data, error } = await supabase\r\n .from('notifications')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .order('created_at', { ascending: false })\r\n .limit(50);\r\n\r\n if (error) {\r\n setNotifications(DEMO_NOTIFICATIONS);\r\n } else {\r\n setNotifications((data as Notification[]) || []);\r\n }\r\n } catch {\r\n setNotifications(DEMO_NOTIFICATIONS);\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [userId]);\r\n\r\n useEffect(() => {\r\n fetchNotifications();\r\n }, [fetchNotifications]);\r\n\r\n useEffect(() => {\r\n if (!features.auth || !userId) return;\r\n\r\n const channel = supabase\r\n .channel(`notifications_${userId}`)\r\n .on(\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n 'postgres_changes' as any,\r\n {\r\n event: 'INSERT',\r\n schema: 'public',\r\n table: 'notifications',\r\n filter: `user_id=eq.${userId}`,\r\n },\r\n (payload: { new: Notification }) => {\r\n setNotifications((prev) => [payload.new, ...prev]);\r\n }\r\n )\r\n .subscribe();\r\n\r\n return () => {\r\n supabase.removeChannel(channel);\r\n };\r\n }, [userId]);\r\n\r\n const markAsRead = useCallback(async (notificationId: string) => {\r\n if (!features.auth) return;\r\n\r\n await supabase\r\n .from('notifications')\r\n .update({ read: true, read_at: new Date().toISOString() })\r\n .eq('id', notificationId);\r\n\r\n setNotifications((prev) =>\r\n prev.map((n) => (n.id === notificationId ? { ...n, read: true } : n))\r\n );\r\n }, []);\r\n\r\n const markAllAsRead = useCallback(async () => {\r\n if (!features.auth || !userId) return;\r\n\r\n await supabase\r\n .from('notifications')\r\n .update({ read: true, read_at: new Date().toISOString() })\r\n .eq('user_id', userId)\r\n .eq('read', false);\r\n\r\n setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));\r\n }, [userId]);\r\n\r\n const deleteNotification = useCallback(async (notificationId: string) => {\r\n if (!features.auth) return;\r\n\r\n await supabase.from('notifications').delete().eq('id', notificationId);\r\n setNotifications((prev) => prev.filter((n) => n.id !== notificationId));\r\n }, []);\r\n\r\n const unreadCount = notifications.filter((n) => !n.read).length;\r\n\r\n return {\r\n notifications,\r\n unreadCount,\r\n loading,\r\n isConfigured: features.auth,\r\n markAsRead,\r\n markAllAsRead,\r\n deleteNotification,\r\n refetch: fetchNotifications,\r\n };\r\n}\r\n","import { useState } from 'react';\r\nimport { useNotifications } from './useNotifications';\r\n\r\ninterface NotificationCenterProps {\r\n className?: string;\r\n}\r\n\r\nexport function NotificationCenter({ className = '' }: NotificationCenterProps) {\r\n const { notifications, unreadCount, markAsRead, markAllAsRead, deleteNotification } = useNotifications();\r\n const [open, setOpen] = useState(false);\r\n\r\n return (\r\n <div className={className} style={{ position: 'relative', display: 'inline-block' }}>\r\n <button\r\n onClick={() => setOpen(!open)}\r\n style={{\r\n background: 'none', border: 'none', cursor: 'pointer',\r\n position: 'relative', padding: '8px', fontSize: '20px',\r\n }}\r\n >\r\n 🔔\r\n {unreadCount > 0 && (\r\n <span style={{\r\n position: 'absolute', top: '2px', right: '2px',\r\n backgroundColor: '#dc2626', color: 'white', fontSize: '10px',\r\n borderRadius: '50%', width: '18px', height: '18px',\r\n display: 'flex', alignItems: 'center', justifyContent: 'center',\r\n }}>\r\n {unreadCount > 9 ? '9+' : unreadCount}\r\n </span>\r\n )}\r\n </button>\r\n\r\n {open && (\r\n <div style={{\r\n position: 'absolute', right: 0, top: '100%', width: '320px',\r\n backgroundColor: 'white', border: '1px solid #e5e7eb',\r\n borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)',\r\n zIndex: 50, maxHeight: '400px', overflow: 'auto',\r\n }}>\r\n <div style={{ padding: '12px 16px', borderBottom: '1px solid #e5e7eb', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>\r\n <span style={{ fontWeight: 600 }}>Notifications</span>\r\n {unreadCount > 0 && (\r\n <button onClick={() => markAllAsRead()} style={{ background: 'none', border: 'none', color: '#3b82f6', cursor: 'pointer', fontSize: '12px' }}>\r\n Mark all read\r\n </button>\r\n )}\r\n </div>\r\n\r\n {notifications.length === 0 ? (\r\n <div style={{ padding: '24px', textAlign: 'center', color: '#9ca3af' }}>\r\n No notifications\r\n </div>\r\n ) : (\r\n notifications.map((n) => (\r\n <div\r\n key={n.id}\r\n style={{\r\n padding: '12px 16px', borderBottom: '1px solid #f3f4f6',\r\n backgroundColor: n.read ? 'transparent' : '#eff6ff',\r\n cursor: 'pointer',\r\n }}\r\n onClick={() => !n.read && markAsRead(n.id)}\r\n >\r\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>\r\n <div>\r\n <p style={{ fontWeight: 500, fontSize: '14px', marginBottom: '2px' }}>{n.title}</p>\r\n <p style={{ color: '#6b7280', fontSize: '13px' }}>{n.message}</p>\r\n <p style={{ color: '#9ca3af', fontSize: '11px', marginTop: '4px' }}>\r\n {new Date(n.created_at).toLocaleDateString()}\r\n </p>\r\n </div>\r\n <button\r\n onClick={(e) => { e.stopPropagation(); deleteNotification(n.id); }}\r\n style={{ background: 'none', border: 'none', color: '#9ca3af', cursor: 'pointer', fontSize: '16px' }}\r\n >\r\n ×\r\n </button>\r\n </div>\r\n </div>\r\n ))\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","/**\r\n * SDK sendNotification (B1).\r\n *\r\n * Routes to /api/project-notifications/send using the SERVER-class project\r\n * token.\r\n *\r\n * BEFORE B1, this function called a Supabase RPC (`sdk_create_notification`)\r\n * directly. That bypassed the platform auth surface entirely, so quota and\r\n * per-tier limits could not be enforced.\r\n *\r\n * AFTER B1:\r\n * - Reads EZC_PROJECT_TOKEN_SERVER from server-side env.\r\n * - Sends via `X-EzCoder-Server-Token` header to the new\r\n * /api/project-notifications/send endpoint.\r\n * - Refuses to run from browser bundles with a clear, actionable error.\r\n * - Per-project rate limits, audit logging, and the credential class check\r\n * are enforced server-side.\r\n *\r\n * For READING notifications (browser-friendly), see `useNotifications`,\r\n * which targets /api/project-notifications/list using the PUBLIC-class\r\n * token via `resolvePublicToken()`.\r\n */\r\n\r\nimport { env } from '../core/config';\r\nimport { resolveServerToken } from '../core/projectToken';\r\n\r\ninterface SendNotificationOptions {\r\n type?: 'info' | 'success' | 'warning' | 'error' | 'system';\r\n data?: Record<string, unknown>;\r\n}\r\n\r\ninterface SendNotificationResult {\r\n success: boolean;\r\n id?: string;\r\n error?: string;\r\n code?: string;\r\n}\r\n\r\nexport async function sendNotification(\r\n userId: string,\r\n title: string,\r\n message: string,\r\n options: SendNotificationOptions = {},\r\n): Promise<SendNotificationResult> {\r\n const apiUrl = env.EZCODER_API_URL;\r\n const projectId = env.EZC_PROJECT_ID;\r\n\r\n if (!apiUrl || !projectId) {\r\n return {\r\n success: false,\r\n error: 'EZCODER_API_URL and EZC_PROJECT_ID are required',\r\n code: 'sdk_misconfigured',\r\n };\r\n }\r\n\r\n const tokenResolution = resolveServerToken();\r\n if (!tokenResolution.ok) {\r\n return { success: false, error: tokenResolution.reason, code: 'no_server_token' };\r\n }\r\n\r\n try {\r\n const res = await fetch(`${apiUrl}/api/project-notifications/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'X-EzCoder-Server-Token': tokenResolution.token,\r\n },\r\n body: JSON.stringify({\r\n projectId,\r\n userId,\r\n title,\r\n message,\r\n type: options.type ?? 'info',\r\n data: options.data ?? {},\r\n }),\r\n });\r\n\r\n const raw: unknown = await res.json().catch(() => ({}));\r\n const data = (raw && typeof raw === 'object' ? raw : {}) as {\r\n id?: string;\r\n error?: string;\r\n code?: string;\r\n };\r\n\r\n if (!res.ok) {\r\n return {\r\n success: false,\r\n error: data.error ?? `HTTP ${res.status}`,\r\n code: data.code,\r\n };\r\n }\r\n\r\n return { success: true, id: data.id };\r\n } catch (err: unknown) {\r\n return {\r\n success: false,\r\n error: err instanceof Error ? err.message : 'Network error',\r\n code: 'network_error',\r\n };\r\n }\r\n}\r\n"],"mappings":"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","names":["useState","useState"]}
|
package/dist/payments/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AuthContext
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-QHB7LGCA.js";
|
|
4
4
|
import {
|
|
5
5
|
ezcoder
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-HJ2EIZ4S.js";
|
|
7
7
|
import {
|
|
8
|
-
features,
|
|
9
8
|
supabase
|
|
10
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-I2YGB7Z6.js";
|
|
10
|
+
import {
|
|
11
|
+
features
|
|
12
|
+
} from "../chunk-LIUE7M7K.js";
|
|
11
13
|
|
|
12
14
|
// src/payments/useSubscription.ts
|
|
13
15
|
import { useState, useEffect, useCallback, useContext } from "react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/payments/useSubscription.ts","../../src/payments/useCustomerAccess.ts","../../src/payments/BuyButton.tsx","../../src/payments/PricingTable.tsx","../../src/payments/ManageSubscriptionButton.tsx","../../src/payments/SubscriptionManager.tsx","../../src/payments/ProtectedContent.tsx","../../src/payments/InvoiceHistory.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\nimport { supabase } from '../core/supabase';\r\nimport { features } from '../core/config';\r\nimport type { SubscriptionTier, SubscriptionStatus } from '../core/types';\r\n\r\ninterface SubscriptionState {\r\n customerId: string | null;\r\n tier: SubscriptionTier;\r\n status: SubscriptionStatus | null;\r\n currentPeriodEnd: string | null;\r\n}\r\n\r\ninterface UseSubscriptionReturn {\r\n subscription: SubscriptionState | null;\r\n tier: SubscriptionTier;\r\n status: SubscriptionStatus | null;\r\n isActive: boolean;\r\n isPro: boolean;\r\n isBusiness: boolean;\r\n isEnterprise: boolean;\r\n canAccess: (requiredTier: SubscriptionTier) => boolean;\r\n loading: boolean;\r\n isConfigured: boolean;\r\n user: unknown;\r\n profile: unknown;\r\n isAuthenticated: boolean;\r\n refetch: () => Promise<void>;\r\n}\r\n\r\nconst TIER_LEVELS: Record<string, number> = {\r\n free: 0, starter: 1, creator: 2, pro: 3, business: 4, enterprise: 5,\r\n};\r\n\r\nexport function useSubscription(): UseSubscriptionReturn {\r\n const auth = useContext(AuthContext);\r\n const [subscription, setSubscription] = useState<SubscriptionState | null>(null);\r\n const [loading, setLoading] = useState(true);\r\n\r\n const fetchSubscription = useCallback(async () => {\r\n if (!auth?.profile) {\r\n setSubscription(null);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const sub: SubscriptionState = {\r\n customerId: auth.profile.stripe_customer_id || null,\r\n tier: (auth.profile.subscription_tier as SubscriptionTier) || 'free',\r\n status: (auth.profile.subscription_status as SubscriptionStatus) || null,\r\n currentPeriodEnd: auth.profile.subscription_period_end || null,\r\n };\r\n\r\n setSubscription(sub);\r\n setLoading(false);\r\n }, [auth?.profile]);\r\n\r\n useEffect(() => {\r\n fetchSubscription();\r\n }, [fetchSubscription]);\r\n\r\n useEffect(() => {\r\n if (!features.auth || !auth?.user?.id) return;\r\n\r\n const channel = supabase\r\n .channel(`subscription_${auth.user.id}`)\r\n .on(\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n 'postgres_changes' as any,\r\n {\r\n event: 'UPDATE',\r\n schema: 'public',\r\n table: 'user_profiles',\r\n filter: `id=eq.${auth.user.id}`,\r\n },\r\n () => {\r\n auth.refetchProfile(auth.user!.id);\r\n }\r\n )\r\n .subscribe();\r\n\r\n return () => {\r\n supabase.removeChannel(channel);\r\n };\r\n }, [auth?.user?.id, auth]);\r\n\r\n const tier = subscription?.tier || 'free';\r\n const status = subscription?.status || null;\r\n const isActive = !status || status === 'active' || status === 'trialing';\r\n const tierLevel = TIER_LEVELS[tier] ?? 0;\r\n\r\n return {\r\n subscription,\r\n tier,\r\n status,\r\n isActive,\r\n isPro: tierLevel >= TIER_LEVELS.pro,\r\n isBusiness: tierLevel >= TIER_LEVELS.business,\r\n isEnterprise: tierLevel >= TIER_LEVELS.enterprise,\r\n canAccess: (requiredTier: SubscriptionTier) => tierLevel >= (TIER_LEVELS[requiredTier] ?? 0),\r\n loading: loading || (auth?.loading ?? false),\r\n isConfigured: features.payments,\r\n user: auth?.user ?? null,\r\n profile: auth?.profile ?? null,\r\n isAuthenticated: Boolean(auth?.user),\r\n refetch: fetchSubscription,\r\n };\r\n}\r\n","import { useState, useEffect, useCallback } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface CustomerAccessReturn {\r\n loading: boolean;\r\n hasAccess: boolean;\r\n customer: Record<string, unknown> | null;\r\n subscription: Record<string, unknown> | null;\r\n error: string | null;\r\n refresh: () => Promise<void>;\r\n logout: () => void;\r\n}\r\n\r\nexport function useCustomerAccess(): CustomerAccessReturn {\r\n const [loading, setLoading] = useState(true);\r\n const [hasAccess, setHasAccess] = useState(false);\r\n const [customer, setCustomer] = useState<Record<string, unknown> | null>(null);\r\n const [subscription, setSubscription] = useState<Record<string, unknown> | null>(null);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const checkAccess = useCallback(async () => {\r\n setLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const customerId = typeof localStorage !== 'undefined'\r\n ? localStorage.getItem('stripeCustomerId')\r\n : null;\r\n const email = typeof localStorage !== 'undefined'\r\n ? localStorage.getItem('customerEmail')\r\n : null;\r\n\r\n if (!customerId && !email) {\r\n setHasAccess(false);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const result = await ezcoder.stripe.getCustomerStatus({ customerId: customerId || undefined, email: email || undefined });\r\n\r\n if (result.success) {\r\n setHasAccess(Boolean(result.hasAccess));\r\n setCustomer((result.customer as Record<string, unknown>) || null);\r\n setSubscription((result.subscription as Record<string, unknown>) || null);\r\n } else {\r\n setHasAccess(false);\r\n setError(result.error || 'Failed to check access');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Unknown error');\r\n setHasAccess(false);\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, []);\r\n\r\n useEffect(() => {\r\n checkAccess();\r\n }, [checkAccess]);\r\n\r\n const logout = useCallback(() => {\r\n if (typeof localStorage !== 'undefined') {\r\n localStorage.removeItem('stripeCustomerId');\r\n localStorage.removeItem('customerEmail');\r\n }\r\n setHasAccess(false);\r\n setCustomer(null);\r\n setSubscription(null);\r\n }, []);\r\n\r\n return { loading, hasAccess, customer, subscription, error, refresh: checkAccess, logout };\r\n}\r\n","import { useState } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface BuyButtonProps {\r\n priceId: string;\r\n productName?: string;\r\n className?: string;\r\n children?: React.ReactNode;\r\n disabled?: boolean;\r\n customerEmail?: string;\r\n quantity?: number;\r\n successUrl?: string;\r\n cancelUrl?: string;\r\n}\r\n\r\nexport function BuyButton({\r\n priceId,\r\n productName,\r\n className = '',\r\n children,\r\n disabled = false,\r\n customerEmail,\r\n quantity,\r\n successUrl,\r\n cancelUrl,\r\n}: BuyButtonProps) {\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const handleClick = async () => {\r\n setError(null);\r\n setLoading(true);\r\n\r\n try {\r\n const result = await ezcoder.stripe.createCheckout(priceId, {\r\n customerEmail,\r\n quantity,\r\n successUrl,\r\n cancelUrl,\r\n redirect: true,\r\n });\r\n\r\n if (!result.success) {\r\n setError(result.error || 'Checkout failed');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Checkout failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <div>\r\n <button\r\n onClick={handleClick}\r\n disabled={disabled || loading}\r\n className={className}\r\n style={!className ? {\r\n padding: '10px 20px',\r\n backgroundColor: disabled || loading ? '#9ca3af' : '#3b82f6',\r\n color: 'white',\r\n border: 'none',\r\n borderRadius: '6px',\r\n fontSize: '14px',\r\n fontWeight: 500,\r\n cursor: disabled || loading ? 'not-allowed' : 'pointer',\r\n } : undefined}\r\n >\r\n {loading ? 'Processing...' : children || `Buy ${productName || 'Now'}`}\r\n </button>\r\n {error && (\r\n <p style={{ color: '#dc2626', fontSize: '12px', marginTop: '4px' }}>{error}</p>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState } from 'react';\r\nimport { BuyButton } from './BuyButton';\r\n\r\ninterface PricingPlan {\r\n name: string;\r\n description: string;\r\n monthlyPriceId?: string;\r\n yearlyPriceId?: string;\r\n monthlyPrice: number;\r\n yearlyPrice?: number;\r\n features: string[];\r\n highlighted?: boolean;\r\n cta?: string;\r\n}\r\n\r\ninterface PricingTableProps {\r\n plans?: PricingPlan[];\r\n className?: string;\r\n customerEmail?: string;\r\n}\r\n\r\nconst DEFAULT_PLANS: PricingPlan[] = [\r\n {\r\n name: 'Starter',\r\n description: 'For individuals getting started',\r\n monthlyPrice: 0,\r\n features: ['Basic features', 'Community support'],\r\n cta: 'Get Started',\r\n },\r\n {\r\n name: 'Creator',\r\n description: 'For growing projects',\r\n monthlyPrice: 25,\r\n yearlyPrice: 270,\r\n features: ['All Starter features', 'Priority support', 'Advanced analytics'],\r\n highlighted: true,\r\n cta: 'Subscribe',\r\n },\r\n {\r\n name: 'Business',\r\n description: 'For teams and businesses',\r\n monthlyPrice: 50,\r\n yearlyPrice: 540,\r\n features: ['All Creator features', 'Team collaboration', 'Custom integrations'],\r\n cta: 'Subscribe',\r\n },\r\n {\r\n name: 'Enterprise',\r\n description: 'For large organizations',\r\n monthlyPrice: -1,\r\n features: ['All Business features', 'Dedicated support', 'SLA guarantee', 'Custom contracts'],\r\n cta: 'Contact Sales',\r\n },\r\n];\r\n\r\nexport function PricingTable({ plans = DEFAULT_PLANS, className = '', customerEmail }: PricingTableProps) {\r\n const [yearly, setYearly] = useState(false);\r\n\r\n return (\r\n <div className={className}>\r\n <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '32px', gap: '8px', alignItems: 'center' }}>\r\n <span style={{ fontSize: '14px', color: !yearly ? '#111' : '#6b7280', fontWeight: !yearly ? 600 : 400 }}>Monthly</span>\r\n <button\r\n onClick={() => setYearly(!yearly)}\r\n style={{\r\n width: '44px', height: '24px', borderRadius: '12px',\r\n backgroundColor: yearly ? '#3b82f6' : '#d1d5db',\r\n border: 'none', cursor: 'pointer', position: 'relative',\r\n }}\r\n >\r\n <span style={{\r\n width: '18px', height: '18px', borderRadius: '50%', backgroundColor: 'white',\r\n position: 'absolute', top: '3px', left: yearly ? '23px' : '3px',\r\n transition: 'left 0.2s',\r\n }} />\r\n </button>\r\n <span style={{ fontSize: '14px', color: yearly ? '#111' : '#6b7280', fontWeight: yearly ? 600 : 400 }}>\r\n Yearly <span style={{ color: '#059669', fontSize: '12px' }}>Save 10%</span>\r\n </span>\r\n </div>\r\n\r\n <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(plans.length, 4)}, 1fr)`, gap: '24px', maxWidth: '1200px', margin: '0 auto' }}>\r\n {plans.map((plan) => {\r\n const price = yearly && plan.yearlyPrice !== undefined ? plan.yearlyPrice / 12 : plan.monthlyPrice;\r\n const priceId = yearly ? plan.yearlyPriceId : plan.monthlyPriceId;\r\n\r\n return (\r\n <div\r\n key={plan.name}\r\n style={{\r\n border: plan.highlighted ? '2px solid #3b82f6' : '1px solid #e5e7eb',\r\n borderRadius: '12px', padding: '24px',\r\n backgroundColor: plan.highlighted ? '#eff6ff' : 'white',\r\n }}\r\n >\r\n <h3 style={{ fontSize: '1.25rem', fontWeight: 600, marginBottom: '4px' }}>{plan.name}</h3>\r\n <p style={{ color: '#6b7280', fontSize: '14px', marginBottom: '16px' }}>{plan.description}</p>\r\n\r\n <div style={{ marginBottom: '24px' }}>\r\n {plan.monthlyPrice === 0 ? (\r\n <span style={{ fontSize: '2rem', fontWeight: 700 }}>Free</span>\r\n ) : plan.monthlyPrice === -1 ? (\r\n <span style={{ fontSize: '1.5rem', fontWeight: 600 }}>Custom</span>\r\n ) : (\r\n <>\r\n <span style={{ fontSize: '2rem', fontWeight: 700 }}>${Math.round(price)}</span>\r\n <span style={{ color: '#6b7280', fontSize: '14px' }}>/mo</span>\r\n </>\r\n )}\r\n </div>\r\n\r\n <ul style={{ listStyle: 'none', padding: 0, marginBottom: '24px' }}>\r\n {plan.features.map((feature) => (\r\n <li key={feature} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px', fontSize: '14px' }}>\r\n <span style={{ color: '#059669' }}>✓</span> {feature}\r\n </li>\r\n ))}\r\n </ul>\r\n\r\n {priceId ? (\r\n <BuyButton priceId={priceId} productName={plan.name} customerEmail={customerEmail}>\r\n {plan.cta || 'Subscribe'}\r\n </BuyButton>\r\n ) : plan.monthlyPrice === 0 ? (\r\n <button style={{\r\n width: '100%', padding: '10px', border: '1px solid #d1d5db',\r\n borderRadius: '6px', background: 'white', cursor: 'pointer', fontSize: '14px',\r\n }}>\r\n {plan.cta || 'Get Started'}\r\n </button>\r\n ) : (\r\n <button style={{\r\n width: '100%', padding: '10px', border: '1px solid #d1d5db',\r\n borderRadius: '6px', background: 'white', cursor: 'pointer', fontSize: '14px',\r\n }}>\r\n {plan.cta || 'Contact Sales'}\r\n </button>\r\n )}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import { useState } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface ManageSubscriptionButtonProps {\r\n children?: React.ReactNode;\r\n className?: string;\r\n customerId?: string;\r\n}\r\n\r\nexport function ManageSubscriptionButton({ children, className = '', customerId }: ManageSubscriptionButtonProps) {\r\n const [loading, setLoading] = useState(false);\r\n\r\n const handleClick = async () => {\r\n const id = customerId || (typeof localStorage !== 'undefined' ? localStorage.getItem('stripeCustomerId') : null);\r\n if (!id) return;\r\n\r\n setLoading(true);\r\n await ezcoder.stripe.createPortalSession(id, { redirect: true });\r\n setLoading(false);\r\n };\r\n\r\n return (\r\n <button\r\n onClick={handleClick}\r\n disabled={loading}\r\n className={className}\r\n style={!className ? {\r\n padding: '8px 16px',\r\n border: '1px solid #d1d5db',\r\n borderRadius: '6px',\r\n background: 'white',\r\n cursor: loading ? 'not-allowed' : 'pointer',\r\n fontSize: '14px',\r\n } : undefined}\r\n >\r\n {loading ? 'Loading...' : children || 'Manage Subscription'}\r\n </button>\r\n );\r\n}\r\n","import { useSubscription } from './useSubscription';\r\nimport { ManageSubscriptionButton } from './ManageSubscriptionButton';\r\n\r\ninterface SubscriptionManagerProps {\r\n className?: string;\r\n}\r\n\r\nconst STATUS_COLORS: Record<string, string> = {\r\n active: '#059669',\r\n trialing: '#3b82f6',\r\n past_due: '#d97706',\r\n canceled: '#dc2626',\r\n unpaid: '#dc2626',\r\n};\r\n\r\nexport function SubscriptionManager({ className = '' }: SubscriptionManagerProps) {\r\n const { subscription, tier, status, isActive, loading } = useSubscription();\r\n\r\n if (loading) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>Loading subscription...</div>;\r\n }\r\n\r\n return (\r\n <div className={className} style={{ border: '1px solid #e5e7eb', borderRadius: '12px', padding: '24px' }}>\r\n <h3 style={{ fontSize: '1.125rem', fontWeight: 600, marginBottom: '16px' }}>Subscription</h3>\r\n\r\n <div style={{ display: 'grid', gap: '12px', marginBottom: '24px' }}>\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>Plan</span>\r\n <span style={{ fontWeight: 500, textTransform: 'capitalize' }}>{tier}</span>\r\n </div>\r\n\r\n {status && (\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>Status</span>\r\n <span style={{\r\n fontWeight: 500,\r\n color: STATUS_COLORS[status] || '#6b7280',\r\n textTransform: 'capitalize',\r\n }}>\r\n {status === 'past_due' ? 'Past Due' : status}\r\n </span>\r\n </div>\r\n )}\r\n\r\n {subscription?.currentPeriodEnd && (\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>{isActive ? 'Renews' : 'Expires'}</span>\r\n <span>{new Date(subscription.currentPeriodEnd).toLocaleDateString()}</span>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {subscription?.customerId && (\r\n <ManageSubscriptionButton customerId={subscription.customerId} />\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useCustomerAccess } from './useCustomerAccess';\r\nimport { useSubscription } from './useSubscription';\r\nimport type { SubscriptionTier } from '../core/types';\r\n\r\ninterface ProtectedContentProps {\r\n children: React.ReactNode;\r\n fallback?: React.ReactNode;\r\n loadingComponent?: React.ReactNode;\r\n requiredTier?: SubscriptionTier;\r\n}\r\n\r\nexport function ProtectedContent({ children, fallback, loadingComponent, requiredTier }: ProtectedContentProps) {\r\n const customerAccess = useCustomerAccess();\r\n const subscription = useSubscription();\r\n\r\n const loading = requiredTier ? subscription.loading : customerAccess.loading;\r\n\r\n if (loading) {\r\n return <>{loadingComponent || <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>Loading...</div>}</>;\r\n }\r\n\r\n const hasAccess = requiredTier\r\n ? subscription.isActive && subscription.canAccess(requiredTier)\r\n : customerAccess.hasAccess;\r\n\r\n if (!hasAccess) {\r\n return <>{fallback || <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>\r\n {requiredTier\r\n ? `This content requires a ${requiredTier} subscription or higher.`\r\n : 'This content requires a subscription.'}\r\n </div>}</>;\r\n }\r\n\r\n return <>{children}</>;\r\n}\r\n","import { useState, useEffect, useCallback } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface Invoice {\r\n id: string;\r\n number: string | null;\r\n amount: number;\r\n currency: string;\r\n status: string;\r\n created: number;\r\n hostedUrl: string | null;\r\n pdfUrl: string | null;\r\n}\r\n\r\ninterface InvoiceHistoryProps {\r\n customerId?: string;\r\n limit?: number;\r\n className?: string;\r\n}\r\n\r\nexport function InvoiceHistory({ customerId, limit = 10, className = '' }: InvoiceHistoryProps) {\r\n const [invoices, setInvoices] = useState<Invoice[]>([]);\r\n const [loading, setLoading] = useState(true);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const fetchInvoices = useCallback(async () => {\r\n const id = customerId || (typeof localStorage !== 'undefined' ? localStorage.getItem('stripeCustomerId') : null);\r\n if (!id) {\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const result = await ezcoder.stripe.getInvoices(id, { limit });\r\n if (result.success && result.invoices) {\r\n setInvoices(result.invoices as Invoice[]);\r\n } else {\r\n setError(result.error || 'Failed to load invoices');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Unknown error');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [customerId, limit]);\r\n\r\n useEffect(() => {\r\n fetchInvoices();\r\n }, [fetchInvoices]);\r\n\r\n if (loading) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>Loading invoices...</div>;\r\n }\r\n\r\n if (error) {\r\n return <div style={{ padding: '20px', color: '#dc2626' }}>{error}</div>;\r\n }\r\n\r\n if (invoices.length === 0) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>No invoices found.</div>;\r\n }\r\n\r\n const formatAmount = (amount: number, currency: string) => {\r\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount / 100);\r\n };\r\n\r\n const STATUS_COLORS: Record<string, string> = {\r\n paid: '#059669',\r\n open: '#3b82f6',\r\n draft: '#6b7280',\r\n void: '#9ca3af',\r\n uncollectible: '#dc2626',\r\n };\r\n\r\n return (\r\n <div className={className} style={{ border: '1px solid #e5e7eb', borderRadius: '12px', overflow: 'hidden' }}>\r\n <div style={{ padding: '16px 24px', borderBottom: '1px solid #e5e7eb' }}>\r\n <h3 style={{ fontSize: '1.125rem', fontWeight: 600, margin: 0 }}>Invoice History</h3>\r\n </div>\r\n <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '14px' }}>\r\n <thead>\r\n <tr style={{ borderBottom: '1px solid #e5e7eb', background: '#f9fafb' }}>\r\n <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500, color: '#6b7280' }}>Invoice</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500, color: '#6b7280' }}>Date</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 500, color: '#6b7280' }}>Amount</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'center', fontWeight: 500, color: '#6b7280' }}>Status</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 500, color: '#6b7280' }}></th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n {invoices.map((invoice) => (\r\n <tr key={invoice.id} style={{ borderBottom: '1px solid #f3f4f6' }}>\r\n <td style={{ padding: '12px 16px' }}>{invoice.number || invoice.id.slice(0, 12)}</td>\r\n <td style={{ padding: '12px 16px', color: '#6b7280' }}>\r\n {new Date(invoice.created * 1000).toLocaleDateString()}\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'right', fontWeight: 500 }}>\r\n {formatAmount(invoice.amount, invoice.currency)}\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'center' }}>\r\n <span style={{\r\n padding: '2px 8px',\r\n borderRadius: '9999px',\r\n fontSize: '12px',\r\n fontWeight: 500,\r\n color: STATUS_COLORS[invoice.status] || '#6b7280',\r\n background: `${STATUS_COLORS[invoice.status] || '#6b7280'}15`,\r\n textTransform: 'capitalize',\r\n }}>\r\n {invoice.status}\r\n </span>\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'right' }}>\r\n {invoice.pdfUrl && (\r\n <a\r\n href={invoice.pdfUrl}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n style={{ color: '#3b82f6', textDecoration: 'none', fontSize: '13px' }}\r\n >\r\n PDF\r\n </a>\r\n )}\r\n </td>\r\n </tr>\r\n ))}\r\n </tbody>\r\n </table>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["useState","useEffect","useCallback","useState","useCallback","useEffect","useState","useState","useState","jsx","jsxs","useState","useState","jsx","useState","jsx","jsxs","Fragment","jsx","useState","useEffect","useCallback","jsx","jsxs","useState","useCallback","useEffect","STATUS_COLORS"]}
|
|
1
|
+
{"version":3,"sources":["../../src/payments/useSubscription.ts","../../src/payments/useCustomerAccess.ts","../../src/payments/BuyButton.tsx","../../src/payments/PricingTable.tsx","../../src/payments/ManageSubscriptionButton.tsx","../../src/payments/SubscriptionManager.tsx","../../src/payments/ProtectedContent.tsx","../../src/payments/InvoiceHistory.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\nimport { supabase } from '../core/supabase';\r\nimport { features } from '../core/config';\r\nimport type { SubscriptionTier, SubscriptionStatus } from '../core/types';\r\n\r\ninterface SubscriptionState {\r\n customerId: string | null;\r\n tier: SubscriptionTier;\r\n status: SubscriptionStatus | null;\r\n currentPeriodEnd: string | null;\r\n}\r\n\r\ninterface UseSubscriptionReturn {\r\n subscription: SubscriptionState | null;\r\n tier: SubscriptionTier;\r\n status: SubscriptionStatus | null;\r\n isActive: boolean;\r\n isPro: boolean;\r\n isBusiness: boolean;\r\n isEnterprise: boolean;\r\n canAccess: (requiredTier: SubscriptionTier) => boolean;\r\n loading: boolean;\r\n isConfigured: boolean;\r\n user: unknown;\r\n profile: unknown;\r\n isAuthenticated: boolean;\r\n refetch: () => Promise<void>;\r\n}\r\n\r\nconst TIER_LEVELS: Record<string, number> = {\r\n free: 0, starter: 1, creator: 2, pro: 3, business: 4, enterprise: 5,\r\n};\r\n\r\nexport function useSubscription(): UseSubscriptionReturn {\r\n const auth = useContext(AuthContext);\r\n const [subscription, setSubscription] = useState<SubscriptionState | null>(null);\r\n const [loading, setLoading] = useState(true);\r\n\r\n const fetchSubscription = useCallback(async () => {\r\n if (!auth?.profile) {\r\n setSubscription(null);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const sub: SubscriptionState = {\r\n customerId: auth.profile.stripe_customer_id || null,\r\n tier: (auth.profile.subscription_tier as SubscriptionTier) || 'free',\r\n status: (auth.profile.subscription_status as SubscriptionStatus) || null,\r\n currentPeriodEnd: auth.profile.subscription_period_end || null,\r\n };\r\n\r\n setSubscription(sub);\r\n setLoading(false);\r\n }, [auth?.profile]);\r\n\r\n useEffect(() => {\r\n fetchSubscription();\r\n }, [fetchSubscription]);\r\n\r\n useEffect(() => {\r\n if (!features.auth || !auth?.user?.id) return;\r\n\r\n const channel = supabase\r\n .channel(`subscription_${auth.user.id}`)\r\n .on(\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n 'postgres_changes' as any,\r\n {\r\n event: 'UPDATE',\r\n schema: 'public',\r\n table: 'user_profiles',\r\n filter: `id=eq.${auth.user.id}`,\r\n },\r\n () => {\r\n auth.refetchProfile(auth.user!.id);\r\n }\r\n )\r\n .subscribe();\r\n\r\n return () => {\r\n supabase.removeChannel(channel);\r\n };\r\n }, [auth?.user?.id, auth]);\r\n\r\n const tier = subscription?.tier || 'free';\r\n const status = subscription?.status || null;\r\n const isActive = !status || status === 'active' || status === 'trialing';\r\n const tierLevel = TIER_LEVELS[tier] ?? 0;\r\n\r\n return {\r\n subscription,\r\n tier,\r\n status,\r\n isActive,\r\n isPro: tierLevel >= TIER_LEVELS.pro,\r\n isBusiness: tierLevel >= TIER_LEVELS.business,\r\n isEnterprise: tierLevel >= TIER_LEVELS.enterprise,\r\n canAccess: (requiredTier: SubscriptionTier) => tierLevel >= (TIER_LEVELS[requiredTier] ?? 0),\r\n loading: loading || (auth?.loading ?? false),\r\n isConfigured: features.payments,\r\n user: auth?.user ?? null,\r\n profile: auth?.profile ?? null,\r\n isAuthenticated: Boolean(auth?.user),\r\n refetch: fetchSubscription,\r\n };\r\n}\r\n","import { useState, useEffect, useCallback } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface CustomerAccessReturn {\r\n loading: boolean;\r\n hasAccess: boolean;\r\n customer: Record<string, unknown> | null;\r\n subscription: Record<string, unknown> | null;\r\n error: string | null;\r\n refresh: () => Promise<void>;\r\n logout: () => void;\r\n}\r\n\r\nexport function useCustomerAccess(): CustomerAccessReturn {\r\n const [loading, setLoading] = useState(true);\r\n const [hasAccess, setHasAccess] = useState(false);\r\n const [customer, setCustomer] = useState<Record<string, unknown> | null>(null);\r\n const [subscription, setSubscription] = useState<Record<string, unknown> | null>(null);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const checkAccess = useCallback(async () => {\r\n setLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const customerId = typeof localStorage !== 'undefined'\r\n ? localStorage.getItem('stripeCustomerId')\r\n : null;\r\n const email = typeof localStorage !== 'undefined'\r\n ? localStorage.getItem('customerEmail')\r\n : null;\r\n\r\n if (!customerId && !email) {\r\n setHasAccess(false);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const result = await ezcoder.stripe.getCustomerStatus({ customerId: customerId || undefined, email: email || undefined });\r\n\r\n if (result.success) {\r\n setHasAccess(Boolean(result.hasAccess));\r\n setCustomer((result.customer as Record<string, unknown>) || null);\r\n setSubscription((result.subscription as Record<string, unknown>) || null);\r\n } else {\r\n setHasAccess(false);\r\n setError(result.error || 'Failed to check access');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Unknown error');\r\n setHasAccess(false);\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, []);\r\n\r\n useEffect(() => {\r\n checkAccess();\r\n }, [checkAccess]);\r\n\r\n const logout = useCallback(() => {\r\n if (typeof localStorage !== 'undefined') {\r\n localStorage.removeItem('stripeCustomerId');\r\n localStorage.removeItem('customerEmail');\r\n }\r\n setHasAccess(false);\r\n setCustomer(null);\r\n setSubscription(null);\r\n }, []);\r\n\r\n return { loading, hasAccess, customer, subscription, error, refresh: checkAccess, logout };\r\n}\r\n","import { useState } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface BuyButtonProps {\r\n priceId: string;\r\n productName?: string;\r\n className?: string;\r\n children?: React.ReactNode;\r\n disabled?: boolean;\r\n customerEmail?: string;\r\n quantity?: number;\r\n successUrl?: string;\r\n cancelUrl?: string;\r\n}\r\n\r\nexport function BuyButton({\r\n priceId,\r\n productName,\r\n className = '',\r\n children,\r\n disabled = false,\r\n customerEmail,\r\n quantity,\r\n successUrl,\r\n cancelUrl,\r\n}: BuyButtonProps) {\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const handleClick = async () => {\r\n setError(null);\r\n setLoading(true);\r\n\r\n try {\r\n const result = await ezcoder.stripe.createCheckout(priceId, {\r\n customerEmail,\r\n quantity,\r\n successUrl,\r\n cancelUrl,\r\n redirect: true,\r\n });\r\n\r\n if (!result.success) {\r\n setError(result.error || 'Checkout failed');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Checkout failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <div>\r\n <button\r\n onClick={handleClick}\r\n disabled={disabled || loading}\r\n className={className}\r\n style={!className ? {\r\n padding: '10px 20px',\r\n backgroundColor: disabled || loading ? '#9ca3af' : '#3b82f6',\r\n color: 'white',\r\n border: 'none',\r\n borderRadius: '6px',\r\n fontSize: '14px',\r\n fontWeight: 500,\r\n cursor: disabled || loading ? 'not-allowed' : 'pointer',\r\n } : undefined}\r\n >\r\n {loading ? 'Processing...' : children || `Buy ${productName || 'Now'}`}\r\n </button>\r\n {error && (\r\n <p style={{ color: '#dc2626', fontSize: '12px', marginTop: '4px' }}>{error}</p>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState } from 'react';\r\nimport { BuyButton } from './BuyButton';\r\n\r\ninterface PricingPlan {\r\n name: string;\r\n description: string;\r\n monthlyPriceId?: string;\r\n yearlyPriceId?: string;\r\n monthlyPrice: number;\r\n yearlyPrice?: number;\r\n features: string[];\r\n highlighted?: boolean;\r\n cta?: string;\r\n}\r\n\r\ninterface PricingTableProps {\r\n plans?: PricingPlan[];\r\n className?: string;\r\n customerEmail?: string;\r\n}\r\n\r\nconst DEFAULT_PLANS: PricingPlan[] = [\r\n {\r\n name: 'Starter',\r\n description: 'For individuals getting started',\r\n monthlyPrice: 0,\r\n features: ['Basic features', 'Community support'],\r\n cta: 'Get Started',\r\n },\r\n {\r\n name: 'Creator',\r\n description: 'For growing projects',\r\n monthlyPrice: 25,\r\n yearlyPrice: 270,\r\n features: ['All Starter features', 'Priority support', 'Advanced analytics'],\r\n highlighted: true,\r\n cta: 'Subscribe',\r\n },\r\n {\r\n name: 'Business',\r\n description: 'For teams and businesses',\r\n monthlyPrice: 50,\r\n yearlyPrice: 540,\r\n features: ['All Creator features', 'Team collaboration', 'Custom integrations'],\r\n cta: 'Subscribe',\r\n },\r\n {\r\n name: 'Enterprise',\r\n description: 'For large organizations',\r\n monthlyPrice: -1,\r\n features: ['All Business features', 'Dedicated support', 'SLA guarantee', 'Custom contracts'],\r\n cta: 'Contact Sales',\r\n },\r\n];\r\n\r\nexport function PricingTable({ plans = DEFAULT_PLANS, className = '', customerEmail }: PricingTableProps) {\r\n const [yearly, setYearly] = useState(false);\r\n\r\n return (\r\n <div className={className}>\r\n <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '32px', gap: '8px', alignItems: 'center' }}>\r\n <span style={{ fontSize: '14px', color: !yearly ? '#111' : '#6b7280', fontWeight: !yearly ? 600 : 400 }}>Monthly</span>\r\n <button\r\n onClick={() => setYearly(!yearly)}\r\n style={{\r\n width: '44px', height: '24px', borderRadius: '12px',\r\n backgroundColor: yearly ? '#3b82f6' : '#d1d5db',\r\n border: 'none', cursor: 'pointer', position: 'relative',\r\n }}\r\n >\r\n <span style={{\r\n width: '18px', height: '18px', borderRadius: '50%', backgroundColor: 'white',\r\n position: 'absolute', top: '3px', left: yearly ? '23px' : '3px',\r\n transition: 'left 0.2s',\r\n }} />\r\n </button>\r\n <span style={{ fontSize: '14px', color: yearly ? '#111' : '#6b7280', fontWeight: yearly ? 600 : 400 }}>\r\n Yearly <span style={{ color: '#059669', fontSize: '12px' }}>Save 10%</span>\r\n </span>\r\n </div>\r\n\r\n <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(plans.length, 4)}, 1fr)`, gap: '24px', maxWidth: '1200px', margin: '0 auto' }}>\r\n {plans.map((plan) => {\r\n const price = yearly && plan.yearlyPrice !== undefined ? plan.yearlyPrice / 12 : plan.monthlyPrice;\r\n const priceId = yearly ? plan.yearlyPriceId : plan.monthlyPriceId;\r\n\r\n return (\r\n <div\r\n key={plan.name}\r\n style={{\r\n border: plan.highlighted ? '2px solid #3b82f6' : '1px solid #e5e7eb',\r\n borderRadius: '12px', padding: '24px',\r\n backgroundColor: plan.highlighted ? '#eff6ff' : 'white',\r\n }}\r\n >\r\n <h3 style={{ fontSize: '1.25rem', fontWeight: 600, marginBottom: '4px' }}>{plan.name}</h3>\r\n <p style={{ color: '#6b7280', fontSize: '14px', marginBottom: '16px' }}>{plan.description}</p>\r\n\r\n <div style={{ marginBottom: '24px' }}>\r\n {plan.monthlyPrice === 0 ? (\r\n <span style={{ fontSize: '2rem', fontWeight: 700 }}>Free</span>\r\n ) : plan.monthlyPrice === -1 ? (\r\n <span style={{ fontSize: '1.5rem', fontWeight: 600 }}>Custom</span>\r\n ) : (\r\n <>\r\n <span style={{ fontSize: '2rem', fontWeight: 700 }}>${Math.round(price)}</span>\r\n <span style={{ color: '#6b7280', fontSize: '14px' }}>/mo</span>\r\n </>\r\n )}\r\n </div>\r\n\r\n <ul style={{ listStyle: 'none', padding: 0, marginBottom: '24px' }}>\r\n {plan.features.map((feature) => (\r\n <li key={feature} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px', fontSize: '14px' }}>\r\n <span style={{ color: '#059669' }}>✓</span> {feature}\r\n </li>\r\n ))}\r\n </ul>\r\n\r\n {priceId ? (\r\n <BuyButton priceId={priceId} productName={plan.name} customerEmail={customerEmail}>\r\n {plan.cta || 'Subscribe'}\r\n </BuyButton>\r\n ) : plan.monthlyPrice === 0 ? (\r\n <button style={{\r\n width: '100%', padding: '10px', border: '1px solid #d1d5db',\r\n borderRadius: '6px', background: 'white', cursor: 'pointer', fontSize: '14px',\r\n }}>\r\n {plan.cta || 'Get Started'}\r\n </button>\r\n ) : (\r\n <button style={{\r\n width: '100%', padding: '10px', border: '1px solid #d1d5db',\r\n borderRadius: '6px', background: 'white', cursor: 'pointer', fontSize: '14px',\r\n }}>\r\n {plan.cta || 'Contact Sales'}\r\n </button>\r\n )}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import { useState } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface ManageSubscriptionButtonProps {\r\n children?: React.ReactNode;\r\n className?: string;\r\n customerId?: string;\r\n}\r\n\r\nexport function ManageSubscriptionButton({ children, className = '', customerId }: ManageSubscriptionButtonProps) {\r\n const [loading, setLoading] = useState(false);\r\n\r\n const handleClick = async () => {\r\n const id = customerId || (typeof localStorage !== 'undefined' ? localStorage.getItem('stripeCustomerId') : null);\r\n if (!id) return;\r\n\r\n setLoading(true);\r\n await ezcoder.stripe.createPortalSession(id, { redirect: true });\r\n setLoading(false);\r\n };\r\n\r\n return (\r\n <button\r\n onClick={handleClick}\r\n disabled={loading}\r\n className={className}\r\n style={!className ? {\r\n padding: '8px 16px',\r\n border: '1px solid #d1d5db',\r\n borderRadius: '6px',\r\n background: 'white',\r\n cursor: loading ? 'not-allowed' : 'pointer',\r\n fontSize: '14px',\r\n } : undefined}\r\n >\r\n {loading ? 'Loading...' : children || 'Manage Subscription'}\r\n </button>\r\n );\r\n}\r\n","import { useSubscription } from './useSubscription';\r\nimport { ManageSubscriptionButton } from './ManageSubscriptionButton';\r\n\r\ninterface SubscriptionManagerProps {\r\n className?: string;\r\n}\r\n\r\nconst STATUS_COLORS: Record<string, string> = {\r\n active: '#059669',\r\n trialing: '#3b82f6',\r\n past_due: '#d97706',\r\n canceled: '#dc2626',\r\n unpaid: '#dc2626',\r\n};\r\n\r\nexport function SubscriptionManager({ className = '' }: SubscriptionManagerProps) {\r\n const { subscription, tier, status, isActive, loading } = useSubscription();\r\n\r\n if (loading) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>Loading subscription...</div>;\r\n }\r\n\r\n return (\r\n <div className={className} style={{ border: '1px solid #e5e7eb', borderRadius: '12px', padding: '24px' }}>\r\n <h3 style={{ fontSize: '1.125rem', fontWeight: 600, marginBottom: '16px' }}>Subscription</h3>\r\n\r\n <div style={{ display: 'grid', gap: '12px', marginBottom: '24px' }}>\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>Plan</span>\r\n <span style={{ fontWeight: 500, textTransform: 'capitalize' }}>{tier}</span>\r\n </div>\r\n\r\n {status && (\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>Status</span>\r\n <span style={{\r\n fontWeight: 500,\r\n color: STATUS_COLORS[status] || '#6b7280',\r\n textTransform: 'capitalize',\r\n }}>\r\n {status === 'past_due' ? 'Past Due' : status}\r\n </span>\r\n </div>\r\n )}\r\n\r\n {subscription?.currentPeriodEnd && (\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>{isActive ? 'Renews' : 'Expires'}</span>\r\n <span>{new Date(subscription.currentPeriodEnd).toLocaleDateString()}</span>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {subscription?.customerId && (\r\n <ManageSubscriptionButton customerId={subscription.customerId} />\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useCustomerAccess } from './useCustomerAccess';\r\nimport { useSubscription } from './useSubscription';\r\nimport type { SubscriptionTier } from '../core/types';\r\n\r\ninterface ProtectedContentProps {\r\n children: React.ReactNode;\r\n fallback?: React.ReactNode;\r\n loadingComponent?: React.ReactNode;\r\n requiredTier?: SubscriptionTier;\r\n}\r\n\r\nexport function ProtectedContent({ children, fallback, loadingComponent, requiredTier }: ProtectedContentProps) {\r\n const customerAccess = useCustomerAccess();\r\n const subscription = useSubscription();\r\n\r\n const loading = requiredTier ? subscription.loading : customerAccess.loading;\r\n\r\n if (loading) {\r\n return <>{loadingComponent || <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>Loading...</div>}</>;\r\n }\r\n\r\n const hasAccess = requiredTier\r\n ? subscription.isActive && subscription.canAccess(requiredTier)\r\n : customerAccess.hasAccess;\r\n\r\n if (!hasAccess) {\r\n return <>{fallback || <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>\r\n {requiredTier\r\n ? `This content requires a ${requiredTier} subscription or higher.`\r\n : 'This content requires a subscription.'}\r\n </div>}</>;\r\n }\r\n\r\n return <>{children}</>;\r\n}\r\n","import { useState, useEffect, useCallback } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface Invoice {\r\n id: string;\r\n number: string | null;\r\n amount: number;\r\n currency: string;\r\n status: string;\r\n created: number;\r\n hostedUrl: string | null;\r\n pdfUrl: string | null;\r\n}\r\n\r\ninterface InvoiceHistoryProps {\r\n customerId?: string;\r\n limit?: number;\r\n className?: string;\r\n}\r\n\r\nexport function InvoiceHistory({ customerId, limit = 10, className = '' }: InvoiceHistoryProps) {\r\n const [invoices, setInvoices] = useState<Invoice[]>([]);\r\n const [loading, setLoading] = useState(true);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const fetchInvoices = useCallback(async () => {\r\n const id = customerId || (typeof localStorage !== 'undefined' ? localStorage.getItem('stripeCustomerId') : null);\r\n if (!id) {\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const result = await ezcoder.stripe.getInvoices(id, { limit });\r\n if (result.success && result.invoices) {\r\n setInvoices(result.invoices as Invoice[]);\r\n } else {\r\n setError(result.error || 'Failed to load invoices');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Unknown error');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [customerId, limit]);\r\n\r\n useEffect(() => {\r\n fetchInvoices();\r\n }, [fetchInvoices]);\r\n\r\n if (loading) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>Loading invoices...</div>;\r\n }\r\n\r\n if (error) {\r\n return <div style={{ padding: '20px', color: '#dc2626' }}>{error}</div>;\r\n }\r\n\r\n if (invoices.length === 0) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>No invoices found.</div>;\r\n }\r\n\r\n const formatAmount = (amount: number, currency: string) => {\r\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount / 100);\r\n };\r\n\r\n const STATUS_COLORS: Record<string, string> = {\r\n paid: '#059669',\r\n open: '#3b82f6',\r\n draft: '#6b7280',\r\n void: '#9ca3af',\r\n uncollectible: '#dc2626',\r\n };\r\n\r\n return (\r\n <div className={className} style={{ border: '1px solid #e5e7eb', borderRadius: '12px', overflow: 'hidden' }}>\r\n <div style={{ padding: '16px 24px', borderBottom: '1px solid #e5e7eb' }}>\r\n <h3 style={{ fontSize: '1.125rem', fontWeight: 600, margin: 0 }}>Invoice History</h3>\r\n </div>\r\n <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '14px' }}>\r\n <thead>\r\n <tr style={{ borderBottom: '1px solid #e5e7eb', background: '#f9fafb' }}>\r\n <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500, color: '#6b7280' }}>Invoice</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500, color: '#6b7280' }}>Date</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 500, color: '#6b7280' }}>Amount</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'center', fontWeight: 500, color: '#6b7280' }}>Status</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 500, color: '#6b7280' }}></th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n {invoices.map((invoice) => (\r\n <tr key={invoice.id} style={{ borderBottom: '1px solid #f3f4f6' }}>\r\n <td style={{ padding: '12px 16px' }}>{invoice.number || invoice.id.slice(0, 12)}</td>\r\n <td style={{ padding: '12px 16px', color: '#6b7280' }}>\r\n {new Date(invoice.created * 1000).toLocaleDateString()}\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'right', fontWeight: 500 }}>\r\n {formatAmount(invoice.amount, invoice.currency)}\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'center' }}>\r\n <span style={{\r\n padding: '2px 8px',\r\n borderRadius: '9999px',\r\n fontSize: '12px',\r\n fontWeight: 500,\r\n color: STATUS_COLORS[invoice.status] || '#6b7280',\r\n background: `${STATUS_COLORS[invoice.status] || '#6b7280'}15`,\r\n textTransform: 'capitalize',\r\n }}>\r\n {invoice.status}\r\n </span>\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'right' }}>\r\n {invoice.pdfUrl && (\r\n <a\r\n href={invoice.pdfUrl}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n style={{ color: '#3b82f6', textDecoration: 'none', fontSize: '13px' }}\r\n >\r\n PDF\r\n </a>\r\n )}\r\n </td>\r\n </tr>\r\n ))}\r\n </tbody>\r\n </table>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["useState","useEffect","useCallback","useState","useCallback","useEffect","useState","useState","useState","jsx","jsxs","useState","useState","jsx","useState","jsx","jsxs","Fragment","jsx","useState","useEffect","useCallback","jsx","jsxs","useState","useCallback","useEffect","STATUS_COLORS"]}
|
package/dist/roles/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AuthContext
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-QHB7LGCA.js";
|
|
4
|
+
import "../chunk-HJ2EIZ4S.js";
|
|
5
5
|
import {
|
|
6
|
-
features,
|
|
7
6
|
supabase
|
|
8
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-I2YGB7Z6.js";
|
|
8
|
+
import {
|
|
9
|
+
features
|
|
10
|
+
} from "../chunk-LIUE7M7K.js";
|
|
9
11
|
|
|
10
12
|
// src/roles/useRoles.ts
|
|
11
13
|
import { useState, useEffect, useCallback, useContext } from "react";
|
package/dist/roles/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/roles/useRoles.ts","../../src/roles/RoleGate.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\r\nimport { supabase } from '../core/supabase';\r\nimport { features } from '../core/config';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\n\r\ninterface RoleData {\r\n role_name: string;\r\n display_name: string;\r\n permissions: string[];\r\n can_access_routes: string[];\r\n}\r\n\r\ninterface UseRolesReturn {\r\n roles: string[];\r\n permissions: string[];\r\n accessibleRoutes: string[];\r\n loading: boolean;\r\n error: string | null;\r\n hasRole: (name: string) => boolean;\r\n hasAnyRole: (names: string[]) => boolean;\r\n hasAllRoles: (names: string[]) => boolean;\r\n hasPermission: (perm: string) => boolean;\r\n hasAnyPermission: (perms: string[]) => boolean;\r\n hasAllPermissions: (perms: string[]) => boolean;\r\n canAccessRoute: (path: string) => boolean;\r\n refreshRoles: () => Promise<void>;\r\n}\r\n\r\nexport function useRoles(): UseRolesReturn {\r\n const auth = useContext(AuthContext);\r\n const [roles, setRoles] = useState<string[]>([]);\r\n const [permissions, setPermissions] = useState<string[]>([]);\r\n const [accessibleRoutes, setAccessibleRoutes] = useState<string[]>([]);\r\n const [loading, setLoading] = useState(true);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const fetchRoles = useCallback(async () => {\r\n if (!auth?.user?.id || !features.auth) {\r\n setRoles([]);\r\n setPermissions([]);\r\n setAccessibleRoutes([]);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n try {\r\n const { data, error: rpcError } = await supabase.rpc('get_user_roles', {\r\n user_uuid: auth.user.id,\r\n });\r\n\r\n if (rpcError) {\r\n setError(rpcError.message);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const roleData = (data || []) as RoleData[];\r\n const allRoles = roleData.map((r) => r.role_name);\r\n const allPerms = [...new Set(roleData.flatMap((r) => r.permissions || []))];\r\n const allRoutes = [...new Set(roleData.flatMap((r) => r.can_access_routes || []))];\r\n\r\n setRoles(allRoles);\r\n setPermissions(allPerms);\r\n setAccessibleRoutes(allRoutes);\r\n setError(null);\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Failed to fetch roles');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [auth?.user?.id]);\r\n\r\n useEffect(() => {\r\n fetchRoles();\r\n }, [fetchRoles]);\r\n\r\n const hasRole = useCallback((name: string) => roles.includes(name), [roles]);\r\n const hasAnyRole = useCallback((names: string[]) => names.some((n) => roles.includes(n)), [roles]);\r\n const hasAllRoles = useCallback((names: string[]) => names.every((n) => roles.includes(n)), [roles]);\r\n const hasPermission = useCallback((perm: string) => permissions.includes(perm), [permissions]);\r\n const hasAnyPermission = useCallback((perms: string[]) => perms.some((p) => permissions.includes(p)), [permissions]);\r\n const hasAllPermissions = useCallback((perms: string[]) => perms.every((p) => permissions.includes(p)), [permissions]);\r\n\r\n const canAccessRoute = useCallback((path: string) => {\r\n return accessibleRoutes.some((route) => {\r\n if (route.endsWith('/*')) {\r\n return path.startsWith(route.slice(0, -2));\r\n }\r\n return route === path;\r\n });\r\n }, [accessibleRoutes]);\r\n\r\n return {\r\n roles, permissions, accessibleRoutes, loading, error,\r\n hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions,\r\n canAccessRoute, refreshRoles: fetchRoles,\r\n };\r\n}\r\n","import { useRoles } from './useRoles';\r\n\r\ninterface RoleGateProps {\r\n children: React.ReactNode;\r\n roles?: string[];\r\n permissions?: string[];\r\n requireAll?: boolean;\r\n fallback?: React.ReactNode;\r\n loadingFallback?: React.ReactNode;\r\n}\r\n\r\ninterface RouteGateProps {\r\n children: React.ReactNode;\r\n route: string;\r\n fallback?: React.ReactNode;\r\n loadingFallback?: React.ReactNode;\r\n}\r\n\r\nexport function RoleGate({\r\n children,\r\n roles: requiredRoles,\r\n permissions: requiredPerms,\r\n requireAll = false,\r\n fallback = null,\r\n loadingFallback = null,\r\n}: RoleGateProps) {\r\n const { hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions, loading } = useRoles();\r\n\r\n if (loading) return <>{loadingFallback}</>;\r\n\r\n let hasAccess = true;\r\n\r\n if (requiredRoles?.length) {\r\n hasAccess = requireAll ? hasAllRoles(requiredRoles) : hasAnyRole(requiredRoles);\r\n }\r\n\r\n if (hasAccess && requiredPerms?.length) {\r\n hasAccess = requireAll ? hasAllPermissions(requiredPerms) : hasAnyPermission(requiredPerms);\r\n }\r\n\r\n if (!hasAccess && !requiredRoles?.length && !requiredPerms?.length) {\r\n hasAccess = true;\r\n }\r\n\r\n // Use individual checks to satisfy linter\r\n void hasRole;\r\n void hasPermission;\r\n\r\n return hasAccess ? <>{children}</> : <>{fallback}</>;\r\n}\r\n\r\nexport function RouteGate({ children, route, fallback = null, loadingFallback = null }: RouteGateProps) {\r\n const { canAccessRoute, loading } = useRoles();\r\n\r\n if (loading) return <>{loadingFallback}</>;\r\n\r\n return canAccessRoute(route) ? <>{children}</> : <>{fallback}</>;\r\n}\r\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/roles/useRoles.ts","../../src/roles/RoleGate.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\r\nimport { supabase } from '../core/supabase';\r\nimport { features } from '../core/config';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\n\r\ninterface RoleData {\r\n role_name: string;\r\n display_name: string;\r\n permissions: string[];\r\n can_access_routes: string[];\r\n}\r\n\r\ninterface UseRolesReturn {\r\n roles: string[];\r\n permissions: string[];\r\n accessibleRoutes: string[];\r\n loading: boolean;\r\n error: string | null;\r\n hasRole: (name: string) => boolean;\r\n hasAnyRole: (names: string[]) => boolean;\r\n hasAllRoles: (names: string[]) => boolean;\r\n hasPermission: (perm: string) => boolean;\r\n hasAnyPermission: (perms: string[]) => boolean;\r\n hasAllPermissions: (perms: string[]) => boolean;\r\n canAccessRoute: (path: string) => boolean;\r\n refreshRoles: () => Promise<void>;\r\n}\r\n\r\nexport function useRoles(): UseRolesReturn {\r\n const auth = useContext(AuthContext);\r\n const [roles, setRoles] = useState<string[]>([]);\r\n const [permissions, setPermissions] = useState<string[]>([]);\r\n const [accessibleRoutes, setAccessibleRoutes] = useState<string[]>([]);\r\n const [loading, setLoading] = useState(true);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const fetchRoles = useCallback(async () => {\r\n if (!auth?.user?.id || !features.auth) {\r\n setRoles([]);\r\n setPermissions([]);\r\n setAccessibleRoutes([]);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n try {\r\n const { data, error: rpcError } = await supabase.rpc('get_user_roles', {\r\n user_uuid: auth.user.id,\r\n });\r\n\r\n if (rpcError) {\r\n setError(rpcError.message);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const roleData = (data || []) as RoleData[];\r\n const allRoles = roleData.map((r) => r.role_name);\r\n const allPerms = [...new Set(roleData.flatMap((r) => r.permissions || []))];\r\n const allRoutes = [...new Set(roleData.flatMap((r) => r.can_access_routes || []))];\r\n\r\n setRoles(allRoles);\r\n setPermissions(allPerms);\r\n setAccessibleRoutes(allRoutes);\r\n setError(null);\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Failed to fetch roles');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [auth?.user?.id]);\r\n\r\n useEffect(() => {\r\n fetchRoles();\r\n }, [fetchRoles]);\r\n\r\n const hasRole = useCallback((name: string) => roles.includes(name), [roles]);\r\n const hasAnyRole = useCallback((names: string[]) => names.some((n) => roles.includes(n)), [roles]);\r\n const hasAllRoles = useCallback((names: string[]) => names.every((n) => roles.includes(n)), [roles]);\r\n const hasPermission = useCallback((perm: string) => permissions.includes(perm), [permissions]);\r\n const hasAnyPermission = useCallback((perms: string[]) => perms.some((p) => permissions.includes(p)), [permissions]);\r\n const hasAllPermissions = useCallback((perms: string[]) => perms.every((p) => permissions.includes(p)), [permissions]);\r\n\r\n const canAccessRoute = useCallback((path: string) => {\r\n return accessibleRoutes.some((route) => {\r\n if (route.endsWith('/*')) {\r\n return path.startsWith(route.slice(0, -2));\r\n }\r\n return route === path;\r\n });\r\n }, [accessibleRoutes]);\r\n\r\n return {\r\n roles, permissions, accessibleRoutes, loading, error,\r\n hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions,\r\n canAccessRoute, refreshRoles: fetchRoles,\r\n };\r\n}\r\n","import { useRoles } from './useRoles';\r\n\r\ninterface RoleGateProps {\r\n children: React.ReactNode;\r\n roles?: string[];\r\n permissions?: string[];\r\n requireAll?: boolean;\r\n fallback?: React.ReactNode;\r\n loadingFallback?: React.ReactNode;\r\n}\r\n\r\ninterface RouteGateProps {\r\n children: React.ReactNode;\r\n route: string;\r\n fallback?: React.ReactNode;\r\n loadingFallback?: React.ReactNode;\r\n}\r\n\r\nexport function RoleGate({\r\n children,\r\n roles: requiredRoles,\r\n permissions: requiredPerms,\r\n requireAll = false,\r\n fallback = null,\r\n loadingFallback = null,\r\n}: RoleGateProps) {\r\n const { hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions, loading } = useRoles();\r\n\r\n if (loading) return <>{loadingFallback}</>;\r\n\r\n let hasAccess = true;\r\n\r\n if (requiredRoles?.length) {\r\n hasAccess = requireAll ? hasAllRoles(requiredRoles) : hasAnyRole(requiredRoles);\r\n }\r\n\r\n if (hasAccess && requiredPerms?.length) {\r\n hasAccess = requireAll ? hasAllPermissions(requiredPerms) : hasAnyPermission(requiredPerms);\r\n }\r\n\r\n if (!hasAccess && !requiredRoles?.length && !requiredPerms?.length) {\r\n hasAccess = true;\r\n }\r\n\r\n // Use individual checks to satisfy linter\r\n void hasRole;\r\n void hasPermission;\r\n\r\n return hasAccess ? <>{children}</> : <>{fallback}</>;\r\n}\r\n\r\nexport function RouteGate({ children, route, fallback = null, loadingFallback = null }: RouteGateProps) {\r\n const { canAccessRoute, loading } = useRoles();\r\n\r\n if (loading) return <>{loadingFallback}</>;\r\n\r\n return canAccessRoute(route) ? <>{children}</> : <>{fallback}</>;\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/storage/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AuthContext
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-QHB7LGCA.js";
|
|
4
|
+
import "../chunk-HJ2EIZ4S.js";
|
|
5
5
|
import {
|
|
6
|
-
env,
|
|
7
|
-
features,
|
|
8
6
|
supabase
|
|
9
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-I2YGB7Z6.js";
|
|
8
|
+
import {
|
|
9
|
+
env,
|
|
10
|
+
features
|
|
11
|
+
} from "../chunk-LIUE7M7K.js";
|
|
10
12
|
|
|
11
13
|
// src/storage/useStorage.ts
|
|
12
14
|
import { useState, useCallback, useContext } from "react";
|