sare26 commited on
Commit
aa3fd22
·
verified ·
1 Parent(s): 25f28bb

Update artifacts/fb-publisher/src/App.tsx

Browse files
Files changed (1) hide show
  1. artifacts/fb-publisher/src/App.tsx +86 -153
artifacts/fb-publisher/src/App.tsx CHANGED
@@ -1,14 +1,5 @@
1
  import { useEffect, useRef, useState } from "react";
2
- import {
3
- ClerkProvider,
4
- SignIn,
5
- SignUp,
6
- Show,
7
- useClerk,
8
- useAuth,
9
- } from "@clerk/react";
10
- import { arSA } from "@clerk/localizations";
11
- import { shadcn } from "@clerk/themes";
12
  import { setAuthTokenGetter } from "@workspace/api-client-react";
13
  import {
14
  Switch,
@@ -30,8 +21,8 @@ import Admin from "@/pages/Admin";
30
  import NotFound from "@/pages/not-found";
31
  import UpdateBanner from "@/components/UpdateBanner";
32
 
33
- const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string;
34
- const clerkProxyUrl = import.meta.env.VITE_CLERK_PROXY_URL as string | undefined;
35
  const basePath = import.meta.env.BASE_URL.replace(/\/$/, "");
36
 
37
  function stripBase(path: string): string {
@@ -40,66 +31,17 @@ function stripBase(path: string): string {
40
  : path;
41
  }
42
 
43
- if (!clerkPubKey) {
44
- throw new Error("Missing VITE_CLERK_PUBLISHABLE_KEY");
45
  }
46
 
47
- const clerkAppearance = {
48
- theme: shadcn,
49
- cssLayerName: "clerk",
50
- options: {
51
- logoPlacement: "inside" as const,
52
- logoLinkUrl: basePath || "/",
53
- logoImageUrl: `${window.location.origin}${basePath}/logo.svg`,
54
- },
55
- variables: {
56
- colorPrimary: "#bb86fc",
57
- colorForeground: "#f5f5f7",
58
- colorMutedForeground: "#a8a8b3",
59
- colorDanger: "#ff6b6b",
60
- colorBackground: "#16162a",
61
- colorInput: "#1f1f3a",
62
- colorInputForeground: "#f5f5f7",
63
- colorNeutral: "#3a3a55",
64
- fontFamily: '"Tajawal", "Inter", system-ui, sans-serif',
65
- borderRadius: "12px",
66
- },
67
- elements: {
68
- rootBox: "w-full",
69
- cardBox: "bg-[#16162a] rounded-2xl w-[440px] max-w-full overflow-hidden border border-[#3a3a55]/40",
70
- card: "!shadow-none !border-0 !bg-transparent !rounded-none",
71
- footer: "!shadow-none !border-0 !bg-transparent !rounded-none",
72
- headerTitle: "text-white",
73
- headerSubtitle: "text-gray-300",
74
- socialButtonsBlockButtonText: "text-white",
75
- formFieldLabel: "text-gray-200",
76
- footerActionLink: "text-[#bb86fc] hover:text-[#d4b3ff]",
77
- footerActionText: "text-gray-300",
78
- dividerText: "text-gray-400",
79
- identityPreviewEditButton: "text-[#bb86fc]",
80
- formFieldSuccessText: "text-green-300",
81
- alertText: "text-gray-200",
82
- logoBox: "mb-2",
83
- logoImage: "h-10 w-10",
84
- socialButtonsBlockButton: "bg-[#1f1f3a] hover:bg-[#2a2a4a] border border-[#3a3a55] text-white",
85
- formButtonPrimary: "btn-primary-grad",
86
- formFieldInput: "bg-[#1f1f3a] border-[#3a3a55] text-white",
87
- footerAction: "text-gray-300",
88
- dividerLine: "bg-[#3a3a55]",
89
- alert: "bg-[#1f1f3a] border-[#3a3a55]",
90
- otpCodeFieldInput: "bg-[#1f1f3a] border-[#3a3a55] text-white",
91
- main: "text-white",
92
- },
93
- };
94
-
95
  function SignInPage() {
96
  return (
97
  <div className="flex min-h-[100dvh] items-center justify-center px-4">
98
  <SignIn
99
- routing="path"
100
- path={`${basePath}/sign-in`}
101
- signUpUrl={`${basePath}/sign-up`}
102
- forceRedirectUrl={`${basePath}/app`}
103
  />
104
  </div>
105
  );
@@ -110,10 +52,8 @@ function SignUpPage() {
110
  <div className="flex min-h-[100dvh] items-center justify-center px-4">
111
  <div className="w-full max-w-md space-y-4">
112
  <SignUp
113
- routing="path"
114
- path={`${basePath}/sign-up`}
115
- signInUrl={`${basePath}/sign-in`}
116
- forceRedirectUrl={`${basePath}/app`}
117
  />
118
  <div className="rounded-lg border border-red-500/30 bg-red-950/20 p-3 text-center text-sm text-red-300 backdrop-blur-sm">
119
  ⚠️ <strong>تنبيه هام لمكافحة السبام والرسائل المزعجة (Spam):</strong>{" "}
@@ -127,43 +67,44 @@ function SignUpPage() {
127
  );
128
  }
129
 
 
 
 
 
 
 
 
 
 
 
130
  function ProtectedPublisher() {
131
- return (
132
- <>
133
- <Show when="signed-in">
134
- <Publisher />
135
- </Show>
136
- <Show when="signed-out">
137
- <Redirect to="/sign-in" />
138
- </Show>
139
- </>
140
- );
141
  }
142
 
143
  function ProtectedSubscription() {
144
- return (
145
- <>
146
- <Show when="signed-in">
147
- <Subscription />
148
- </Show>
149
- <Show when="signed-out">
150
- <Redirect to="/sign-in" />
151
- </Show>
152
- </>
153
- );
154
  }
155
 
156
  function ProtectedExtension() {
157
- return (
158
- <>
159
- <Show when="signed-in">
160
- <InstallExtension />
161
- </Show>
162
- <Show when="signed-out">
163
- <Redirect to="/sign-in" />
164
- </Show>
165
- </>
166
- );
167
  }
168
 
169
  function ServerKeepAlive() {
@@ -200,82 +141,74 @@ function ServerKeepAlive() {
200
  );
201
  }
202
 
203
- function ClerkAuthTokenSetter() {
204
- const { getToken } = useAuth();
 
205
  useEffect(() => {
206
- setAuthTokenGetter(() => getToken());
 
 
 
207
  return () => setAuthTokenGetter(null);
208
- }, [getToken]);
 
209
  return null;
210
  }
211
 
212
- function ClerkQueryClientCacheInvalidator() {
213
- const { addListener } = useClerk();
214
  const qc = useQueryClient();
215
  const prevUserIdRef = useRef<string | null | undefined>(undefined);
216
 
217
  useEffect(() => {
218
- const unsubscribe = addListener(({ user }) => {
219
- const userId = user?.id ?? null;
220
- if (prevUserIdRef.current !== undefined && prevUserIdRef.current !== userId) {
221
- qc.clear();
222
- }
223
- prevUserIdRef.current = userId;
224
- });
225
- return unsubscribe;
226
- }, [addListener, qc]);
227
 
228
  return null;
229
  }
230
 
231
- function ClerkProviderWithRoutes() {
232
  const [, setLocation] = useLocation();
233
 
234
  return (
235
- <ClerkProvider
236
- publishableKey={clerkPubKey}
237
- proxyUrl={clerkProxyUrl}
238
- appearance={clerkAppearance}
239
- signInUrl={`${basePath}/sign-in`}
240
- signUpUrl={`${basePath}/sign-up`}
241
- localization={{
242
- ...arSA,
243
- formFieldInputPlaceholder__emailAddress: "أدخل بريدك الإلكتروني",
244
- signIn: { ...arSA.signIn, start: { ...((arSA.signIn as any)?.start), title: "تسجيل الدخول", subtitle: "أدخل بياناتك للوصول إلى حسابك" } },
245
- signUp: { ...arSA.signUp, start: { ...((arSA.signUp as any)?.start), title: "إنشاء حساب جديد", subtitle: "ابدأ تجربتك المجانية لمدة شهر كامل" } },
246
- signIn__forgotPasswordLink: "نسيت كلمة المرور؟",
247
- }}
248
- routerPush={(to) => setLocation(stripBase(to))}
249
- routerReplace={(to) => setLocation(stripBase(to), { replace: true })}
250
  >
251
- <QueryClientProvider client={queryClient}>
252
- <ServerKeepAlive />
253
- <ClerkAuthTokenSetter />
254
- <ClerkQueryClientCacheInvalidator />
255
- <TooltipProvider>
256
- <Switch>
257
- <Route path="/" component={Landing} />
258
- <Route path="/sign-in/*?" component={SignInPage} />
259
- <Route path="/sign-up/*?" component={SignUpPage} />
260
- <Route path="/app" component={ProtectedPublisher} />
261
- <Route path="/subscription" component={ProtectedSubscription} />
262
- <Route path="/subscription/return" component={SubscriptionReturn} />
263
- <Route path="/extension" component={ProtectedExtension} />
264
- <Route path="/admin" component={Admin} />
265
- <Route component={NotFound} />
266
- </Switch>
267
- <UpdateBanner />
268
- <Toaster />
269
- </TooltipProvider>
270
- </QueryClientProvider>
271
- </ClerkProvider>
 
 
272
  );
273
  }
274
 
275
  export default function App() {
276
  return (
277
  <WouterRouter base={basePath}>
278
- <ClerkProviderWithRoutes />
279
  </WouterRouter>
280
  );
281
  }
 
1
  import { useEffect, useRef, useState } from "react";
2
+ import { StackProvider, StackTheme, useStackApp, Stack, SignIn, SignUp, useUser } from "@stackframe/react";
 
 
 
 
 
 
 
 
 
3
  import { setAuthTokenGetter } from "@workspace/api-client-react";
4
  import {
5
  Switch,
 
21
  import NotFound from "@/pages/not-found";
22
  import UpdateBanner from "@/components/UpdateBanner";
23
 
24
+ const stackProjectId = import.meta.env.VITE_STACK_PROJECT_ID as string;
25
+ const stackPublishableKey = import.meta.env.VITE_STACK_PUBLISHABLE_KEY as string;
26
  const basePath = import.meta.env.BASE_URL.replace(/\/$/, "");
27
 
28
  function stripBase(path: string): string {
 
31
  : path;
32
  }
33
 
34
+ if (!stackProjectId) {
35
+ throw new Error("Missing VITE_STACK_PROJECT_ID");
36
  }
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  function SignInPage() {
39
  return (
40
  <div className="flex min-h-[100dvh] items-center justify-center px-4">
41
  <SignIn
42
+ firstTab="password"
43
+ automaticRedirect={true}
44
+ redirectUrl={`${basePath}/app`}
 
45
  />
46
  </div>
47
  );
 
52
  <div className="flex min-h-[100dvh] items-center justify-center px-4">
53
  <div className="w-full max-w-md space-y-4">
54
  <SignUp
55
+ automaticRedirect={true}
56
+ redirectUrl={`${basePath}/app`}
 
 
57
  />
58
  <div className="rounded-lg border border-red-500/30 bg-red-950/20 p-3 text-center text-sm text-red-300 backdrop-blur-sm">
59
  ⚠️ <strong>تنبيه هام لمكافحة السبام والرسائل المزعجة (Spam):</strong>{" "}
 
67
  );
68
  }
69
 
70
+ function HomePage() {
71
+ const user = useUser();
72
+
73
+ if (user) {
74
+ return <Redirect to="/app" />;
75
+ }
76
+
77
+ return <Landing />;
78
+ }
79
+
80
  function ProtectedPublisher() {
81
+ const user = useUser();
82
+
83
+ if (!user) {
84
+ return <Redirect to="/sign-in" />;
85
+ }
86
+
87
+ return <Publisher />;
 
 
 
88
  }
89
 
90
  function ProtectedSubscription() {
91
+ const user = useUser();
92
+
93
+ if (!user) {
94
+ return <Redirect to="/sign-in" />;
95
+ }
96
+
97
+ return <Subscription />;
 
 
 
98
  }
99
 
100
  function ProtectedExtension() {
101
+ const user = useUser();
102
+
103
+ if (!user) {
104
+ return <Redirect to="/sign-in" />;
105
+ }
106
+
107
+ return <InstallExtension />;
 
 
 
108
  }
109
 
110
  function ServerKeepAlive() {
 
141
  );
142
  }
143
 
144
+ function StackAuthTokenSetter() {
145
+ const app = useStackApp();
146
+
147
  useEffect(() => {
148
+ setAuthTokenGetter(async () => {
149
+ const token = await app.getAccessToken();
150
+ return token ?? null;
151
+ });
152
  return () => setAuthTokenGetter(null);
153
+ }, [app]);
154
+
155
  return null;
156
  }
157
 
158
+ function StackQueryClientCacheInvalidator() {
159
+ const user = useUser();
160
  const qc = useQueryClient();
161
  const prevUserIdRef = useRef<string | null | undefined>(undefined);
162
 
163
  useEffect(() => {
164
+ const userId = user?.id ?? null;
165
+ if (prevUserIdRef.current !== undefined && prevUserIdRef.current !== userId) {
166
+ qc.clear();
167
+ }
168
+ prevUserIdRef.current = userId;
169
+ }, [user?.id, qc]);
 
 
 
170
 
171
  return null;
172
  }
173
 
174
+ function StackProviderWithRoutes() {
175
  const [, setLocation] = useLocation();
176
 
177
  return (
178
+ <StackProvider
179
+ projectId={stackProjectId}
180
+ publishableClientKey={stackPublishableKey}
 
 
 
 
 
 
 
 
 
 
 
 
181
  >
182
+ <StackTheme>
183
+ <QueryClientProvider client={queryClient}>
184
+ <ServerKeepAlive />
185
+ <StackAuthTokenSetter />
186
+ <StackQueryClientCacheInvalidator />
187
+ <TooltipProvider>
188
+ <Switch>
189
+ <Route path="/" component={HomePage} />
190
+ <Route path="/sign-in/*?" component={SignInPage} />
191
+ <Route path="/sign-up/*?" component={SignUpPage} />
192
+ <Route path="/app" component={ProtectedPublisher} />
193
+ <Route path="/subscription" component={ProtectedSubscription} />
194
+ <Route path="/subscription/return" component={SubscriptionReturn} />
195
+ <Route path="/extension" component={ProtectedExtension} />
196
+ <Route path="/admin" component={Admin} />
197
+ <Route component={NotFound} />
198
+ </Switch>
199
+ <UpdateBanner />
200
+ <Toaster />
201
+ </TooltipProvider>
202
+ </QueryClientProvider>
203
+ </StackTheme>
204
+ </StackProvider>
205
  );
206
  }
207
 
208
  export default function App() {
209
  return (
210
  <WouterRouter base={basePath}>
211
+ <StackProviderWithRoutes />
212
  </WouterRouter>
213
  );
214
  }