@entropy-softworks/ui 2026.9.9 → 2026.9.10

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.
@@ -1,31 +1,27 @@
1
1
  import React, { useCallback, useEffect, useRef, useState } from "react";
2
- import { Platform, ScrollView, View } from "react-native";
3
- import { SafeAreaView } from "react-native-safe-area-context";
2
+ import { Platform } from "react-native";
4
3
  import { useLocalSearchParams } from "expo-router";
5
4
  import { useNavigation } from "../../context/NavigationContext";
6
- import { StatusBar } from "expo-status-bar";
7
5
  import { Ionicons } from "@expo/vector-icons";
8
6
  import { useColorScheme } from "nativewind";
9
7
  import * as Haptics from "expo-haptics";
10
8
  import Toast from "react-native-toast-message";
11
9
  import { MutedText, Text } from "../../components/text";
12
10
  import { Button, IconButton } from "../../components/button";
13
- import {
14
- Card,
15
- CardContent,
16
- CardDescription,
17
- CardFooter,
18
- CardHeader,
19
- CardTitle,
20
- } from "../../components/card";
21
11
  import { withClickTracking } from "../../services/telemetry";
22
12
  import { Input } from "../../components/input";
23
13
  import AuthService from "../../services/auth.service";
24
14
  import { useAuth } from "../../hooks/useAuth";
25
15
  import { useAppConfig } from "../../context/AppConfigContext";
26
16
  import { getErrorMessage } from "../../utils/error";
17
+ import { AuthPage, type AuthPageLayout } from "./AuthPage";
27
18
 
28
- export function VerifyEmailScreen() {
19
+ export interface VerifyEmailScreenProps {
20
+ /** See `AuthPageLayout`. Defaults to the card, as it always was. */
21
+ layout?: AuthPageLayout;
22
+ }
23
+
24
+ export function VerifyEmailScreen({ layout }: VerifyEmailScreenProps = {}) {
29
25
  const { refreshUser, logout, user } = useAuth();
30
26
  const { routes } = useAppConfig();
31
27
  const router = useNavigation();
@@ -214,106 +210,87 @@ export function VerifyEmailScreen() {
214
210
  };
215
211
 
216
212
  return (
217
- <SafeAreaView className="flex-1 bg-background dark:bg-black">
218
- <StatusBar style={isDark ? "light" : "dark"} />
219
- <ScrollView
220
- className="flex-1"
221
- contentContainerClassName="flex-1 items-center justify-center p-6"
222
- keyboardShouldPersistTaps="handled"
223
- >
224
- <View className="mt-4 self-start absolute top-4 left-6">
225
- <IconButton
213
+ <AuthPage
214
+ layout={layout}
215
+ title="Verify your email"
216
+ subtitle={`Enter the verification code sent to ${params.email || "your email"}`}
217
+ corner={
218
+ <IconButton
219
+ variant="ghost"
220
+ telemetryId="auth.verify_email.logout"
221
+ onPress={() => logout()}
222
+ accessibilityLabel="Sign out"
223
+ accessibilityRole="button"
224
+ >
225
+ <Ionicons name="log-out-outline" size={24} color={isDark ? "#FFFFFF" : "#000000"} />
226
+ </IconButton>
227
+ }
228
+ footer={
229
+ <>
230
+ <Button
231
+ className="h-14 w-full"
232
+ telemetryId="auth.verify_email.verify"
233
+ onPress={handleVerify}
234
+ isLoading={isLoading}
235
+ accessibilityLabel="Continue"
236
+ accessibilityRole="button"
237
+ accessibilityHint="Double tap to verify your email"
238
+ testID="verify-submit"
239
+ >
240
+ Continue
241
+ </Button>
242
+
243
+ <Button
226
244
  variant="ghost"
227
- telemetryId="auth.verify_email.logout"
245
+ className="w-full"
246
+ telemetryId="auth.verify_email.logout_2"
228
247
  onPress={() => logout()}
229
248
  accessibilityLabel="Sign out"
230
249
  accessibilityRole="button"
231
250
  >
232
- <Ionicons name="log-out-outline" size={24} color={isDark ? "#FFFFFF" : "#000000"} />
233
- </IconButton>
234
- </View>
235
-
236
- <Card className="w-full max-w-[440px]">
237
- <CardHeader className="items-center">
238
- <CardTitle className="text-center text-2xl">Verify your email</CardTitle>
239
- <CardDescription className="text-center text-base">
240
- {`Enter the verification code sent to\n${params.email || "your email"}`}
241
- </CardDescription>
242
- </CardHeader>
243
-
244
- <CardContent className="gap-4 pb-3">
245
- <Input
246
- label="Verification Code"
247
- labelClassName="text-base"
248
- className="h-14"
249
- value={code}
250
- onChangeText={(text) => {
251
- setCode(text);
252
- if (error) {
253
- setError("");
254
- }
255
- }}
256
- error={error}
257
- placeholder="Paste your verification code"
258
- autoCapitalize="none"
259
- autoFocus
260
- returnKeyType="done"
261
- onSubmitEditing={handleVerify}
262
- accessibilityLabel="Verification code"
263
- testID="verify-code-input"
264
- />
265
-
266
- <MutedText className="text-center text-base">
267
- Didn&apos;t receive the code?{" "}
268
- {resendCooldown > 0 ? (
269
- <Text className="text-base text-foreground">Resend ({resendCooldown})</Text>
270
- ) : (
271
- <Text
272
- className="text-base text-foreground underline"
273
- onPress={withClickTracking("auth.verify_email.resend", handleResend)}
274
- accessibilityRole="button"
275
- accessibilityLabel="Resend verification code"
276
- >
277
- Resend
278
- </Text>
279
- )}
280
- </MutedText>
281
- </CardContent>
282
-
283
- {/* `items-stretch`, like every other stacked footer in this package.
284
- `CardFooter` defaults to `flex-row items-center`, and turning it
285
- into a column leaves the centring behind — so a `w-full` child
286
- resolves its percentage against a parent that is itself sizing to
287
- its content. On Android that measured out wide, with the label
288
- sitting in one half of a button twice the width it asked for. */}
289
- <CardFooter className="flex-col items-stretch gap-1 border-t-0 pt-0">
290
- <Button
291
- className="h-14 w-full"
292
- telemetryId="auth.verify_email.verify"
293
- onPress={handleVerify}
294
- isLoading={isLoading}
295
- accessibilityLabel="Continue"
296
- accessibilityRole="button"
297
- accessibilityHint="Double tap to verify your email"
298
- testID="verify-submit"
299
- >
300
- Continue
301
- </Button>
251
+ Sign Out
252
+ </Button>
253
+ </>
254
+ }
255
+ >
256
+ <Input
257
+ label="Verification Code"
258
+ labelClassName="text-base"
259
+ className="h-14"
260
+ value={code}
261
+ onChangeText={(text) => {
262
+ setCode(text);
263
+ if (error) {
264
+ setError("");
265
+ }
266
+ }}
267
+ error={error}
268
+ placeholder="Enter the 6-digit code"
269
+ autoCapitalize="none"
270
+ keyboardType="number-pad"
271
+ autoFocus
272
+ returnKeyType="done"
273
+ onSubmitEditing={handleVerify}
274
+ accessibilityLabel="Verification code"
275
+ testID="verify-code-input"
276
+ />
302
277
 
303
- <Button
304
- variant="ghost"
305
- className="w-full"
306
- telemetryId="auth.verify_email.logout_2"
307
- onPress={() => logout()}
308
- accessibilityLabel="Sign out"
309
- accessibilityRole="button"
310
- >
311
- Sign Out
312
- </Button>
313
- </CardFooter>
314
- </Card>
315
- </ScrollView>
316
- </SafeAreaView>
278
+ <MutedText className="text-center text-base">
279
+ Didn&apos;t receive the code?{" "}
280
+ {resendCooldown > 0 ? (
281
+ <Text className="text-base text-foreground">Resend ({resendCooldown})</Text>
282
+ ) : (
283
+ <Text
284
+ className="text-base text-foreground underline"
285
+ onPress={withClickTracking("auth.verify_email.resend", handleResend)}
286
+ accessibilityRole="button"
287
+ accessibilityLabel="Resend verification code"
288
+ >
289
+ Resend
290
+ </Text>
291
+ )}
292
+ </MutedText>
293
+ </AuthPage>
317
294
  );
318
295
  }
319
296
 
@@ -1,3 +1,4 @@
1
+ export { AuthPage, type AuthPageProps, type AuthPageLayout } from "./AuthPage";
1
2
  export { WelcomeScreen, type WelcomeScreenProps } from "./WelcomeScreen";
2
3
  export { ForgotPasswordScreen } from "./ForgotPasswordScreen";
3
4
  export { MfaChallengeScreen } from "./MfaChallengeScreen";
@@ -6,7 +7,7 @@ export {
6
7
  type TermsOfServiceScreenProps,
7
8
  type LegalSection,
8
9
  } from "./TermsOfServiceScreen";
9
- export { VerifyEmailScreen } from "./VerifyEmailScreen";
10
+ export { VerifyEmailScreen, type VerifyEmailScreenProps } from "./VerifyEmailScreen";
10
11
  export { PrivacyPolicyScreen, type PrivacyPolicyScreenProps } from "./PrivacyPolicyScreen";
11
12
  export { ResetPasswordScreen, type ResetPasswordScreenProps } from "./ResetPasswordScreen";
12
13
  export { LoginScreen, type LoginScreenProps } from "./LoginScreen";
@@ -15,22 +16,12 @@ export { MfaEnrollScreen, type MfaEnrollScreenProps } from "./MfaEnrollScreen";
15
16
  export { RegisterScreen, type RegisterScreenProps } from "./RegisterScreen";
16
17
  export { ServerConfigScreen, type ServerConfigScreenProps } from "./ServerConfigScreen";
17
18
  export { ImportDataScreen, type ImportDataScreenProps } from "./ImportDataScreen";
18
- export {
19
- ConsentScreen,
20
- type ConsentScreenProps,
21
- type ConsentScope,
22
- } from "./ConsentScreen";
19
+ export { ConsentScreen, type ConsentScreenProps, type ConsentScope } from "./ConsentScreen";
23
20
  export {
24
21
  AccountPickerScreen,
25
22
  type AccountPickerScreenProps,
26
23
  type PickableAccount,
27
24
  } from "./AccountPickerScreen";
28
- export {
29
- PasskeyEnrollScreen,
30
- type PasskeyEnrollScreenProps,
31
- } from "./PasskeyEnrollScreen";
25
+ export { PasskeyEnrollScreen, type PasskeyEnrollScreenProps } from "./PasskeyEnrollScreen";
32
26
  export { ErrorScreen, type ErrorScreenProps } from "./ErrorScreen";
33
- export {
34
- ExpiredLinkScreen,
35
- type ExpiredLinkScreenProps,
36
- } from "./ExpiredLinkScreen";
27
+ export { ExpiredLinkScreen, type ExpiredLinkScreenProps } from "./ExpiredLinkScreen";