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,139 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import Link from "next/link";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { Label } from "@/components/ui/label";
|
|
9
|
+
import { signUp } from "@/lib/auth/actions";
|
|
10
|
+
|
|
11
|
+
export function RegisterForm() {
|
|
12
|
+
const router = useRouter();
|
|
13
|
+
const [name, setName] = useState("");
|
|
14
|
+
const [email, setEmail] = useState("");
|
|
15
|
+
const [password, setPassword] = useState("");
|
|
16
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
17
|
+
const [error, setError] = useState<string | null>(null);
|
|
18
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
19
|
+
|
|
20
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
setError(null);
|
|
23
|
+
|
|
24
|
+
if (password !== confirmPassword) {
|
|
25
|
+
setError("Passwords do not match");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (password.length < 8) {
|
|
30
|
+
setError("Password must be at least 8 characters");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setIsLoading(true);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const result = await signUp(email, password, name || undefined);
|
|
38
|
+
|
|
39
|
+
if (!result.success) {
|
|
40
|
+
setError(result.error || "Failed to create account");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
<% if (features.authentication.emailVerification) { %>
|
|
45
|
+
if (result.needsEmailVerification) {
|
|
46
|
+
router.push(`/verify-email?email=${encodeURIComponent(email)}`);
|
|
47
|
+
} else {
|
|
48
|
+
router.push("/dashboard");
|
|
49
|
+
router.refresh();
|
|
50
|
+
}
|
|
51
|
+
<% } else { %>
|
|
52
|
+
router.push("/dashboard");
|
|
53
|
+
router.refresh();
|
|
54
|
+
<% } %>
|
|
55
|
+
} catch {
|
|
56
|
+
setError("An unexpected error occurred");
|
|
57
|
+
} finally {
|
|
58
|
+
setIsLoading(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<form onSubmit={handleSubmit} className="space-y-3">
|
|
64
|
+
<div className="space-y-1.5">
|
|
65
|
+
<Label htmlFor="name" className="text-sm">
|
|
66
|
+
Name <span className="text-muted-foreground font-normal">(optional)</span>
|
|
67
|
+
</Label>
|
|
68
|
+
<Input
|
|
69
|
+
id="name"
|
|
70
|
+
type="text"
|
|
71
|
+
value={name}
|
|
72
|
+
onChange={(e) => setName(e.target.value)}
|
|
73
|
+
placeholder="John Doe"
|
|
74
|
+
disabled={isLoading}
|
|
75
|
+
autoComplete="name"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div className="space-y-1.5">
|
|
80
|
+
<Label htmlFor="email" className="text-sm">Email</Label>
|
|
81
|
+
<Input
|
|
82
|
+
id="email"
|
|
83
|
+
type="email"
|
|
84
|
+
value={email}
|
|
85
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
86
|
+
placeholder="you@example.com"
|
|
87
|
+
required
|
|
88
|
+
disabled={isLoading}
|
|
89
|
+
autoComplete="email"
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className="space-y-1.5">
|
|
94
|
+
<Label htmlFor="password" className="text-sm">Password</Label>
|
|
95
|
+
<Input
|
|
96
|
+
id="password"
|
|
97
|
+
type="password"
|
|
98
|
+
value={password}
|
|
99
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
100
|
+
placeholder="Min. 8 characters"
|
|
101
|
+
required
|
|
102
|
+
disabled={isLoading}
|
|
103
|
+
autoComplete="new-password"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="space-y-1.5">
|
|
108
|
+
<Label htmlFor="confirmPassword" className="text-sm">Confirm Password</Label>
|
|
109
|
+
<Input
|
|
110
|
+
id="confirmPassword"
|
|
111
|
+
type="password"
|
|
112
|
+
value={confirmPassword}
|
|
113
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
114
|
+
placeholder="Confirm your password"
|
|
115
|
+
required
|
|
116
|
+
disabled={isLoading}
|
|
117
|
+
autoComplete="new-password"
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{error && (
|
|
122
|
+
<div className="p-2.5 text-sm text-destructive bg-destructive/10 rounded-lg border border-destructive/20">
|
|
123
|
+
{error}
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
|
|
127
|
+
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
128
|
+
{isLoading ? "Creating account..." : "Create account"}
|
|
129
|
+
</Button>
|
|
130
|
+
|
|
131
|
+
<p className="text-center text-sm text-muted-foreground">
|
|
132
|
+
Already have an account?{" "}
|
|
133
|
+
<Link href="/login" className="text-primary hover:underline font-medium">
|
|
134
|
+
Sign in
|
|
135
|
+
</Link>
|
|
136
|
+
</p>
|
|
137
|
+
</form>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import { type SessionInfo } from "@/lib/auth/sessions";
|
|
5
|
+
import { parseUserAgent } from "@/lib/auth/user-agent";
|
|
6
|
+
|
|
7
|
+
interface SessionCardProps {
|
|
8
|
+
session: SessionInfo;
|
|
9
|
+
isCurrentSession: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function SessionCard({ session, isCurrentSession }: SessionCardProps) {
|
|
13
|
+
const { browser, os, device } = parseUserAgent(session.userAgent);
|
|
14
|
+
const createdAt = new Date(session.createdAt);
|
|
15
|
+
const expiresAt = new Date(session.expiresAt);
|
|
16
|
+
const isExpired = expiresAt < new Date();
|
|
17
|
+
|
|
18
|
+
const handleRevoke = () => {
|
|
19
|
+
// Session revocation requires WebSocket support for proper real-time notification
|
|
20
|
+
// Without it, the revoked device would experience broken states (401 errors, failed forms)
|
|
21
|
+
alert("Session revocation will be available after WebSocket support is implemented.");
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
className={`p-4 border rounded-lg ${
|
|
27
|
+
isCurrentSession
|
|
28
|
+
? "border-primary bg-primary/5"
|
|
29
|
+
: isExpired
|
|
30
|
+
? "border-muted bg-muted/20 opacity-60"
|
|
31
|
+
: "border-border"
|
|
32
|
+
}`}
|
|
33
|
+
>
|
|
34
|
+
<div className="flex items-start justify-between gap-4">
|
|
35
|
+
<div className="flex items-start gap-3">
|
|
36
|
+
{/* Device Icon */}
|
|
37
|
+
<div className="mt-1">
|
|
38
|
+
{device === "Mobile" ? (
|
|
39
|
+
<svg
|
|
40
|
+
className="w-5 h-5 text-muted-foreground"
|
|
41
|
+
fill="none"
|
|
42
|
+
viewBox="0 0 24 24"
|
|
43
|
+
stroke="currentColor"
|
|
44
|
+
>
|
|
45
|
+
<path
|
|
46
|
+
strokeLinecap="round"
|
|
47
|
+
strokeLinejoin="round"
|
|
48
|
+
strokeWidth={2}
|
|
49
|
+
d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z"
|
|
50
|
+
/>
|
|
51
|
+
</svg>
|
|
52
|
+
) : device === "Tablet" ? (
|
|
53
|
+
<svg
|
|
54
|
+
className="w-5 h-5 text-muted-foreground"
|
|
55
|
+
fill="none"
|
|
56
|
+
viewBox="0 0 24 24"
|
|
57
|
+
stroke="currentColor"
|
|
58
|
+
>
|
|
59
|
+
<path
|
|
60
|
+
strokeLinecap="round"
|
|
61
|
+
strokeLinejoin="round"
|
|
62
|
+
strokeWidth={2}
|
|
63
|
+
d="M12 18h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"
|
|
64
|
+
/>
|
|
65
|
+
</svg>
|
|
66
|
+
) : (
|
|
67
|
+
<svg
|
|
68
|
+
className="w-5 h-5 text-muted-foreground"
|
|
69
|
+
fill="none"
|
|
70
|
+
viewBox="0 0 24 24"
|
|
71
|
+
stroke="currentColor"
|
|
72
|
+
>
|
|
73
|
+
<path
|
|
74
|
+
strokeLinecap="round"
|
|
75
|
+
strokeLinejoin="round"
|
|
76
|
+
strokeWidth={2}
|
|
77
|
+
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
78
|
+
/>
|
|
79
|
+
</svg>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
{/* Session Info */}
|
|
84
|
+
<div className="space-y-1">
|
|
85
|
+
<div className="flex items-center gap-2">
|
|
86
|
+
<span className="font-medium">
|
|
87
|
+
{browser} on {os}
|
|
88
|
+
</span>
|
|
89
|
+
{isCurrentSession && (
|
|
90
|
+
<span className="px-2 py-0.5 text-xs font-medium bg-primary text-primary-foreground rounded-full">
|
|
91
|
+
Current
|
|
92
|
+
</span>
|
|
93
|
+
)}
|
|
94
|
+
{isExpired && (
|
|
95
|
+
<span className="px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground rounded-full">
|
|
96
|
+
Expired
|
|
97
|
+
</span>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div className="text-sm text-muted-foreground space-y-0.5">
|
|
102
|
+
{session.ipAddress && (
|
|
103
|
+
<p>IP: {session.ipAddress}</p>
|
|
104
|
+
)}
|
|
105
|
+
<p>
|
|
106
|
+
Created: {createdAt.toLocaleDateString()} at{" "}
|
|
107
|
+
{createdAt.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
|
108
|
+
</p>
|
|
109
|
+
<p>
|
|
110
|
+
{isExpired ? "Expired" : "Expires"}:{" "}
|
|
111
|
+
{expiresAt.toLocaleDateString()} at{" "}
|
|
112
|
+
{expiresAt.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
|
113
|
+
</p>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
{/* Revoke Button */}
|
|
119
|
+
{!isCurrentSession && !isExpired && (
|
|
120
|
+
<Button
|
|
121
|
+
variant="outline"
|
|
122
|
+
size="sm"
|
|
123
|
+
onClick={handleRevoke}
|
|
124
|
+
className="shrink-0"
|
|
125
|
+
>
|
|
126
|
+
Revoke
|
|
127
|
+
</Button>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { Adjust, AdjustConfig } from 'react-native-adjust';
|
|
3
|
+
import Constants from 'expo-constants';
|
|
4
|
+
import { logger } from '../utils/logger';
|
|
5
|
+
|
|
6
|
+
class AdjustService {
|
|
7
|
+
private static readonly APP_TOKEN = Constants.expoConfig?.extra?.adjust?.appToken || '';
|
|
8
|
+
private static readonly ATT_CONSENT_WAITING_INTERVAL = 120;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Initialize Adjust SDK
|
|
12
|
+
*/
|
|
13
|
+
initialize(): void {
|
|
14
|
+
try {
|
|
15
|
+
logger.info('AdjustService: Initializing Adjust SDK...');
|
|
16
|
+
|
|
17
|
+
// Determine environment from config or fallback to __DEV__
|
|
18
|
+
const configEnvironment = Constants.expoConfig?.extra?.adjust?.environment;
|
|
19
|
+
const environment = configEnvironment === 'production'
|
|
20
|
+
? AdjustConfig.EnvironmentProduction
|
|
21
|
+
: configEnvironment === 'sandbox'
|
|
22
|
+
? AdjustConfig.EnvironmentSandbox
|
|
23
|
+
: __DEV__ ? AdjustConfig.EnvironmentSandbox : AdjustConfig.EnvironmentProduction;
|
|
24
|
+
|
|
25
|
+
const adjustConfig = new AdjustConfig(
|
|
26
|
+
AdjustService.APP_TOKEN,
|
|
27
|
+
environment
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Set ATT consent waiting interval - gives time for ATT prompt response
|
|
31
|
+
adjustConfig.setAttConsentWaitingInterval(AdjustService.ATT_CONSENT_WAITING_INTERVAL);
|
|
32
|
+
|
|
33
|
+
// Initialize the SDK
|
|
34
|
+
Adjust.initSdk(adjustConfig);
|
|
35
|
+
|
|
36
|
+
logger.info('AdjustService: Adjust SDK initialized successfully');
|
|
37
|
+
} catch (error) {
|
|
38
|
+
logger.error('AdjustService: Failed to initialize Adjust SDK', { error });
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Retrieve ADID with retry mechanism and exponential backoff
|
|
45
|
+
*/
|
|
46
|
+
async retrieveAdidWithRetry(
|
|
47
|
+
maxAttempts: number = 5,
|
|
48
|
+
maxTotalTimeMs: number = 10000,
|
|
49
|
+
initialDelayMs: number = 500
|
|
50
|
+
): Promise<string | null> {
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
let cumulativeDelay = 0;
|
|
53
|
+
|
|
54
|
+
logger.info('AdjustService: Starting ADID retrieval with exponential backoff retry...');
|
|
55
|
+
|
|
56
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
57
|
+
// Check if we have time for this attempt
|
|
58
|
+
const elapsedTime = Date.now() - startTime;
|
|
59
|
+
const remainingTime = maxTotalTimeMs - elapsedTime - cumulativeDelay;
|
|
60
|
+
|
|
61
|
+
if (remainingTime <= 0) {
|
|
62
|
+
logger.info(`AdjustService: ADID retry timeout reached before attempt ${attempt}`);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Calculate delay for this attempt (exponential backoff)
|
|
67
|
+
const delay = attempt === 1 ? initialDelayMs : Math.min(
|
|
68
|
+
initialDelayMs * Math.pow(2, attempt - 2),
|
|
69
|
+
remainingTime
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Wait before attempting (except for first attempt)
|
|
73
|
+
if (attempt > 1) {
|
|
74
|
+
logger.info(`AdjustService: ADID retry attempt ${attempt}/${maxAttempts} - waiting ${delay}ms...`);
|
|
75
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
76
|
+
cumulativeDelay += delay;
|
|
77
|
+
} else {
|
|
78
|
+
logger.info(`AdjustService: ADID attempt ${attempt}/${maxAttempts} (initial attempt)...`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check remaining time after delay
|
|
82
|
+
const currentElapsed = Date.now() - startTime;
|
|
83
|
+
if (currentElapsed >= maxTotalTimeMs) {
|
|
84
|
+
logger.info('AdjustService: ADID retry timeout reached after waiting');
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Attempt to retrieve ADID
|
|
89
|
+
try {
|
|
90
|
+
const result = await new Promise<string | null>((resolve) => {
|
|
91
|
+
Adjust.getAdid((retrievedAdid) => {
|
|
92
|
+
logger.info(`AdjustService: ADID attempt ${attempt} result:`, retrievedAdid);
|
|
93
|
+
resolve(retrievedAdid);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (result) {
|
|
98
|
+
const totalTime = Date.now() - startTime;
|
|
99
|
+
logger.info(`AdjustService: ADID retrieved successfully on attempt ${attempt} after ${totalTime}ms:`, result);
|
|
100
|
+
return result;
|
|
101
|
+
} else {
|
|
102
|
+
logger.info(`AdjustService: ADID attempt ${attempt} returned null`);
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.error(`AdjustService: ADID attempt ${attempt} failed:`, error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const totalTime = Date.now() - startTime;
|
|
110
|
+
logger.info(`AdjustService: ADID retrieval failed after ${maxAttempts} attempts in ${totalTime}ms`);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get ADID immediately (single attempt)
|
|
116
|
+
*/
|
|
117
|
+
async getAdid(): Promise<string | null> {
|
|
118
|
+
try {
|
|
119
|
+
return new Promise<string | null>((resolve) => {
|
|
120
|
+
Adjust.getAdid((adid) => {
|
|
121
|
+
resolve(adid);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
logger.error('AdjustService: Failed to get ADID', { error });
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Track event with Adjust
|
|
132
|
+
*/
|
|
133
|
+
trackEvent(eventToken: string, revenue?: number, currency?: string): void {
|
|
134
|
+
try {
|
|
135
|
+
// Implementation would depend on your event tracking needs
|
|
136
|
+
logger.info('AdjustService: Event tracking would be implemented here', {
|
|
137
|
+
eventToken,
|
|
138
|
+
revenue,
|
|
139
|
+
currency
|
|
140
|
+
});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error('AdjustService: Failed to track event', { error });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get environment info
|
|
148
|
+
*/
|
|
149
|
+
getEnvironment(): string {
|
|
150
|
+
const configEnvironment = Constants.expoConfig?.extra?.adjust?.environment;
|
|
151
|
+
return configEnvironment || (__DEV__ ? 'sandbox' : 'production');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get app token
|
|
156
|
+
*/
|
|
157
|
+
getAppToken(): string {
|
|
158
|
+
return AdjustService.APP_TOKEN;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Export singleton instance
|
|
163
|
+
export const adjustService = new AdjustService();
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { adjustService } from '../services/adjustService';
|
|
3
|
+
import { useRevenueCatStore } from './revenuecat.store';
|
|
4
|
+
import { useScateStore } from './scate.store';
|
|
5
|
+
import { logger } from '../utils/logger';
|
|
6
|
+
|
|
7
|
+
interface AdjustState {
|
|
8
|
+
// State
|
|
9
|
+
isInitialized: boolean;
|
|
10
|
+
adid: string | null;
|
|
11
|
+
isRetrievingAdid: boolean;
|
|
12
|
+
isLoading: boolean;
|
|
13
|
+
error: string | null;
|
|
14
|
+
|
|
15
|
+
// Actions
|
|
16
|
+
initialize: () => void;
|
|
17
|
+
retrieveAdid: () => Promise<void>;
|
|
18
|
+
retrieveAdidWithRetry: () => Promise<void>;
|
|
19
|
+
setAdid: (adid: string | null) => void;
|
|
20
|
+
setLoading: (loading: boolean) => void;
|
|
21
|
+
setError: (error: string | null) => void;
|
|
22
|
+
reset: () => void;
|
|
23
|
+
notifyOtherStores: (adid: string) => void;
|
|
24
|
+
|
|
25
|
+
// Computed values
|
|
26
|
+
hasAdid: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const useAdjustStore = create<AdjustState>((set, get) => ({
|
|
30
|
+
// Initial state
|
|
31
|
+
isInitialized: false,
|
|
32
|
+
adid: null,
|
|
33
|
+
isRetrievingAdid: false,
|
|
34
|
+
isLoading: false,
|
|
35
|
+
error: null,
|
|
36
|
+
hasAdid: false,
|
|
37
|
+
|
|
38
|
+
// Actions
|
|
39
|
+
initialize: () => {
|
|
40
|
+
const state = get();
|
|
41
|
+
if (state.isInitialized) {
|
|
42
|
+
logger.info('AdjustStore: Already initialized, skipping');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
set({ isLoading: true, error: null });
|
|
48
|
+
logger.info('AdjustStore: Initializing Adjust SDK...');
|
|
49
|
+
|
|
50
|
+
adjustService.initialize();
|
|
51
|
+
|
|
52
|
+
set({
|
|
53
|
+
isInitialized: true,
|
|
54
|
+
isLoading: false,
|
|
55
|
+
error: null,
|
|
56
|
+
hasAdid: false
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
logger.info('AdjustStore: Adjust SDK initialized successfully');
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to initialize Adjust SDK';
|
|
62
|
+
logger.error('AdjustStore: Failed to initialize Adjust SDK', { error });
|
|
63
|
+
set({
|
|
64
|
+
error: errorMessage,
|
|
65
|
+
isLoading: false,
|
|
66
|
+
isInitialized: false,
|
|
67
|
+
hasAdid: false
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
retrieveAdid: async () => {
|
|
73
|
+
const state = get();
|
|
74
|
+
if (state.isRetrievingAdid || state.adid) {
|
|
75
|
+
logger.info('AdjustStore: ADID retrieval already in progress or ADID already available');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
set({ isRetrievingAdid: true, error: null });
|
|
81
|
+
logger.info('AdjustStore: Retrieving ADID...');
|
|
82
|
+
|
|
83
|
+
const adid = await adjustService.getAdid();
|
|
84
|
+
|
|
85
|
+
if (adid) {
|
|
86
|
+
set({
|
|
87
|
+
adid,
|
|
88
|
+
isRetrievingAdid: false,
|
|
89
|
+
error: null,
|
|
90
|
+
hasAdid: true
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Notify other stores about ADID availability
|
|
94
|
+
get().notifyOtherStores(adid);
|
|
95
|
+
|
|
96
|
+
logger.info('AdjustStore: ADID retrieved successfully', { adid });
|
|
97
|
+
} else {
|
|
98
|
+
set({
|
|
99
|
+
adid: null,
|
|
100
|
+
isRetrievingAdid: false,
|
|
101
|
+
error: 'ADID not available',
|
|
102
|
+
hasAdid: false
|
|
103
|
+
});
|
|
104
|
+
logger.info('AdjustStore: ADID not available');
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to retrieve ADID';
|
|
108
|
+
logger.error('AdjustStore: Failed to retrieve ADID', { error });
|
|
109
|
+
set({
|
|
110
|
+
error: errorMessage,
|
|
111
|
+
isRetrievingAdid: false,
|
|
112
|
+
adid: null,
|
|
113
|
+
hasAdid: false
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
retrieveAdidWithRetry: async () => {
|
|
119
|
+
const state = get();
|
|
120
|
+
if (state.isRetrievingAdid || state.adid) {
|
|
121
|
+
logger.info('AdjustStore: ADID retrieval with retry already in progress or ADID already available');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
set({ isRetrievingAdid: true, error: null });
|
|
127
|
+
logger.info('AdjustStore: Retrieving ADID with retry mechanism...');
|
|
128
|
+
|
|
129
|
+
const adid = await adjustService.retrieveAdidWithRetry();
|
|
130
|
+
|
|
131
|
+
if (adid) {
|
|
132
|
+
set({
|
|
133
|
+
adid,
|
|
134
|
+
isRetrievingAdid: false,
|
|
135
|
+
error: null,
|
|
136
|
+
hasAdid: true
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Notify other stores about ADID availability
|
|
140
|
+
get().notifyOtherStores(adid);
|
|
141
|
+
|
|
142
|
+
logger.info('AdjustStore: ADID retrieved successfully with retry', { adid });
|
|
143
|
+
} else {
|
|
144
|
+
set({
|
|
145
|
+
adid: null,
|
|
146
|
+
isRetrievingAdid: false,
|
|
147
|
+
error: 'ADID not available after retry attempts',
|
|
148
|
+
hasAdid: false
|
|
149
|
+
});
|
|
150
|
+
logger.info('AdjustStore: ADID not available after retry attempts');
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to retrieve ADID with retry';
|
|
154
|
+
logger.error('AdjustStore: Failed to retrieve ADID with retry', { error });
|
|
155
|
+
set({
|
|
156
|
+
error: errorMessage,
|
|
157
|
+
isRetrievingAdid: false,
|
|
158
|
+
adid: null,
|
|
159
|
+
hasAdid: false
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
setAdid: (adid) => {
|
|
165
|
+
const currentState = get();
|
|
166
|
+
if (currentState.adid === adid) {
|
|
167
|
+
return; // No change needed
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
set({ adid, hasAdid: !!adid });
|
|
171
|
+
|
|
172
|
+
if (adid) {
|
|
173
|
+
// Notify other stores about ADID availability
|
|
174
|
+
get().notifyOtherStores(adid);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
logger.info('AdjustStore: ADID set', { adid });
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
setLoading: (isLoading) => set({ isLoading }),
|
|
181
|
+
|
|
182
|
+
setError: (error) => set({ error }),
|
|
183
|
+
|
|
184
|
+
reset: () => {
|
|
185
|
+
set({
|
|
186
|
+
isInitialized: false,
|
|
187
|
+
adid: null,
|
|
188
|
+
isRetrievingAdid: false,
|
|
189
|
+
isLoading: false,
|
|
190
|
+
error: null,
|
|
191
|
+
hasAdid: false
|
|
192
|
+
});
|
|
193
|
+
logger.info('AdjustStore: Store reset');
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
// Helper method to notify other stores about ADID
|
|
197
|
+
notifyOtherStores: (adid: string) => {
|
|
198
|
+
try {
|
|
199
|
+
// Notify RevenueCat store
|
|
200
|
+
const revenueCatStore = useRevenueCatStore.getState();
|
|
201
|
+
if (revenueCatStore.isInitialized) {
|
|
202
|
+
revenueCatStore.setAdjustId(adid);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Notify Scate store
|
|
206
|
+
const scateStore = useScateStore.getState();
|
|
207
|
+
if (scateStore.isInitialized) {
|
|
208
|
+
scateStore.setAdid(adid);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
logger.info('AdjustStore: Other stores notified about ADID', { adid });
|
|
212
|
+
} catch (error) {
|
|
213
|
+
logger.error('AdjustStore: Failed to notify other stores about ADID', { error });
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
}));
|
|
218
|
+
|
|
219
|
+
// Selectors for commonly used Adjust state
|
|
220
|
+
export const useAdjust = () => {
|
|
221
|
+
const state = useAdjustStore();
|
|
222
|
+
return {
|
|
223
|
+
isInitialized: state.isInitialized,
|
|
224
|
+
adid: state.adid,
|
|
225
|
+
isRetrievingAdid: state.isRetrievingAdid,
|
|
226
|
+
isLoading: state.isLoading,
|
|
227
|
+
error: state.error,
|
|
228
|
+
hasAdid: state.hasAdid,
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const useAdjustActions = () => {
|
|
233
|
+
const state = useAdjustStore();
|
|
234
|
+
return {
|
|
235
|
+
initialize: state.initialize,
|
|
236
|
+
retrieveAdid: state.retrieveAdid,
|
|
237
|
+
retrieveAdidWithRetry: state.retrieveAdidWithRetry,
|
|
238
|
+
setAdid: state.setAdid,
|
|
239
|
+
setLoading: state.setLoading,
|
|
240
|
+
setError: state.setError,
|
|
241
|
+
reset: state.reset,
|
|
242
|
+
};
|
|
243
|
+
};
|