create-stackr 0.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/LICENSE +21 -0
- package/README.md +642 -0
- package/bin/cli.js +12 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +113 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/dependencies.d.ts +82 -0
- package/dist/config/dependencies.d.ts.map +1 -0
- package/dist/config/dependencies.js +82 -0
- package/dist/config/dependencies.js.map +1 -0
- package/dist/config/presets.d.ts +3 -0
- package/dist/config/presets.d.ts.map +1 -0
- package/dist/config/presets.js +174 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/generators/index.d.ts +40 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +130 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/onboarding.d.ts +8 -0
- package/dist/generators/onboarding.d.ts.map +1 -0
- package/dist/generators/onboarding.js +141 -0
- package/dist/generators/onboarding.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/features.d.ts +14 -0
- package/dist/prompts/features.d.ts.map +1 -0
- package/dist/prompts/features.js +96 -0
- package/dist/prompts/features.js.map +1 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +93 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/onboarding.d.ts +6 -0
- package/dist/prompts/onboarding.d.ts.map +1 -0
- package/dist/prompts/onboarding.js +37 -0
- package/dist/prompts/onboarding.js.map +1 -0
- package/dist/prompts/orm.d.ts +3 -0
- package/dist/prompts/orm.d.ts.map +1 -0
- package/dist/prompts/orm.js +23 -0
- package/dist/prompts/orm.js.map +1 -0
- package/dist/prompts/packageManager.d.ts +2 -0
- package/dist/prompts/packageManager.d.ts.map +1 -0
- package/dist/prompts/packageManager.js +18 -0
- package/dist/prompts/packageManager.js.map +1 -0
- package/dist/prompts/platform.d.ts +3 -0
- package/dist/prompts/platform.d.ts.map +1 -0
- package/dist/prompts/platform.js +21 -0
- package/dist/prompts/platform.js.map +1 -0
- package/dist/prompts/preset.d.ts +4 -0
- package/dist/prompts/preset.d.ts.map +1 -0
- package/dist/prompts/preset.js +165 -0
- package/dist/prompts/preset.js.map +1 -0
- package/dist/prompts/project.d.ts +2 -0
- package/dist/prompts/project.d.ts.map +1 -0
- package/dist/prompts/project.js +27 -0
- package/dist/prompts/project.js.map +1 -0
- package/dist/prompts/sdks.d.ts +2 -0
- package/dist/prompts/sdks.d.ts.map +1 -0
- package/dist/prompts/sdks.js +46 -0
- package/dist/prompts/sdks.js.map +1 -0
- package/dist/types/index.d.ts +77 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +25 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/cleanup.d.ts +5 -0
- package/dist/utils/cleanup.d.ts.map +1 -0
- package/dist/utils/cleanup.js +38 -0
- package/dist/utils/cleanup.js.map +1 -0
- package/dist/utils/copy.d.ts +10 -0
- package/dist/utils/copy.d.ts.map +1 -0
- package/dist/utils/copy.js +53 -0
- package/dist/utils/copy.js.map +1 -0
- package/dist/utils/errors.d.ts +33 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +136 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/git.d.ts +5 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +33 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +22 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/package.d.ts +16 -0
- package/dist/utils/package.d.ts.map +1 -0
- package/dist/utils/package.js +86 -0
- package/dist/utils/package.js.map +1 -0
- package/dist/utils/system-validation.d.ts +9 -0
- package/dist/utils/system-validation.d.ts.map +1 -0
- package/dist/utils/system-validation.js +31 -0
- package/dist/utils/system-validation.js.map +1 -0
- package/dist/utils/template.d.ts +20 -0
- package/dist/utils/template.d.ts.map +1 -0
- package/dist/utils/template.js +234 -0
- package/dist/utils/template.js.map +1 -0
- package/dist/utils/validation.d.ts +8 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +94 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +96 -0
- package/templates/base/backend/.dockerignore.ejs +62 -0
- package/templates/base/backend/.env.example.ejs +116 -0
- package/templates/base/backend/Dockerfile.ejs +142 -0
- package/templates/base/backend/controllers/event-queue/index.ts +20 -0
- package/templates/base/backend/controllers/event-queue/workers/user.ts +39 -0
- package/templates/base/backend/controllers/rest-api/index.ts +48 -0
- package/templates/base/backend/controllers/rest-api/plugins/auth.ts +152 -0
- package/templates/base/backend/controllers/rest-api/plugins/config.ts +64 -0
- package/templates/base/backend/controllers/rest-api/plugins/error-handler.ts +118 -0
- package/templates/base/backend/controllers/rest-api/routes/auth.ts.ejs +180 -0
- package/templates/base/backend/controllers/rest-api/routes/device-sessions.ts +197 -0
- package/templates/base/backend/controllers/rest-api/routes/oauth-web.ts.ejs +375 -0
- package/templates/base/backend/controllers/rest-api/server.ts.ejs +87 -0
- package/templates/base/backend/domain/device-session/repository.drizzle.ts +209 -0
- package/templates/base/backend/domain/device-session/repository.prisma.ts +248 -0
- package/templates/base/backend/domain/device-session/schema.ts +72 -0
- package/templates/base/backend/domain/session/repository.drizzle.ts +72 -0
- package/templates/base/backend/domain/session/repository.prisma.ts +72 -0
- package/templates/base/backend/domain/session/schema.ts +29 -0
- package/templates/base/backend/domain/user/repository.drizzle.ts +127 -0
- package/templates/base/backend/domain/user/repository.prisma.ts +115 -0
- package/templates/base/backend/domain/user/schema.ts +14 -0
- package/templates/base/backend/drizzle/schema.drizzle.ts +111 -0
- package/templates/base/backend/drizzle.config.drizzle.ts +13 -0
- package/templates/base/backend/lib/auth.drizzle.ts.ejs +104 -0
- package/templates/base/backend/lib/auth.prisma.ts.ejs +97 -0
- package/templates/base/backend/lib/constants.ts.ejs +29 -0
- package/templates/base/backend/package.json.ejs +50 -0
- package/templates/base/backend/prisma/schema.prisma.ejs +102 -0
- package/templates/base/backend/prisma.config.prisma.ts +12 -0
- package/templates/base/backend/tsconfig.json +39 -0
- package/templates/base/backend/utils/db.drizzle.ts +41 -0
- package/templates/base/backend/utils/db.prisma.ts +51 -0
- package/templates/base/backend/utils/email.ts.ejs +35 -0
- package/templates/base/backend/utils/errors.ts +348 -0
- package/templates/base/backend/utils/redis.ts.ejs +279 -0
- package/templates/base/mobile/.env.example.ejs +35 -0
- package/templates/base/mobile/.gitignore.ejs +167 -0
- package/templates/base/mobile/app/+not-found.tsx +85 -0
- package/templates/base/mobile/app/_layout.tsx.ejs +71 -0
- package/templates/base/mobile/app.json.ejs +88 -0
- package/templates/base/mobile/assets/images/adaptive-icon.png +0 -0
- package/templates/base/mobile/assets/images/favicon.png +0 -0
- package/templates/base/mobile/assets/images/icon.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_1.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_2.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_3.png +0 -0
- package/templates/base/mobile/assets/images/paywall_image.png +0 -0
- package/templates/base/mobile/assets/images/splash.png +0 -0
- package/templates/base/mobile/eas.json.ejs +49 -0
- package/templates/base/mobile/metro.config.js +9 -0
- package/templates/base/mobile/package.json.ejs +53 -0
- package/templates/base/mobile/src/components/ui/Button.tsx +131 -0
- package/templates/base/mobile/src/components/ui/Card.tsx +68 -0
- package/templates/base/mobile/src/components/ui/IconSymbol.tsx +90 -0
- package/templates/base/mobile/src/components/ui/Input.tsx +142 -0
- package/templates/base/mobile/src/components/ui/LoadingSpinner.tsx +98 -0
- package/templates/base/mobile/src/components/ui/OnboardingLayout.tsx +356 -0
- package/templates/base/mobile/src/components/ui/PaywallLayout.tsx +311 -0
- package/templates/base/mobile/src/components/ui/Skeleton.tsx +58 -0
- package/templates/base/mobile/src/components/ui/index.ts +6 -0
- package/templates/base/mobile/src/constants/Theme.ts +163 -0
- package/templates/base/mobile/src/context/ThemeContext.tsx +157 -0
- package/templates/base/mobile/src/lib/auth-client.ts.ejs +51 -0
- package/templates/base/mobile/src/services/api.ts.ejs +71 -0
- package/templates/base/mobile/src/services/errorService.ts +179 -0
- package/templates/base/mobile/src/services/sdkInitializer.ts.ejs +36 -0
- package/templates/base/mobile/src/store/index.ts.ejs +18 -0
- package/templates/base/mobile/src/store/ui.store.ts +100 -0
- package/templates/base/mobile/src/utils/formatters.ts +105 -0
- package/templates/base/mobile/src/utils/logger.ts +73 -0
- package/templates/base/mobile/src/utils/responsive.ts +234 -0
- package/templates/base/mobile/tsconfig.json +32 -0
- package/templates/base/web/.env.example.ejs +26 -0
- package/templates/base/web/components.json +22 -0
- package/templates/base/web/eslint.config.mjs +18 -0
- package/templates/base/web/next.config.ts +7 -0
- package/templates/base/web/package.json.ejs +35 -0
- package/templates/base/web/postcss.config.mjs +7 -0
- package/templates/base/web/public/.gitkeep +0 -0
- package/templates/base/web/public/file.svg +1 -0
- package/templates/base/web/public/globe.svg +1 -0
- package/templates/base/web/public/next.svg +1 -0
- package/templates/base/web/public/vercel.svg +1 -0
- package/templates/base/web/public/window.svg +1 -0
- package/templates/base/web/src/app/favicon.ico +0 -0
- package/templates/base/web/src/app/globals.css +152 -0
- package/templates/base/web/src/app/layout.tsx.ejs +54 -0
- package/templates/base/web/src/app/page.tsx.ejs +92 -0
- package/templates/base/web/src/components/auth/auth-hydrator.tsx.ejs +19 -0
- package/templates/base/web/src/components/auth/protected-route.tsx.ejs +109 -0
- package/templates/base/web/src/components/providers/device-session-setup.tsx.ejs +56 -0
- package/templates/base/web/src/components/providers/theme-provider.tsx +17 -0
- package/templates/base/web/src/components/theme-toggle.tsx +34 -0
- package/templates/base/web/src/components/ui/button.tsx +62 -0
- package/templates/base/web/src/components/ui/card.tsx +92 -0
- package/templates/base/web/src/components/ui/input.tsx +21 -0
- package/templates/base/web/src/components/ui/label.tsx +24 -0
- package/templates/base/web/src/components/ui/skeleton.tsx +13 -0
- package/templates/base/web/src/components/ui/spinner.tsx +20 -0
- package/templates/base/web/src/hooks/use-device-session.ts.ejs +40 -0
- package/templates/base/web/src/hooks/use-session.ts.ejs +56 -0
- package/templates/base/web/src/lib/auth/actions.ts.ejs +334 -0
- package/templates/base/web/src/lib/auth/config.ts.ejs +65 -0
- package/templates/base/web/src/lib/auth/cookies.ts.ejs +74 -0
- package/templates/base/web/src/lib/auth/index.ts.ejs +40 -0
- package/templates/base/web/src/lib/auth/oauth.ts.ejs +72 -0
- package/templates/base/web/src/lib/auth/pkce.ts.ejs +48 -0
- package/templates/base/web/src/lib/auth/sessions.ts.ejs +135 -0
- package/templates/base/web/src/lib/auth/user-agent.ts.ejs +47 -0
- package/templates/base/web/src/lib/device/actions.ts.ejs +148 -0
- package/templates/base/web/src/lib/device/id.ts.ejs +74 -0
- package/templates/base/web/src/lib/utils.ts +6 -0
- package/templates/base/web/src/proxy.ts.ejs +66 -0
- package/templates/base/web/src/store/auth.store.ts.ejs +89 -0
- package/templates/base/web/src/store/deviceSession.store.ts.ejs +141 -0
- package/templates/base/web/tsconfig.json +34 -0
- package/templates/features/mobile/auth/app/(auth)/_layout.tsx +16 -0
- package/templates/features/mobile/auth/app/(auth)/login.tsx +86 -0
- package/templates/features/mobile/auth/app/(auth)/register.tsx +86 -0
- package/templates/features/mobile/auth/components/auth/LoginForm.tsx.ejs +349 -0
- package/templates/features/mobile/auth/components/auth/RegisterForm.tsx.ejs +407 -0
- package/templates/features/mobile/auth/components/auth/index.ts +2 -0
- package/templates/features/mobile/auth/hooks/index.ts.ejs +1 -0
- package/templates/features/mobile/auth/hooks/useAuth.ts.ejs +367 -0
- package/templates/features/mobile/auth/services/deviceSession.ts +370 -0
- package/templates/features/mobile/auth/store/deviceSession.store.ts +326 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/_layout.tsx.ejs +11 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-1.tsx.ejs +52 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-2.tsx.ejs +52 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-3.tsx.ejs +60 -0
- package/templates/features/mobile/paywall/app/paywall.tsx +550 -0
- package/templates/features/mobile/tabs/app/(tabs)/_layout.tsx +26 -0
- package/templates/features/mobile/tabs/app/(tabs)/index.tsx +565 -0
- package/templates/features/web/.gitkeep +0 -0
- package/templates/features/web/auth/app/(app)/dashboard/dashboard-client.tsx.ejs +166 -0
- package/templates/features/web/auth/app/(app)/dashboard/page.tsx.ejs +24 -0
- package/templates/features/web/auth/app/(app)/layout.tsx.ejs +43 -0
- package/templates/features/web/auth/app/(app)/settings/sessions/page.tsx.ejs +29 -0
- package/templates/features/web/auth/app/(app)/settings/sessions/sessions-client.tsx.ejs +77 -0
- package/templates/features/web/auth/app/(auth)/forgot-password/page.tsx.ejs +127 -0
- package/templates/features/web/auth/app/(auth)/layout.tsx.ejs +32 -0
- package/templates/features/web/auth/app/(auth)/login/page.tsx.ejs +35 -0
- package/templates/features/web/auth/app/(auth)/register/page.tsx.ejs +19 -0
- package/templates/features/web/auth/app/(auth)/reset-password/page.tsx.ejs +40 -0
- package/templates/features/web/auth/app/(auth)/verify-email/page.tsx.ejs +198 -0
- package/templates/features/web/auth/app/auth/callback/route.ts.ejs +152 -0
- package/templates/features/web/auth/components/auth/login-form.tsx.ejs +100 -0
- package/templates/features/web/auth/components/auth/oauth-buttons.tsx.ejs +126 -0
- package/templates/features/web/auth/components/auth/password-reset-form.tsx.ejs +103 -0
- package/templates/features/web/auth/components/auth/register-form.tsx.ejs +139 -0
- package/templates/features/web/auth/components/settings/session-card.tsx.ejs +132 -0
- package/templates/integrations/mobile/adjust/services/adjustService.ts.ejs +163 -0
- package/templates/integrations/mobile/adjust/store/adjust.store.ts +243 -0
- package/templates/integrations/mobile/att/services/attService.ts +84 -0
- package/templates/integrations/mobile/att/services/trackingPermissions.ts +208 -0
- package/templates/integrations/mobile/att/store/att.store.ts +162 -0
- package/templates/integrations/mobile/revenuecat/services/revenuecatService.ts.ejs +174 -0
- package/templates/integrations/mobile/revenuecat/store/revenuecat.store.ts +286 -0
- package/templates/integrations/mobile/scate/services/scateService.ts.ejs +85 -0
- package/templates/integrations/mobile/scate/store/scate.store.ts +125 -0
- package/templates/integrations/web/.gitkeep +0 -0
- package/templates/shared/.env.example.ejs +21 -0
- package/templates/shared/.gitignore.ejs +145 -0
- package/templates/shared/README.md.ejs +134 -0
- package/templates/shared/docker-compose.prod.yml.ejs +120 -0
- package/templates/shared/docker-compose.yml.ejs +129 -0
- package/templates/shared/scripts/docker-dev.sh.ejs +395 -0
- package/templates/shared/scripts/docker-prod.sh.ejs +542 -0
- package/templates/shared/scripts/setup.sh.ejs +979 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { revenueCatService } from '../services/revenuecatService';
|
|
3
|
+
import { CustomerInfo, PurchasesPackage, PurchasesOfferings } from 'react-native-purchases';
|
|
4
|
+
import { logger } from '../utils/logger';
|
|
5
|
+
|
|
6
|
+
interface RevenueCatState {
|
|
7
|
+
// State
|
|
8
|
+
isInitialized: boolean;
|
|
9
|
+
customerInfo: CustomerInfo | null;
|
|
10
|
+
offerings: PurchasesOfferings | null;
|
|
11
|
+
adjustId: string | null;
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
error: string | null;
|
|
14
|
+
|
|
15
|
+
// Actions
|
|
16
|
+
initialize: () => void;
|
|
17
|
+
setAdjustId: (adjustId: string) => void;
|
|
18
|
+
getCustomerInfo: () => Promise<void>;
|
|
19
|
+
getOfferings: () => Promise<void>;
|
|
20
|
+
purchasePackage: (pkg: PurchasesPackage) => Promise<{ success: boolean; userCancelled: boolean }>;
|
|
21
|
+
restorePurchases: () => Promise<void>;
|
|
22
|
+
setLoading: (loading: boolean) => void;
|
|
23
|
+
setError: (error: string | null) => void;
|
|
24
|
+
reset: () => void;
|
|
25
|
+
|
|
26
|
+
// Computed values
|
|
27
|
+
hasActiveSubscription: boolean;
|
|
28
|
+
getActiveEntitlements: () => string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const useRevenueCatStore = create<RevenueCatState>((set, get) => ({
|
|
32
|
+
// Initial state
|
|
33
|
+
isInitialized: false,
|
|
34
|
+
customerInfo: null,
|
|
35
|
+
offerings: null,
|
|
36
|
+
adjustId: null,
|
|
37
|
+
isLoading: false,
|
|
38
|
+
error: null,
|
|
39
|
+
|
|
40
|
+
// Actions
|
|
41
|
+
initialize: () => {
|
|
42
|
+
const state = get();
|
|
43
|
+
if (state.isInitialized) {
|
|
44
|
+
logger.info('RevenueCatStore: Already initialized, skipping');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
set({ isLoading: true, error: null });
|
|
50
|
+
logger.info('RevenueCatStore: Initializing RevenueCat SDK...');
|
|
51
|
+
|
|
52
|
+
revenueCatService.initialize();
|
|
53
|
+
|
|
54
|
+
set({
|
|
55
|
+
isInitialized: true,
|
|
56
|
+
isLoading: false,
|
|
57
|
+
error: null
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
logger.info('RevenueCatStore: RevenueCat SDK initialized successfully');
|
|
61
|
+
|
|
62
|
+
// Get initial customer info
|
|
63
|
+
get().getCustomerInfo();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to initialize RevenueCat SDK';
|
|
66
|
+
logger.error('RevenueCatStore: Failed to initialize RevenueCat SDK', { error });
|
|
67
|
+
set({
|
|
68
|
+
error: errorMessage,
|
|
69
|
+
isLoading: false,
|
|
70
|
+
isInitialized: false
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
setAdjustId: (adjustId) => {
|
|
76
|
+
try {
|
|
77
|
+
const currentState = get();
|
|
78
|
+
if (currentState.adjustId === adjustId) {
|
|
79
|
+
logger.info('RevenueCatStore: Adjust ID already set, skipping');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
logger.info('RevenueCatStore: Setting Adjust ID', { adjustId });
|
|
84
|
+
revenueCatService.setAdjustId(adjustId);
|
|
85
|
+
|
|
86
|
+
set({ adjustId });
|
|
87
|
+
logger.info('RevenueCatStore: Adjust ID set successfully');
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to set Adjust ID';
|
|
90
|
+
logger.error('RevenueCatStore: Failed to set Adjust ID', { error });
|
|
91
|
+
set({ error: errorMessage });
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
getCustomerInfo: async () => {
|
|
96
|
+
const state = get();
|
|
97
|
+
if (state.isLoading) {
|
|
98
|
+
logger.info('RevenueCatStore: Customer info request already in progress');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
set({ isLoading: true, error: null });
|
|
104
|
+
logger.info('RevenueCatStore: Getting customer info...');
|
|
105
|
+
|
|
106
|
+
const customerInfo = await revenueCatService.getCustomerInfo();
|
|
107
|
+
|
|
108
|
+
set({
|
|
109
|
+
customerInfo,
|
|
110
|
+
isLoading: false,
|
|
111
|
+
error: null
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
logger.info('RevenueCatStore: Customer info retrieved successfully');
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to get customer info';
|
|
117
|
+
logger.error('RevenueCatStore: Failed to get customer info', { error });
|
|
118
|
+
set({
|
|
119
|
+
error: errorMessage,
|
|
120
|
+
isLoading: false,
|
|
121
|
+
customerInfo: null
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
getOfferings: async () => {
|
|
127
|
+
const state = get();
|
|
128
|
+
if (state.isLoading) {
|
|
129
|
+
logger.info('RevenueCatStore: Offerings request already in progress');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
set({ isLoading: true, error: null });
|
|
135
|
+
logger.info('RevenueCatStore: Getting offerings...');
|
|
136
|
+
|
|
137
|
+
const offerings = await revenueCatService.getOfferings();
|
|
138
|
+
|
|
139
|
+
set({
|
|
140
|
+
offerings,
|
|
141
|
+
isLoading: false,
|
|
142
|
+
error: null
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
logger.info('RevenueCatStore: Offerings retrieved successfully', {
|
|
146
|
+
offeringsCount: Object.keys(offerings.all).length,
|
|
147
|
+
hasCurrent: !!offerings.current,
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to get offerings';
|
|
151
|
+
logger.error('RevenueCatStore: Failed to get offerings', { error });
|
|
152
|
+
set({
|
|
153
|
+
error: errorMessage,
|
|
154
|
+
isLoading: false,
|
|
155
|
+
offerings: null
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
purchasePackage: async (pkg) => {
|
|
161
|
+
const state = get();
|
|
162
|
+
if (state.isLoading) {
|
|
163
|
+
logger.info('RevenueCatStore: Purchase already in progress');
|
|
164
|
+
return { success: false, userCancelled: false };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
set({ isLoading: true, error: null });
|
|
169
|
+
logger.info('RevenueCatStore: Purchasing package', { packageId: pkg.identifier });
|
|
170
|
+
|
|
171
|
+
const result = await revenueCatService.purchasePackage(pkg);
|
|
172
|
+
|
|
173
|
+
set({
|
|
174
|
+
customerInfo: result.customerInfo,
|
|
175
|
+
isLoading: false,
|
|
176
|
+
error: null
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const success = !result.userCancelled;
|
|
180
|
+
logger.info('RevenueCatStore: Package purchase completed', {
|
|
181
|
+
success,
|
|
182
|
+
userCancelled: result.userCancelled
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return { success, userCancelled: result.userCancelled };
|
|
186
|
+
} catch (error) {
|
|
187
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to purchase package';
|
|
188
|
+
logger.error('RevenueCatStore: Failed to purchase package', { error });
|
|
189
|
+
set({
|
|
190
|
+
error: errorMessage,
|
|
191
|
+
isLoading: false
|
|
192
|
+
});
|
|
193
|
+
return { success: false, userCancelled: false };
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
restorePurchases: async () => {
|
|
198
|
+
const state = get();
|
|
199
|
+
if (state.isLoading) {
|
|
200
|
+
logger.info('RevenueCatStore: Restore purchases already in progress');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
set({ isLoading: true, error: null });
|
|
206
|
+
logger.info('RevenueCatStore: Restoring purchases...');
|
|
207
|
+
|
|
208
|
+
const customerInfo = await revenueCatService.restorePurchases();
|
|
209
|
+
|
|
210
|
+
set({
|
|
211
|
+
customerInfo,
|
|
212
|
+
isLoading: false,
|
|
213
|
+
error: null
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
logger.info('RevenueCatStore: Purchases restored successfully');
|
|
217
|
+
} catch (error) {
|
|
218
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to restore purchases';
|
|
219
|
+
logger.error('RevenueCatStore: Failed to restore purchases', { error });
|
|
220
|
+
set({
|
|
221
|
+
error: errorMessage,
|
|
222
|
+
isLoading: false
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
setLoading: (isLoading) => set({ isLoading }),
|
|
228
|
+
|
|
229
|
+
setError: (error) => set({ error }),
|
|
230
|
+
|
|
231
|
+
reset: () => {
|
|
232
|
+
set({
|
|
233
|
+
isInitialized: false,
|
|
234
|
+
customerInfo: null,
|
|
235
|
+
offerings: null,
|
|
236
|
+
adjustId: null,
|
|
237
|
+
isLoading: false,
|
|
238
|
+
error: null
|
|
239
|
+
});
|
|
240
|
+
revenueCatService.reset();
|
|
241
|
+
logger.info('RevenueCatStore: Store reset');
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
// Computed values
|
|
245
|
+
get hasActiveSubscription() {
|
|
246
|
+
const state = get();
|
|
247
|
+
if (!state.customerInfo) return false;
|
|
248
|
+
return Object.keys(state.customerInfo.entitlements.active).length > 0;
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
getActiveEntitlements: () => {
|
|
252
|
+
const state = get();
|
|
253
|
+
if (!state.customerInfo) return [];
|
|
254
|
+
return Object.keys(state.customerInfo.entitlements.active);
|
|
255
|
+
},
|
|
256
|
+
}));
|
|
257
|
+
|
|
258
|
+
// Selectors for commonly used RevenueCat state
|
|
259
|
+
export const useRevenueCat = () => {
|
|
260
|
+
const state = useRevenueCatStore();
|
|
261
|
+
return {
|
|
262
|
+
isInitialized: state.isInitialized,
|
|
263
|
+
customerInfo: state.customerInfo,
|
|
264
|
+
offerings: state.offerings,
|
|
265
|
+
adjustId: state.adjustId,
|
|
266
|
+
isLoading: state.isLoading,
|
|
267
|
+
error: state.error,
|
|
268
|
+
hasActiveSubscription: state.hasActiveSubscription,
|
|
269
|
+
activeEntitlements: state.getActiveEntitlements(),
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export const useRevenueCatActions = () => {
|
|
274
|
+
const state = useRevenueCatStore();
|
|
275
|
+
return {
|
|
276
|
+
initialize: state.initialize,
|
|
277
|
+
setAdjustId: state.setAdjustId,
|
|
278
|
+
getCustomerInfo: state.getCustomerInfo,
|
|
279
|
+
getOfferings: state.getOfferings,
|
|
280
|
+
purchasePackage: state.purchasePackage,
|
|
281
|
+
restorePurchases: state.restorePurchases,
|
|
282
|
+
setLoading: state.setLoading,
|
|
283
|
+
setError: state.setError,
|
|
284
|
+
reset: state.reset,
|
|
285
|
+
};
|
|
286
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ScateSDK } from 'scatesdk-react';
|
|
2
|
+
import Constants from 'expo-constants';
|
|
3
|
+
import { logger } from '../utils/logger';
|
|
4
|
+
|
|
5
|
+
class ScateService {
|
|
6
|
+
private static readonly API_KEY = Constants.expoConfig?.extra?.scate?.apiKey || '';
|
|
7
|
+
|
|
8
|
+
private _isInitialized: boolean = false;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check if Scate SDK is initialized
|
|
12
|
+
*/
|
|
13
|
+
get isInitialized(): boolean {
|
|
14
|
+
return this._isInitialized;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize Scate SDK
|
|
19
|
+
*/
|
|
20
|
+
initialize(): void {
|
|
21
|
+
if (this._isInitialized) {
|
|
22
|
+
logger.info('ScateService: Already initialized, skipping');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
logger.info('ScateService: Initializing Scate SDK...');
|
|
28
|
+
|
|
29
|
+
ScateSDK.Init(ScateService.API_KEY);
|
|
30
|
+
|
|
31
|
+
this._isInitialized = true;
|
|
32
|
+
logger.info('ScateService: Scate SDK initialized successfully');
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.error('ScateService: Failed to initialize Scate SDK', { error });
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set Adjust ADID for attribution
|
|
41
|
+
*/
|
|
42
|
+
setAdid(adid: string): void {
|
|
43
|
+
try {
|
|
44
|
+
logger.info('ScateService: Setting ADID', { adid });
|
|
45
|
+
ScateSDK.SetAdid(adid);
|
|
46
|
+
logger.info('ScateService: ADID set successfully');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
logger.error('ScateService: Failed to set ADID', { error });
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Track custom event (if needed for future features)
|
|
55
|
+
*/
|
|
56
|
+
trackEvent(eventName: string, parameters?: Record<string, any>): void {
|
|
57
|
+
try {
|
|
58
|
+
logger.info('ScateService: Custom event tracking would be implemented here', {
|
|
59
|
+
eventName,
|
|
60
|
+
parameters
|
|
61
|
+
});
|
|
62
|
+
// Implementation would depend on Scate SDK's event tracking capabilities
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.error('ScateService: Failed to track event', { error });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get API key
|
|
70
|
+
*/
|
|
71
|
+
getApiKey(): string {
|
|
72
|
+
return ScateService.API_KEY;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Reset Scate SDK state (useful for testing)
|
|
77
|
+
*/
|
|
78
|
+
reset(): void {
|
|
79
|
+
this._isInitialized = false;
|
|
80
|
+
logger.info('ScateService: Service reset');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Export singleton instance
|
|
85
|
+
export const scateService = new ScateService();
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { scateService } from '../services/scateService';
|
|
3
|
+
import { logger } from '../utils/logger';
|
|
4
|
+
|
|
5
|
+
interface ScateState {
|
|
6
|
+
// State
|
|
7
|
+
isInitialized: boolean;
|
|
8
|
+
adid: string | null;
|
|
9
|
+
isLoading: boolean;
|
|
10
|
+
error: string | null;
|
|
11
|
+
|
|
12
|
+
// Actions
|
|
13
|
+
initialize: () => void;
|
|
14
|
+
setAdid: (adid: string) => void;
|
|
15
|
+
setLoading: (loading: boolean) => void;
|
|
16
|
+
setError: (error: string | null) => void;
|
|
17
|
+
reset: () => void;
|
|
18
|
+
|
|
19
|
+
// Computed values
|
|
20
|
+
hasAdid: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const useScateStore = create<ScateState>((set, get) => ({
|
|
24
|
+
// Initial state
|
|
25
|
+
isInitialized: false,
|
|
26
|
+
adid: null,
|
|
27
|
+
isLoading: false,
|
|
28
|
+
error: null,
|
|
29
|
+
|
|
30
|
+
// Actions
|
|
31
|
+
initialize: () => {
|
|
32
|
+
const state = get();
|
|
33
|
+
if (state.isInitialized) {
|
|
34
|
+
logger.info('ScateStore: Already initialized, skipping');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
set({ isLoading: true, error: null });
|
|
40
|
+
logger.info('ScateStore: Initializing Scate SDK...');
|
|
41
|
+
|
|
42
|
+
scateService.initialize();
|
|
43
|
+
|
|
44
|
+
set({
|
|
45
|
+
isInitialized: true,
|
|
46
|
+
isLoading: false,
|
|
47
|
+
error: null
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
logger.info('ScateStore: Scate SDK initialized successfully');
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to initialize Scate SDK';
|
|
53
|
+
logger.error('ScateStore: Failed to initialize Scate SDK', { error });
|
|
54
|
+
set({
|
|
55
|
+
error: errorMessage,
|
|
56
|
+
isLoading: false,
|
|
57
|
+
isInitialized: false
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
setAdid: (adid) => {
|
|
63
|
+
try {
|
|
64
|
+
const currentState = get();
|
|
65
|
+
if (currentState.adid === adid) {
|
|
66
|
+
logger.info('ScateStore: ADID already set, skipping');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
logger.info('ScateStore: Setting ADID', { adid });
|
|
71
|
+
scateService.setAdid(adid);
|
|
72
|
+
|
|
73
|
+
set({ adid });
|
|
74
|
+
logger.info('ScateStore: ADID set successfully');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to set ADID';
|
|
77
|
+
logger.error('ScateStore: Failed to set ADID', { error });
|
|
78
|
+
set({ error: errorMessage });
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
setLoading: (isLoading) => set({ isLoading }),
|
|
83
|
+
|
|
84
|
+
setError: (error) => set({ error }),
|
|
85
|
+
|
|
86
|
+
reset: () => {
|
|
87
|
+
set({
|
|
88
|
+
isInitialized: false,
|
|
89
|
+
adid: null,
|
|
90
|
+
isLoading: false,
|
|
91
|
+
error: null
|
|
92
|
+
});
|
|
93
|
+
scateService.reset();
|
|
94
|
+
logger.info('ScateStore: Store reset');
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// Computed values
|
|
98
|
+
get hasAdid() {
|
|
99
|
+
const state = get();
|
|
100
|
+
return !!state.adid;
|
|
101
|
+
},
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
// Selectors for commonly used Scate state
|
|
105
|
+
export const useScate = () => {
|
|
106
|
+
const state = useScateStore();
|
|
107
|
+
return {
|
|
108
|
+
isInitialized: state.isInitialized,
|
|
109
|
+
adid: state.adid,
|
|
110
|
+
isLoading: state.isLoading,
|
|
111
|
+
error: state.error,
|
|
112
|
+
hasAdid: state.hasAdid,
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const useScateActions = () => {
|
|
117
|
+
const state = useScateStore();
|
|
118
|
+
return {
|
|
119
|
+
initialize: state.initialize,
|
|
120
|
+
setAdid: state.setAdid,
|
|
121
|
+
setLoading: state.setLoading,
|
|
122
|
+
setError: state.setError,
|
|
123
|
+
reset: state.reset,
|
|
124
|
+
};
|
|
125
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Docker Compose Environment Variables
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# These variables are used by docker-compose.yml for container orchestration.
|
|
5
|
+
# The setup.sh script will create .env from this template automatically.
|
|
6
|
+
#
|
|
7
|
+
# For application-specific configuration (OAuth, BetterAuth, etc.),
|
|
8
|
+
# see backend/.env.example
|
|
9
|
+
# =============================================================================
|
|
10
|
+
|
|
11
|
+
# Database Configuration (for PostgreSQL container)
|
|
12
|
+
DB_USER=postgres
|
|
13
|
+
DB_PASSWORD=your-secure-database-password
|
|
14
|
+
DB_NAME=<%= projectName.toLowerCase().replace(/[^a-z0-9]/g, '_') %>_db
|
|
15
|
+
|
|
16
|
+
# JWT Secret (must match between services)
|
|
17
|
+
JWT_SECRET=your-jwt-secret-change-in-production
|
|
18
|
+
<% if (backend.eventQueue) { %>
|
|
19
|
+
# Redis Configuration
|
|
20
|
+
REDIS_PASSWORD=your-redis-password
|
|
21
|
+
<% } %>
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
.bun/
|
|
4
|
+
|
|
5
|
+
# Environment variables
|
|
6
|
+
.env
|
|
7
|
+
.env.local
|
|
8
|
+
.env.development.local
|
|
9
|
+
.env.test.local
|
|
10
|
+
.env.production.local
|
|
11
|
+
.env.staging
|
|
12
|
+
|
|
13
|
+
# Build outputs
|
|
14
|
+
dist/
|
|
15
|
+
build/
|
|
16
|
+
out/
|
|
17
|
+
.next/
|
|
18
|
+
|
|
19
|
+
# Logs
|
|
20
|
+
*.log
|
|
21
|
+
npm-debug.log*
|
|
22
|
+
yarn-debug.log*
|
|
23
|
+
yarn-error.log*
|
|
24
|
+
lerna-debug.log*
|
|
25
|
+
.pnpm-debug.log*
|
|
26
|
+
|
|
27
|
+
# Runtime data
|
|
28
|
+
pids/
|
|
29
|
+
*.pid
|
|
30
|
+
*.seed
|
|
31
|
+
*.pid.lock
|
|
32
|
+
|
|
33
|
+
# Coverage directory used by tools like istanbul
|
|
34
|
+
coverage/
|
|
35
|
+
*.lcov
|
|
36
|
+
.nyc_output/
|
|
37
|
+
|
|
38
|
+
# Dependency directories
|
|
39
|
+
jspm_packages/
|
|
40
|
+
|
|
41
|
+
# TypeScript cache
|
|
42
|
+
*.tsbuildinfo
|
|
43
|
+
|
|
44
|
+
# Optional npm cache directory
|
|
45
|
+
.npm
|
|
46
|
+
|
|
47
|
+
# Optional eslint cache
|
|
48
|
+
.eslintcache
|
|
49
|
+
|
|
50
|
+
# Microbundle cache
|
|
51
|
+
.rpt2_cache/
|
|
52
|
+
.rts2_cache_cjs/
|
|
53
|
+
.rts2_cache_es/
|
|
54
|
+
.rts2_cache_umd/
|
|
55
|
+
|
|
56
|
+
# Optional REPL history
|
|
57
|
+
.node_repl_history
|
|
58
|
+
|
|
59
|
+
# Output of 'npm pack'
|
|
60
|
+
*.tgz
|
|
61
|
+
|
|
62
|
+
# Yarn Integrity file
|
|
63
|
+
.yarn-integrity
|
|
64
|
+
|
|
65
|
+
# parcel-bundler cache (https://parceljs.org/)
|
|
66
|
+
.cache
|
|
67
|
+
.parcel-cache
|
|
68
|
+
|
|
69
|
+
# Stores VSCode versions used for testing VSCode extensions
|
|
70
|
+
.vscode-test
|
|
71
|
+
|
|
72
|
+
# yarn v2
|
|
73
|
+
.yarn/cache
|
|
74
|
+
.yarn/unplugged
|
|
75
|
+
.yarn/build-state.yml
|
|
76
|
+
.yarn/install-state.gz
|
|
77
|
+
.pnp.*
|
|
78
|
+
|
|
79
|
+
# IDEs and editors
|
|
80
|
+
.vscode/
|
|
81
|
+
.idea/
|
|
82
|
+
*.swp
|
|
83
|
+
*.swo
|
|
84
|
+
*~
|
|
85
|
+
*.sublime-*
|
|
86
|
+
|
|
87
|
+
# OS generated files
|
|
88
|
+
.DS_Store
|
|
89
|
+
.DS_Store?
|
|
90
|
+
._*
|
|
91
|
+
.Spotlight-V100
|
|
92
|
+
.Trashes
|
|
93
|
+
ehthumbs.db
|
|
94
|
+
Thumbs.db
|
|
95
|
+
|
|
96
|
+
# Docker
|
|
97
|
+
docker-compose.override.yml
|
|
98
|
+
.dockerignore.local
|
|
99
|
+
|
|
100
|
+
# Temporary files
|
|
101
|
+
tmp/
|
|
102
|
+
temp/
|
|
103
|
+
*.tmp
|
|
104
|
+
*.temp
|
|
105
|
+
|
|
106
|
+
# Backup files
|
|
107
|
+
*.backup
|
|
108
|
+
*.bak
|
|
109
|
+
*.orig
|
|
110
|
+
|
|
111
|
+
# Database
|
|
112
|
+
*.sqlite
|
|
113
|
+
*.db
|
|
114
|
+
database.sqlite
|
|
115
|
+
|
|
116
|
+
# Redis dump
|
|
117
|
+
dump.rdb
|
|
118
|
+
|
|
119
|
+
# Prisma
|
|
120
|
+
prisma/migrations/migration_lock.toml
|
|
121
|
+
|
|
122
|
+
# Production deployment artifacts
|
|
123
|
+
.vercel/
|
|
124
|
+
.netlify/
|
|
125
|
+
.firebase/
|
|
126
|
+
|
|
127
|
+
# Local development overrides
|
|
128
|
+
*.local
|
|
129
|
+
local-*
|
|
130
|
+
|
|
131
|
+
# Test artifacts
|
|
132
|
+
.nyc_output/
|
|
133
|
+
test-results/
|
|
134
|
+
playwright-report/
|
|
135
|
+
|
|
136
|
+
# Certificates
|
|
137
|
+
*.pem
|
|
138
|
+
*.key
|
|
139
|
+
*.crt
|
|
140
|
+
*.cert
|
|
141
|
+
|
|
142
|
+
# Secrets
|
|
143
|
+
secrets/
|
|
144
|
+
.secrets/
|
|
145
|
+
*.secret
|