@primstack/cli 0.0.1
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/generators/crud/templates/drizzle-table.ts.template +12 -0
- package/dist/generators/crud/templates/handlers.ts.template +136 -0
- package/dist/generators/crud/templates/routes.ts.template +21 -0
- package/dist/generators/crud/templates/schema.ts.template +20 -0
- package/dist/index.js +9618 -0
- package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/index.ts.template +79 -0
- package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/types.ts.template +12 -0
- package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/index.ts.template +62 -0
- package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/types.ts.template +12 -0
- package/dist/integrations/analytics/providers/posthog/templates/src/analytics/index.ts.template +67 -0
- package/dist/integrations/analytics/providers/posthog/templates/src/analytics/types.ts.template +12 -0
- package/dist/integrations/auth/providers/authjoy/templates/api/src/middleware/auth.ts.template +89 -0
- package/dist/integrations/auth/providers/authjoy/templates/api/src/routes/auth.ts.template +27 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/components/AuthProvider.tsx.template +40 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/hooks/use-auth.ts.template +71 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/lib/auth.ts.template +59 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/pages/account.tsx.template +84 -0
- package/dist/integrations/auth/providers/authjoy/templates/web/src/pages/login.tsx.template +73 -0
- package/dist/integrations/cache/providers/memory/templates/src/cache/index.ts.template +43 -0
- package/dist/integrations/cache/providers/memory/templates/src/cache/types.ts.template +3 -0
- package/dist/integrations/cache/providers/redis/templates/src/cache/index.ts.template +37 -0
- package/dist/integrations/cache/providers/redis/templates/src/cache/types.ts.template +3 -0
- package/dist/integrations/cache/providers/valkey/templates/src/cache/index.ts.template +38 -0
- package/dist/integrations/cache/providers/valkey/templates/src/cache/types.ts.template +3 -0
- package/dist/integrations/db/providers/postgres/templates/drizzle.config.ts.template +10 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/index.ts.template +13 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/migrate.ts.template +19 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/postgres/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/db/providers/sqlite/templates/drizzle.config.ts.template +10 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/index.ts.template +10 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/sqlite/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/db/providers/supabase/templates/drizzle.config.ts.template +10 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/index.ts.template +13 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/migrate.ts.template +19 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/supabase/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/db/providers/turso/templates/drizzle.config.ts.template +11 -0
- package/dist/integrations/db/providers/turso/templates/src/db/index.ts.template +14 -0
- package/dist/integrations/db/providers/turso/templates/src/db/schema/index.ts.template +1 -0
- package/dist/integrations/db/providers/turso/templates/src/db/schema/users.ts.template +12 -0
- package/dist/integrations/db/providers/turso/templates/src/db/seed.ts.template +28 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/index.ts.template +24 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/templates/index.ts.template +1 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/templates/welcome.ts.template +7 -0
- package/dist/integrations/email/providers/nodemailer/templates/src/email/types.ts.template +7 -0
- package/dist/integrations/email/providers/resend/templates/src/email/index.ts.template +18 -0
- package/dist/integrations/email/providers/resend/templates/src/email/templates/index.ts.template +1 -0
- package/dist/integrations/email/providers/resend/templates/src/email/templates/welcome.ts.template +7 -0
- package/dist/integrations/email/providers/resend/templates/src/email/types.ts.template +7 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/index.ts.template +16 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/templates/index.ts.template +1 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/templates/welcome.ts.template +7 -0
- package/dist/integrations/email/providers/sendgrid/templates/src/email/types.ts.template +7 -0
- package/dist/integrations/flags/providers/local/templates/api/src/lib/flags.ts.template +97 -0
- package/dist/integrations/flags/providers/local/templates/api/src/routes/flags.ts.template +36 -0
- package/dist/integrations/flags/providers/local/templates/flags.json.template +8 -0
- package/dist/integrations/flags/providers/local/templates/web/src/hooks/use-flag.ts.template +60 -0
- package/dist/integrations/logging/providers/axiom/templates/src/logging/index.ts.template +56 -0
- package/dist/integrations/logging/providers/axiom/templates/src/logging/types.ts.template +5 -0
- package/dist/integrations/logging/providers/pino/templates/src/logging/index.ts.template +21 -0
- package/dist/integrations/logging/providers/pino/templates/src/logging/types.ts.template +5 -0
- package/dist/integrations/logging/providers/winston/templates/src/logging/index.ts.template +30 -0
- package/dist/integrations/logging/providers/winston/templates/src/logging/types.ts.template +5 -0
- package/dist/integrations/monitor/providers/datadog/templates/src/monitor/index.ts.template +78 -0
- package/dist/integrations/monitor/providers/datadog/templates/src/monitor/types.ts.template +12 -0
- package/dist/integrations/monitor/providers/newrelic/templates/src/monitor/index.ts.template +60 -0
- package/dist/integrations/monitor/providers/newrelic/templates/src/monitor/types.ts.template +12 -0
- package/dist/integrations/monitor/providers/sentry/templates/src/monitor/index.ts.template +70 -0
- package/dist/integrations/monitor/providers/sentry/templates/src/monitor/types.ts.template +12 -0
- package/dist/integrations/queue/providers/bullmq/templates/src/queue/index.ts.template +56 -0
- package/dist/integrations/queue/providers/bullmq/templates/src/queue/types.ts.template +10 -0
- package/dist/integrations/queue/providers/memory/templates/src/queue/index.ts.template +73 -0
- package/dist/integrations/queue/providers/memory/templates/src/queue/types.ts.template +10 -0
- package/dist/integrations/queue/providers/pgboss/templates/src/queue/index.ts.template +34 -0
- package/dist/integrations/queue/providers/pgboss/templates/src/queue/types.ts.template +10 -0
- package/dist/integrations/ratelimit/providers/memory/templates/src/ratelimit/index.ts.template +95 -0
- package/dist/integrations/ratelimit/providers/memory/templates/src/ratelimit/types.ts.template +12 -0
- package/dist/integrations/ratelimit/providers/rate-limiter-flexible/templates/src/ratelimit/index.ts.template +80 -0
- package/dist/integrations/ratelimit/providers/rate-limiter-flexible/templates/src/ratelimit/types.ts.template +12 -0
- package/dist/integrations/ratelimit/providers/upstash/templates/src/ratelimit/index.ts.template +67 -0
- package/dist/integrations/ratelimit/providers/upstash/templates/src/ratelimit/types.ts.template +12 -0
- package/dist/integrations/schedule/providers/bullmq/templates/src/schedule/index.ts.template +81 -0
- package/dist/integrations/schedule/providers/bullmq/templates/src/schedule/types.ts.template +10 -0
- package/dist/integrations/schedule/providers/croner/templates/src/schedule/index.ts.template +47 -0
- package/dist/integrations/schedule/providers/croner/templates/src/schedule/types.ts.template +10 -0
- package/dist/integrations/schedule/providers/node-cron/templates/src/schedule/index.ts.template +45 -0
- package/dist/integrations/schedule/providers/node-cron/templates/src/schedule/types.ts.template +10 -0
- package/dist/integrations/search/providers/algolia/templates/src/search/index.ts.template +52 -0
- package/dist/integrations/search/providers/algolia/templates/src/search/types.ts.template +18 -0
- package/dist/integrations/search/providers/meilisearch/templates/src/search/index.ts.template +49 -0
- package/dist/integrations/search/providers/meilisearch/templates/src/search/types.ts.template +18 -0
- package/dist/integrations/search/providers/typesense/templates/src/search/index.ts.template +71 -0
- package/dist/integrations/search/providers/typesense/templates/src/search/types.ts.template +35 -0
- package/dist/integrations/storage/providers/local/templates/src/storage/index.ts.template +69 -0
- package/dist/integrations/storage/providers/local/templates/src/storage/types.ts.template +12 -0
- package/dist/integrations/storage/providers/r2/templates/src/storage/index.ts.template +80 -0
- package/dist/integrations/storage/providers/r2/templates/src/storage/types.ts.template +12 -0
- package/dist/integrations/storage/providers/s3/templates/src/storage/index.ts.template +78 -0
- package/dist/integrations/storage/providers/s3/templates/src/storage/types.ts.template +12 -0
- package/dist/integrations/stripe/templates/api/src/lib/stripe.ts.template +259 -0
- package/dist/integrations/stripe/templates/api/src/routes/stripe-webhooks.ts.template +284 -0
- package/dist/integrations/stripe/templates/api/stripe.config.ts.template +178 -0
- package/dist/integrations/stripe/templates/shared/src/pricing.ts.template +117 -0
- package/dist/integrations/stripe/templates/shared/src/stripe-types.ts.template +133 -0
- package/dist/integrations/stripe/templates/web/src/components/billing-settings.tsx.template +123 -0
- package/dist/integrations/stripe/templates/web/src/components/pricing-cards.tsx.template +115 -0
- package/dist/integrations/stripe/templates/web/src/pages/pricing.tsx.template +95 -0
- package/dist/templates/api/fastify/.env.example.template +7 -0
- package/dist/templates/api/fastify/.gitignore.template +24 -0
- package/dist/templates/api/fastify/package.json.template +23 -0
- package/dist/templates/api/fastify/src/index.ts.template +52 -0
- package/dist/templates/api/fastify/src/lib/env.ts.template +20 -0
- package/dist/templates/api/fastify/src/routes/health.ts.template +12 -0
- package/dist/templates/api/fastify/tsconfig.json.template +18 -0
- package/dist/templates/api/fastify-postgres/.env.example.template +10 -0
- package/dist/templates/api/fastify-postgres/drizzle.config.ts.template +10 -0
- package/dist/templates/api/fastify-postgres/package.json.template +16 -0
- package/dist/templates/api/fastify-postgres/src/db/index.ts.template +9 -0
- package/dist/templates/api/fastify-postgres/src/db/schema/users.ts.template +12 -0
- package/dist/templates/api/fastify-sqlite/.env.example.template +10 -0
- package/dist/templates/api/fastify-sqlite/drizzle.config.ts.template +10 -0
- package/dist/templates/api/fastify-sqlite/package.json.template +16 -0
- package/dist/templates/api/fastify-sqlite/src/db/index.ts.template +6 -0
- package/dist/templates/api/fastify-sqlite/src/db/schema/users.ts.template +12 -0
- package/dist/templates/api/fastify-supabase/.env.example.template +10 -0
- package/dist/templates/api/fastify-supabase/drizzle.config.ts.template +10 -0
- package/dist/templates/api/fastify-supabase/package.json.template +15 -0
- package/dist/templates/api/fastify-supabase/src/db/index.ts.template +9 -0
- package/dist/templates/api/fastify-supabase/src/db/schema/users.ts.template +12 -0
- package/dist/templates/api/fastify-turso/.env.example.template +11 -0
- package/dist/templates/api/fastify-turso/drizzle.config.ts.template +11 -0
- package/dist/templates/api/fastify-turso/package.json.template +15 -0
- package/dist/templates/api/fastify-turso/src/db/index.ts.template +10 -0
- package/dist/templates/api/fastify-turso/src/db/schema/users.ts.template +12 -0
- package/dist/templates/fullstack/api/.env.example.template +10 -0
- package/dist/templates/fullstack/api/.gitignore.template +4 -0
- package/dist/templates/fullstack/api/drizzle.config.ts.template +14 -0
- package/dist/templates/fullstack/api/package.json.template +33 -0
- package/dist/templates/fullstack/api/src/db/index.ts.template +13 -0
- package/dist/templates/fullstack/api/src/db/schema/api-keys.ts.template +19 -0
- package/dist/templates/fullstack/api/src/db/schema/audit-logs.ts.template +23 -0
- package/dist/templates/fullstack/api/src/db/schema/index.ts.template +8 -0
- package/dist/templates/fullstack/api/src/db/schema/invites.ts.template +19 -0
- package/dist/templates/fullstack/api/src/db/schema/memberships.ts.template +16 -0
- package/dist/templates/fullstack/api/src/db/schema/organizations.ts.template +13 -0
- package/dist/templates/fullstack/api/src/db/schema/plans.ts.template +29 -0
- package/dist/templates/fullstack/api/src/db/schema/subscriptions.ts.template +38 -0
- package/dist/templates/fullstack/api/src/db/schema/users.ts.template +14 -0
- package/dist/templates/fullstack/api/src/index.ts.template +54 -0
- package/dist/templates/fullstack/api/src/lib/env.ts.template +22 -0
- package/dist/templates/fullstack/api/src/routes/health.ts.template +14 -0
- package/dist/templates/fullstack/api/tsconfig.json.template +15 -0
- package/dist/templates/fullstack/root/.gitignore.template +26 -0
- package/dist/templates/fullstack/root/package.json.template +15 -0
- package/dist/templates/fullstack/root/pnpm-workspace.yaml.template +3 -0
- package/dist/templates/fullstack/root/turbo.json.template +17 -0
- package/dist/templates/fullstack/shared/package.json.template +36 -0
- package/dist/templates/fullstack/shared/src/index.ts.template +8 -0
- package/dist/templates/fullstack/shared/src/schemas/api-key.ts.template +28 -0
- package/dist/templates/fullstack/shared/src/schemas/audit-log.ts.template +41 -0
- package/dist/templates/fullstack/shared/src/schemas/index.ts.template +8 -0
- package/dist/templates/fullstack/shared/src/schemas/invite.ts.template +25 -0
- package/dist/templates/fullstack/shared/src/schemas/membership.ts.template +20 -0
- package/dist/templates/fullstack/shared/src/schemas/organization.ts.template +18 -0
- package/dist/templates/fullstack/shared/src/schemas/plan.ts.template +38 -0
- package/dist/templates/fullstack/shared/src/schemas/subscription.ts.template +56 -0
- package/dist/templates/fullstack/shared/src/schemas/user.ts.template +21 -0
- package/dist/templates/fullstack/shared/src/types/index.ts.template +75 -0
- package/dist/templates/fullstack/shared/src/validators/index.ts.template +53 -0
- package/dist/templates/fullstack/shared/tsconfig.json.template +17 -0
- package/dist/templates/fullstack/web/.gitignore.template +3 -0
- package/dist/templates/fullstack/web/index.html.template +13 -0
- package/dist/templates/fullstack/web/package.json.template +23 -0
- package/dist/templates/fullstack/web/src/App.tsx.template +47 -0
- package/dist/templates/fullstack/web/src/index.css.template +54 -0
- package/dist/templates/fullstack/web/src/main.tsx.template +10 -0
- package/dist/templates/fullstack/web/src/vite-env.d.ts.template +1 -0
- package/dist/templates/fullstack/web/tsconfig.json.template +21 -0
- package/dist/templates/fullstack/web/tsconfig.node.json.template +11 -0
- package/dist/templates/fullstack/web/vite.config.ts.template +15 -0
- package/dist/templates/hosted/root/.env.local.template +13 -0
- package/dist/templates/hosted/root/.gitignore.template +32 -0
- package/dist/templates/hosted/root/CLAUDE.md.template +139 -0
- package/dist/templates/hosted/root/drizzle.config.ts.template +10 -0
- package/dist/templates/hosted/root/next.config.ts.template +15 -0
- package/dist/templates/hosted/root/package.json.template +40 -0
- package/dist/templates/hosted/root/postcss.config.mjs.template +9 -0
- package/dist/templates/hosted/root/primstack.config.json.template +5 -0
- package/dist/templates/hosted/root/tailwind.config.ts.template +14 -0
- package/dist/templates/hosted/root/tsconfig.json.template +25 -0
- package/dist/templates/hosted/root/wrangler.toml.template +9 -0
- package/dist/templates/hosted/src/app/actions/example.ts.template +50 -0
- package/dist/templates/hosted/src/app/api/health/route.ts.template +5 -0
- package/dist/templates/hosted/src/app/auth/login/page.tsx.template +32 -0
- package/dist/templates/hosted/src/app/globals.css.template +59 -0
- package/dist/templates/hosted/src/app/layout.tsx.template +24 -0
- package/dist/templates/hosted/src/app/page.tsx.template +34 -0
- package/dist/templates/hosted/src/db/migrations/0000_initial.sql.template +43 -0
- package/dist/templates/hosted/src/db/schema.ts.template +52 -0
- package/dist/templates/hosted/src/env.d.ts.template +10 -0
- package/dist/templates/hosted/src/instrumentation.ts.template +6 -0
- package/dist/templates/hosted/src/lib/auth.ts.template +35 -0
- package/dist/templates/hosted/src/lib/db.ts.template +17 -0
- package/dist/templates/hosted/src/middleware.ts.template +6 -0
- package/package.json +46 -0
package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/index.ts.template
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { init, track, identify, groupIdentify as ampGroupIdentify, flush } from '@amplitude/analytics-node';
|
|
2
|
+
import { Identify } from '@amplitude/analytics-node';
|
|
3
|
+
import type { AnalyticsConfig, EventProperties, UserProperties } from './types.js';
|
|
4
|
+
|
|
5
|
+
let initialized = false;
|
|
6
|
+
|
|
7
|
+
export function initAnalytics(config?: Partial<AnalyticsConfig>): boolean {
|
|
8
|
+
const apiKey = config?.apiKey ?? process.env.AMPLITUDE_API_KEY;
|
|
9
|
+
if (!apiKey) {
|
|
10
|
+
console.warn('[analytics] AMPLITUDE_API_KEY not set — analytics disabled');
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
init(apiKey, {
|
|
15
|
+
logLevel: config?.debug ? 3 : 0,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
initialized = true;
|
|
19
|
+
|
|
20
|
+
if (config?.debug) {
|
|
21
|
+
console.log('[analytics] Amplitude initialized');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function trackEvent(
|
|
28
|
+
event: string,
|
|
29
|
+
properties?: EventProperties,
|
|
30
|
+
userId?: string,
|
|
31
|
+
): void {
|
|
32
|
+
if (!initialized) return;
|
|
33
|
+
|
|
34
|
+
track(event, properties, {
|
|
35
|
+
user_id: userId ?? 'anonymous',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function identifyUser(userId: string, properties?: UserProperties): void {
|
|
40
|
+
if (!initialized) return;
|
|
41
|
+
|
|
42
|
+
const identifyObj = new Identify();
|
|
43
|
+
if (properties) {
|
|
44
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
45
|
+
if (value !== null && value !== undefined) {
|
|
46
|
+
identifyObj.set(key, value);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
identify(identifyObj, { user_id: userId });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function groupIdentify(
|
|
55
|
+
groupType: string,
|
|
56
|
+
groupId: string,
|
|
57
|
+
properties?: UserProperties,
|
|
58
|
+
): void {
|
|
59
|
+
if (!initialized) return;
|
|
60
|
+
|
|
61
|
+
const identifyObj = new Identify();
|
|
62
|
+
if (properties) {
|
|
63
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
64
|
+
if (value !== null && value !== undefined) {
|
|
65
|
+
identifyObj.set(key, value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ampGroupIdentify(groupType, groupId, identifyObj);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function shutdownAnalytics(): Promise<void> {
|
|
74
|
+
if (!initialized) return;
|
|
75
|
+
await flush();
|
|
76
|
+
initialized = false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type { AnalyticsConfig, EventProperties, UserProperties } from './types.js';
|
package/dist/integrations/analytics/providers/amplitude/templates/src/analytics/types.ts.template
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface AnalyticsConfig {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
debug?: boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface EventProperties {
|
|
7
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UserProperties {
|
|
11
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
12
|
+
}
|
package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/index.ts.template
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import Mixpanel from 'mixpanel';
|
|
2
|
+
import type { AnalyticsConfig, EventProperties, UserProperties } from './types.js';
|
|
3
|
+
|
|
4
|
+
let client: Mixpanel.Mixpanel | null = null;
|
|
5
|
+
|
|
6
|
+
export function initAnalytics(config?: Partial<AnalyticsConfig>): boolean {
|
|
7
|
+
const token = config?.apiKey ?? process.env.MIXPANEL_TOKEN;
|
|
8
|
+
if (!token) {
|
|
9
|
+
console.warn('[analytics] MIXPANEL_TOKEN not set — analytics disabled');
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
client = Mixpanel.init(token, {
|
|
14
|
+
debug: config?.debug ?? false,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (config?.debug) {
|
|
18
|
+
console.log('[analytics] Mixpanel initialized');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function trackEvent(
|
|
25
|
+
event: string,
|
|
26
|
+
properties?: EventProperties,
|
|
27
|
+
userId?: string,
|
|
28
|
+
): void {
|
|
29
|
+
if (!client) return;
|
|
30
|
+
|
|
31
|
+
client.track(event, {
|
|
32
|
+
...properties,
|
|
33
|
+
distinct_id: userId ?? 'anonymous',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function identifyUser(userId: string, properties?: UserProperties): void {
|
|
38
|
+
if (!client) return;
|
|
39
|
+
|
|
40
|
+
client.people.set(userId, properties ?? {});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function groupIdentify(
|
|
44
|
+
groupType: string,
|
|
45
|
+
groupId: string,
|
|
46
|
+
properties?: UserProperties,
|
|
47
|
+
): void {
|
|
48
|
+
if (!client) return;
|
|
49
|
+
|
|
50
|
+
client.track('$group_identify', {
|
|
51
|
+
$group_type: groupType,
|
|
52
|
+
$group_id: groupId,
|
|
53
|
+
...properties,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function shutdownAnalytics(): Promise<void> {
|
|
58
|
+
// Mixpanel Node SDK sends events immediately; no shutdown needed
|
|
59
|
+
client = null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type { AnalyticsConfig, EventProperties, UserProperties } from './types.js';
|
package/dist/integrations/analytics/providers/mixpanel/templates/src/analytics/types.ts.template
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface AnalyticsConfig {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
debug?: boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface EventProperties {
|
|
7
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UserProperties {
|
|
11
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
12
|
+
}
|
package/dist/integrations/analytics/providers/posthog/templates/src/analytics/index.ts.template
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { PostHog } from 'posthog-node';
|
|
2
|
+
import type { AnalyticsConfig, EventProperties, UserProperties } from './types.js';
|
|
3
|
+
|
|
4
|
+
let client: PostHog | null = null;
|
|
5
|
+
|
|
6
|
+
export function initAnalytics(config?: Partial<AnalyticsConfig>): boolean {
|
|
7
|
+
const apiKey = config?.apiKey ?? process.env.POSTHOG_API_KEY;
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
console.warn('[analytics] POSTHOG_API_KEY not set — analytics disabled');
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const host = process.env.POSTHOG_HOST ?? 'https://us.i.posthog.com';
|
|
14
|
+
|
|
15
|
+
client = new PostHog(apiKey, { host });
|
|
16
|
+
|
|
17
|
+
if (config?.debug) {
|
|
18
|
+
console.log('[analytics] PostHog initialized');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function trackEvent(
|
|
25
|
+
event: string,
|
|
26
|
+
properties?: EventProperties,
|
|
27
|
+
userId?: string,
|
|
28
|
+
): void {
|
|
29
|
+
if (!client) return;
|
|
30
|
+
|
|
31
|
+
client.capture({
|
|
32
|
+
distinctId: userId ?? 'anonymous',
|
|
33
|
+
event,
|
|
34
|
+
properties,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function identifyUser(userId: string, properties?: UserProperties): void {
|
|
39
|
+
if (!client) return;
|
|
40
|
+
|
|
41
|
+
client.identify({
|
|
42
|
+
distinctId: userId,
|
|
43
|
+
properties,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function groupIdentify(
|
|
48
|
+
groupType: string,
|
|
49
|
+
groupId: string,
|
|
50
|
+
properties?: UserProperties,
|
|
51
|
+
): void {
|
|
52
|
+
if (!client) return;
|
|
53
|
+
|
|
54
|
+
client.groupIdentify({
|
|
55
|
+
groupType,
|
|
56
|
+
groupKey: groupId,
|
|
57
|
+
properties,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function shutdownAnalytics(): Promise<void> {
|
|
62
|
+
if (!client) return;
|
|
63
|
+
await client.shutdown();
|
|
64
|
+
client = null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type { AnalyticsConfig, EventProperties, UserProperties } from './types.js';
|
package/dist/integrations/analytics/providers/posthog/templates/src/analytics/types.ts.template
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface AnalyticsConfig {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
debug?: boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface EventProperties {
|
|
7
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UserProperties {
|
|
11
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
12
|
+
}
|
package/dist/integrations/auth/providers/authjoy/templates/api/src/middleware/auth.ts.template
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { FastifyRequest, FastifyReply } from 'fastify';
|
|
2
|
+
|
|
3
|
+
// Extend FastifyRequest to include user
|
|
4
|
+
declare module 'fastify' {
|
|
5
|
+
interface FastifyRequest {
|
|
6
|
+
user?: {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const AUTHJOY_API_URL = process.env.AUTHJOY_API_URL || 'https://api.authjoy.io';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Verify a token with AuthJoy
|
|
19
|
+
*/
|
|
20
|
+
async function verifyToken(token: string): Promise<Record<string, unknown> | null> {
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch(`${AUTHJOY_API_URL}/v1/verify`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
'Authorization': `Bearer ${token}`,
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return await response.json() as Record<string, unknown>;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Require authentication - returns 401 if not authenticated
|
|
42
|
+
*/
|
|
43
|
+
export async function requireAuth(
|
|
44
|
+
request: FastifyRequest,
|
|
45
|
+
reply: FastifyReply
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
48
|
+
|
|
49
|
+
if (!token) {
|
|
50
|
+
reply.status(401).send({ error: 'Unauthorized', message: 'No token provided' });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const user = await verifyToken(token);
|
|
55
|
+
|
|
56
|
+
if (!user) {
|
|
57
|
+
reply.status(401).send({ error: 'Unauthorized', message: 'Invalid token' });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
request.user = user as FastifyRequest['user'];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Optional authentication - attaches user if token is valid, continues otherwise
|
|
66
|
+
*/
|
|
67
|
+
export async function optionalAuth(
|
|
68
|
+
request: FastifyRequest,
|
|
69
|
+
_reply: FastifyReply
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
const token = request.headers.authorization?.replace('Bearer ', '');
|
|
72
|
+
|
|
73
|
+
if (!token) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const user = await verifyToken(token);
|
|
78
|
+
|
|
79
|
+
if (user) {
|
|
80
|
+
request.user = user as FastifyRequest['user'];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get the current user from request (after auth middleware)
|
|
86
|
+
*/
|
|
87
|
+
export function getCurrentUser(request: FastifyRequest) {
|
|
88
|
+
return request.user;
|
|
89
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FastifyPluginAsync } from 'fastify';
|
|
2
|
+
import { requireAuth, optionalAuth, getCurrentUser } from '../middleware/auth.js';
|
|
3
|
+
|
|
4
|
+
export const authRoutes: FastifyPluginAsync = async (fastify) => {
|
|
5
|
+
/**
|
|
6
|
+
* Get current authenticated user
|
|
7
|
+
* GET /auth/me
|
|
8
|
+
*/
|
|
9
|
+
fastify.get('/me', { preHandler: requireAuth }, async (request, reply) => {
|
|
10
|
+
const user = getCurrentUser(request);
|
|
11
|
+
return reply.send({ user });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check authentication status
|
|
16
|
+
* GET /auth/status
|
|
17
|
+
*/
|
|
18
|
+
fastify.get('/status', { preHandler: optionalAuth }, async (request, reply) => {
|
|
19
|
+
const user = getCurrentUser(request);
|
|
20
|
+
return reply.send({
|
|
21
|
+
authenticated: !!user,
|
|
22
|
+
user: user || null,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default authRoutes;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AuthJoyProvider } from '@authjoyio/react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface AuthProviderProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* AuthJoy authentication provider wrapper
|
|
10
|
+
*
|
|
11
|
+
* Wraps your app with AuthJoy context for authentication state management.
|
|
12
|
+
*
|
|
13
|
+
* Required environment variables:
|
|
14
|
+
* - VITE_AUTHJOY_CLIENT_ID: Your AuthJoy client ID
|
|
15
|
+
* - VITE_AUTHJOY_DOMAIN: Your AuthJoy domain (e.g., your-app.authjoy.io)
|
|
16
|
+
*/
|
|
17
|
+
export function AuthProvider({ children }: AuthProviderProps) {
|
|
18
|
+
const clientId = import.meta.env.VITE_AUTHJOY_CLIENT_ID;
|
|
19
|
+
const domain = import.meta.env.VITE_AUTHJOY_DOMAIN;
|
|
20
|
+
|
|
21
|
+
if (!clientId || !domain) {
|
|
22
|
+
console.warn(
|
|
23
|
+
'AuthJoy not configured. Set VITE_AUTHJOY_CLIENT_ID and VITE_AUTHJOY_DOMAIN environment variables.'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<AuthJoyProvider
|
|
29
|
+
config={{
|
|
30
|
+
clientId: clientId || '',
|
|
31
|
+
domain: domain || '',
|
|
32
|
+
redirectUri: window.location.origin,
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</AuthJoyProvider>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default AuthProvider;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useAuthJoy } from '@authjoyio/react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Authentication hook for accessing user state and auth actions
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* function ProfileButton() {
|
|
9
|
+
* const { user, isAuthenticated, login, logout } = useAuth();
|
|
10
|
+
*
|
|
11
|
+
* if (!isAuthenticated) {
|
|
12
|
+
* return <button onClick={() => login()}>Sign In</button>;
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* return (
|
|
16
|
+
* <div>
|
|
17
|
+
* <span>{user?.name}</span>
|
|
18
|
+
* <button onClick={() => logout()}>Sign Out</button>
|
|
19
|
+
* </div>
|
|
20
|
+
* );
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function useAuth() {
|
|
25
|
+
const authJoy = useAuthJoy();
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
/**
|
|
29
|
+
* The authenticated user object, or null if not authenticated
|
|
30
|
+
*/
|
|
31
|
+
user: authJoy.user,
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether authentication state is still loading
|
|
35
|
+
*/
|
|
36
|
+
isLoading: authJoy.isLoading,
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether the user is authenticated
|
|
40
|
+
*/
|
|
41
|
+
isAuthenticated: authJoy.isAuthenticated,
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Trigger the login flow
|
|
45
|
+
* @param options Optional login options (e.g., { screen_hint: 'signup' })
|
|
46
|
+
*/
|
|
47
|
+
login: authJoy.login,
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Log out the current user
|
|
51
|
+
* @param options Optional logout options (e.g., { returnTo: '/goodbye' })
|
|
52
|
+
*/
|
|
53
|
+
logout: authJoy.logout,
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get an access token for API calls
|
|
57
|
+
* @returns Promise<string> The access token
|
|
58
|
+
*/
|
|
59
|
+
getToken: authJoy.getToken,
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if the user has a specific permission/scope
|
|
63
|
+
* @param permission The permission to check
|
|
64
|
+
*/
|
|
65
|
+
hasPermission: (permission: string) => {
|
|
66
|
+
return authJoy.user?.permissions?.includes(permission) ?? false;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default useAuth;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthJoy configuration and utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const authConfig = {
|
|
6
|
+
clientId: import.meta.env.VITE_AUTHJOY_CLIENT_ID || '',
|
|
7
|
+
domain: import.meta.env.VITE_AUTHJOY_DOMAIN || '',
|
|
8
|
+
redirectUri: typeof window !== 'undefined' ? window.location.origin : '',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Make an authenticated API request
|
|
13
|
+
*
|
|
14
|
+
* @param url The URL to fetch
|
|
15
|
+
* @param options Fetch options
|
|
16
|
+
* @param getToken Function to get the current access token
|
|
17
|
+
* @returns The fetch response
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const { getToken } = useAuth();
|
|
22
|
+
* const response = await authFetch('/api/users', { method: 'GET' }, getToken);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export async function authFetch(
|
|
26
|
+
url: string,
|
|
27
|
+
options: RequestInit = {},
|
|
28
|
+
getToken: () => Promise<string>
|
|
29
|
+
): Promise<Response> {
|
|
30
|
+
const token = await getToken();
|
|
31
|
+
|
|
32
|
+
return fetch(url, {
|
|
33
|
+
...options,
|
|
34
|
+
headers: {
|
|
35
|
+
...options.headers,
|
|
36
|
+
Authorization: `Bearer ${token}`,
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create an authenticated fetch wrapper
|
|
44
|
+
*
|
|
45
|
+
* @param getToken Function to get the current access token
|
|
46
|
+
* @returns A fetch function that automatically adds auth headers
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const { getToken } = useAuth();
|
|
51
|
+
* const api = createAuthFetch(getToken);
|
|
52
|
+
* const response = await api('/api/users');
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function createAuthFetch(getToken: () => Promise<string>) {
|
|
56
|
+
return async (url: string, options: RequestInit = {}): Promise<Response> => {
|
|
57
|
+
return authFetch(url, options, getToken);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useAuth } from '../hooks/use-auth';
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardContent,
|
|
10
|
+
} from '@primstack/ui/card';
|
|
11
|
+
import { Avatar, AvatarImage, AvatarFallback } from '@primstack/ui/avatar';
|
|
12
|
+
import { Badge } from '@primstack/ui/badge';
|
|
13
|
+
import { Button } from '@primstack/ui/button';
|
|
14
|
+
import { Separator } from '@primstack/ui/separator';
|
|
15
|
+
import { useEffect } from 'react';
|
|
16
|
+
|
|
17
|
+
function getInitials(name?: string): string {
|
|
18
|
+
if (!name) return '?';
|
|
19
|
+
return name
|
|
20
|
+
.split(' ')
|
|
21
|
+
.map((part) => part[0])
|
|
22
|
+
.join('')
|
|
23
|
+
.toUpperCase()
|
|
24
|
+
.slice(0, 2);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function AccountPage() {
|
|
28
|
+
const { user, isAuthenticated, isLoading, logout } = useAuth();
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!isLoading && !isAuthenticated) {
|
|
32
|
+
window.location.href = '/login';
|
|
33
|
+
}
|
|
34
|
+
}, [isLoading, isAuthenticated]);
|
|
35
|
+
|
|
36
|
+
if (isLoading || !isAuthenticated) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex min-h-screen items-center justify-center p-4">
|
|
42
|
+
<Card className="w-full max-w-md">
|
|
43
|
+
<CardHeader className="items-center text-center">
|
|
44
|
+
<Avatar className="h-20 w-20">
|
|
45
|
+
<AvatarImage src={user?.picture} alt={user?.name} />
|
|
46
|
+
<AvatarFallback className="text-lg">
|
|
47
|
+
{getInitials(user?.name)}
|
|
48
|
+
</AvatarFallback>
|
|
49
|
+
</Avatar>
|
|
50
|
+
<CardTitle className="mt-4">{user?.name}</CardTitle>
|
|
51
|
+
<CardDescription>{user?.email}</CardDescription>
|
|
52
|
+
<Badge variant="outline" className="mt-2">
|
|
53
|
+
Authenticated
|
|
54
|
+
</Badge>
|
|
55
|
+
</CardHeader>
|
|
56
|
+
|
|
57
|
+
<Separator />
|
|
58
|
+
|
|
59
|
+
<CardContent className="space-y-4 pt-6">
|
|
60
|
+
<div className="space-y-1">
|
|
61
|
+
<p className="text-sm font-medium text-muted-foreground">Email</p>
|
|
62
|
+
<p className="text-sm">{user?.email}</p>
|
|
63
|
+
</div>
|
|
64
|
+
{user?.name && (
|
|
65
|
+
<div className="space-y-1">
|
|
66
|
+
<p className="text-sm font-medium text-muted-foreground">Name</p>
|
|
67
|
+
<p className="text-sm">{user.name}</p>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
<Separator />
|
|
72
|
+
|
|
73
|
+
<Button
|
|
74
|
+
variant="destructive"
|
|
75
|
+
className="w-full"
|
|
76
|
+
onClick={() => logout({ returnTo: window.location.origin })}
|
|
77
|
+
>
|
|
78
|
+
Sign Out
|
|
79
|
+
</Button>
|
|
80
|
+
</CardContent>
|
|
81
|
+
</Card>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useAuth } from '../hooks/use-auth';
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
CardDescription,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardFooter,
|
|
11
|
+
} from '@primstack/ui/card';
|
|
12
|
+
import { Button } from '@primstack/ui/button';
|
|
13
|
+
import { Separator } from '@primstack/ui/separator';
|
|
14
|
+
import { useEffect } from 'react';
|
|
15
|
+
|
|
16
|
+
export default function LoginPage() {
|
|
17
|
+
const { login, isAuthenticated, isLoading } = useAuth();
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (isAuthenticated) {
|
|
21
|
+
window.location.href = '/';
|
|
22
|
+
}
|
|
23
|
+
}, [isAuthenticated]);
|
|
24
|
+
|
|
25
|
+
if (isLoading || isAuthenticated) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex min-h-screen items-center justify-center p-4">
|
|
31
|
+
<Card className="w-full max-w-md">
|
|
32
|
+
<CardHeader className="text-center">
|
|
33
|
+
<CardTitle className="text-2xl">Welcome back</CardTitle>
|
|
34
|
+
<CardDescription>
|
|
35
|
+
Sign in to your account to continue.
|
|
36
|
+
</CardDescription>
|
|
37
|
+
</CardHeader>
|
|
38
|
+
|
|
39
|
+
<CardContent className="space-y-4">
|
|
40
|
+
<Button
|
|
41
|
+
className="w-full"
|
|
42
|
+
size="lg"
|
|
43
|
+
onClick={() => login()}
|
|
44
|
+
>
|
|
45
|
+
Sign In
|
|
46
|
+
</Button>
|
|
47
|
+
|
|
48
|
+
<div className="relative">
|
|
49
|
+
<Separator />
|
|
50
|
+
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-card px-2 text-xs text-muted-foreground">
|
|
51
|
+
or
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<Button
|
|
56
|
+
className="w-full"
|
|
57
|
+
variant="outline"
|
|
58
|
+
size="lg"
|
|
59
|
+
onClick={() => login({ screen_hint: 'signup' })}
|
|
60
|
+
>
|
|
61
|
+
Create Account
|
|
62
|
+
</Button>
|
|
63
|
+
</CardContent>
|
|
64
|
+
|
|
65
|
+
<CardFooter className="justify-center">
|
|
66
|
+
<p className="text-xs text-muted-foreground">
|
|
67
|
+
Secured by AuthJoy
|
|
68
|
+
</p>
|
|
69
|
+
</CardFooter>
|
|
70
|
+
</Card>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|