@supatype/react-native-auth 0.1.6

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.
Files changed (56) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +14 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/README.md +66 -0
  5. package/dist/AuthGate.d.ts +15 -0
  6. package/dist/AuthGate.d.ts.map +1 -0
  7. package/dist/AuthGate.js +19 -0
  8. package/dist/AuthGate.js.map +1 -0
  9. package/dist/LoginForm.d.ts +19 -0
  10. package/dist/LoginForm.d.ts.map +1 -0
  11. package/dist/LoginForm.js +45 -0
  12. package/dist/LoginForm.js.map +1 -0
  13. package/dist/MagicLinkForm.d.ts +20 -0
  14. package/dist/MagicLinkForm.d.ts.map +1 -0
  15. package/dist/MagicLinkForm.js +50 -0
  16. package/dist/MagicLinkForm.js.map +1 -0
  17. package/dist/OAuthButton.d.ts +27 -0
  18. package/dist/OAuthButton.d.ts.map +1 -0
  19. package/dist/OAuthButton.js +54 -0
  20. package/dist/OAuthButton.js.map +1 -0
  21. package/dist/ResetPassword.d.ts +28 -0
  22. package/dist/ResetPassword.d.ts.map +1 -0
  23. package/dist/ResetPassword.js +73 -0
  24. package/dist/ResetPassword.js.map +1 -0
  25. package/dist/SignUpForm.d.ts +20 -0
  26. package/dist/SignUpForm.d.ts.map +1 -0
  27. package/dist/SignUpForm.js +55 -0
  28. package/dist/SignUpForm.js.map +1 -0
  29. package/dist/index.d.ts +15 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +8 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/styles.d.ts +76 -0
  34. package/dist/styles.d.ts.map +1 -0
  35. package/dist/styles.js +77 -0
  36. package/dist/styles.js.map +1 -0
  37. package/dist/theme.d.ts +22 -0
  38. package/dist/theme.d.ts.map +1 -0
  39. package/dist/theme.js +23 -0
  40. package/dist/theme.js.map +1 -0
  41. package/package.json +53 -0
  42. package/src/AuthGate.tsx +43 -0
  43. package/src/LoginForm.tsx +142 -0
  44. package/src/MagicLinkForm.tsx +136 -0
  45. package/src/OAuthButton.tsx +99 -0
  46. package/src/ResetPassword.tsx +191 -0
  47. package/src/SignUpForm.tsx +160 -0
  48. package/src/index.ts +24 -0
  49. package/src/react-native.d.ts +103 -0
  50. package/src/styles.ts +78 -0
  51. package/src/theme.tsx +45 -0
  52. package/tests/auth-ui.test.tsx +186 -0
  53. package/tests/mocks/react-native.ts +23 -0
  54. package/tsconfig.json +17 -0
  55. package/tsconfig.tsbuildinfo +1 -0
  56. package/vitest.config.ts +16 -0
@@ -0,0 +1,15 @@
1
+ export { LoginForm } from "./LoginForm.js";
2
+ export type { LoginFormProps, LoginFormLabels } from "./LoginForm.js";
3
+ export { SignUpForm } from "./SignUpForm.js";
4
+ export type { SignUpFormProps, SignUpFormLabels } from "./SignUpForm.js";
5
+ export { MagicLinkForm } from "./MagicLinkForm.js";
6
+ export type { MagicLinkFormProps, MagicLinkFormLabels } from "./MagicLinkForm.js";
7
+ export { OAuthButton } from "./OAuthButton.js";
8
+ export type { OAuthButtonProps } from "./OAuthButton.js";
9
+ export { AuthGate } from "./AuthGate.js";
10
+ export type { AuthGateProps } from "./AuthGate.js";
11
+ export { ResetPassword } from "./ResetPassword.js";
12
+ export type { ResetPasswordProps, ResetPasswordLabels } from "./ResetPassword.js";
13
+ export { AuthThemeProvider, useAuthTheme, defaultAuthTheme, } from "./theme.js";
14
+ export type { AuthThemeTokens, AuthThemeProviderProps } from "./theme.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAExE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAEjF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAEjF,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { LoginForm } from "./LoginForm.js";
2
+ export { SignUpForm } from "./SignUpForm.js";
3
+ export { MagicLinkForm } from "./MagicLinkForm.js";
4
+ export { OAuthButton } from "./OAuthButton.js";
5
+ export { AuthGate } from "./AuthGate.js";
6
+ export { ResetPassword } from "./ResetPassword.js";
7
+ export { AuthThemeProvider, useAuthTheme, defaultAuthTheme, } from "./theme.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAG1C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAG5C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAG9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGxC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,YAAY,CAAA"}
@@ -0,0 +1,76 @@
1
+ import type { AuthThemeTokens } from "./theme.js";
2
+ export declare function fieldStyles(theme: AuthThemeTokens): {
3
+ root: {
4
+ backgroundColor: string;
5
+ padding: number;
6
+ gap: number;
7
+ };
8
+ title: {
9
+ color: string;
10
+ fontSize: number;
11
+ fontWeight: "600";
12
+ marginBottom: number;
13
+ };
14
+ label: {
15
+ color: string;
16
+ fontSize: number;
17
+ marginBottom: number;
18
+ fontWeight: "500";
19
+ };
20
+ input: {
21
+ borderWidth: number;
22
+ borderColor: string;
23
+ borderRadius: number;
24
+ paddingHorizontal: number;
25
+ paddingVertical: number;
26
+ fontSize: number;
27
+ color: string;
28
+ backgroundColor: string;
29
+ minHeight: number;
30
+ };
31
+ error: {
32
+ color: string;
33
+ fontSize: number;
34
+ marginBottom: number;
35
+ };
36
+ muted: {
37
+ color: string;
38
+ fontSize: number;
39
+ };
40
+ button: {
41
+ backgroundColor: string;
42
+ borderRadius: number;
43
+ paddingVertical: number;
44
+ paddingHorizontal: number;
45
+ alignItems: "center";
46
+ justifyContent: "center";
47
+ minHeight: number;
48
+ };
49
+ buttonDisabled: {
50
+ opacity: number;
51
+ };
52
+ buttonText: {
53
+ color: string;
54
+ fontSize: number;
55
+ fontWeight: "600";
56
+ };
57
+ secondaryButton: {
58
+ backgroundColor: string;
59
+ borderWidth: number;
60
+ borderColor: string;
61
+ borderRadius: number;
62
+ paddingVertical: number;
63
+ paddingHorizontal: number;
64
+ alignItems: "center";
65
+ justifyContent: "center";
66
+ minHeight: number;
67
+ flexDirection: "row";
68
+ gap: number;
69
+ };
70
+ secondaryButtonText: {
71
+ color: string;
72
+ fontSize: number;
73
+ fontWeight: "500";
74
+ };
75
+ };
76
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2EjD"}
package/dist/styles.js ADDED
@@ -0,0 +1,77 @@
1
+ export function fieldStyles(theme) {
2
+ return {
3
+ root: {
4
+ backgroundColor: theme.colorBackground,
5
+ padding: theme.spacing,
6
+ gap: theme.spacing,
7
+ },
8
+ title: {
9
+ color: theme.colorText,
10
+ fontSize: theme.fontSizeTitle,
11
+ fontWeight: "600",
12
+ marginBottom: theme.spacing / 2,
13
+ },
14
+ label: {
15
+ color: theme.colorText,
16
+ fontSize: theme.fontSize,
17
+ marginBottom: 6,
18
+ fontWeight: "500",
19
+ },
20
+ input: {
21
+ borderWidth: 1,
22
+ borderColor: theme.colorBorder,
23
+ borderRadius: theme.borderRadius,
24
+ paddingHorizontal: 12,
25
+ paddingVertical: 10,
26
+ fontSize: theme.fontSize,
27
+ color: theme.colorText,
28
+ backgroundColor: theme.colorSurface,
29
+ minHeight: 44,
30
+ },
31
+ error: {
32
+ color: theme.colorDanger,
33
+ fontSize: theme.fontSize,
34
+ marginBottom: 4,
35
+ },
36
+ muted: {
37
+ color: theme.colorTextMuted,
38
+ fontSize: theme.fontSize,
39
+ },
40
+ button: {
41
+ backgroundColor: theme.colorPrimary,
42
+ borderRadius: theme.borderRadius,
43
+ paddingVertical: 12,
44
+ paddingHorizontal: 16,
45
+ alignItems: "center",
46
+ justifyContent: "center",
47
+ minHeight: 44,
48
+ },
49
+ buttonDisabled: {
50
+ opacity: 0.6,
51
+ },
52
+ buttonText: {
53
+ color: "#ffffff",
54
+ fontSize: theme.fontSize,
55
+ fontWeight: "600",
56
+ },
57
+ secondaryButton: {
58
+ backgroundColor: theme.colorSurface,
59
+ borderWidth: 1,
60
+ borderColor: theme.colorBorder,
61
+ borderRadius: theme.borderRadius,
62
+ paddingVertical: 12,
63
+ paddingHorizontal: 16,
64
+ alignItems: "center",
65
+ justifyContent: "center",
66
+ minHeight: 44,
67
+ flexDirection: "row",
68
+ gap: 8,
69
+ },
70
+ secondaryButtonText: {
71
+ color: theme.colorText,
72
+ fontSize: theme.fontSize,
73
+ fontWeight: "500",
74
+ },
75
+ };
76
+ }
77
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,WAAW,CAAC,KAAsB;IAChD,OAAO;QACL,IAAI,EAAE;YACJ,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,GAAG,EAAE,KAAK,CAAC,OAAO;SACnB;QACD,KAAK,EAAE;YACL,KAAK,EAAE,KAAK,CAAC,SAAS;YACtB,QAAQ,EAAE,KAAK,CAAC,aAAa;YAC7B,UAAU,EAAE,KAAc;YAC1B,YAAY,EAAE,KAAK,CAAC,OAAO,GAAG,CAAC;SAChC;QACD,KAAK,EAAE;YACL,KAAK,EAAE,KAAK,CAAC,SAAS;YACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,YAAY,EAAE,CAAC;YACf,UAAU,EAAE,KAAc;SAC3B;QACD,KAAK,EAAE;YACL,WAAW,EAAE,CAAC;YACd,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,iBAAiB,EAAE,EAAE;YACrB,eAAe,EAAE,EAAE;YACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,KAAK,EAAE,KAAK,CAAC,SAAS;YACtB,eAAe,EAAE,KAAK,CAAC,YAAY;YACnC,SAAS,EAAE,EAAE;SACd;QACD,KAAK,EAAE;YACL,KAAK,EAAE,KAAK,CAAC,WAAW;YACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,YAAY,EAAE,CAAC;SAChB;QACD,KAAK,EAAE;YACL,KAAK,EAAE,KAAK,CAAC,cAAc;YAC3B,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB;QACD,MAAM,EAAE;YACN,eAAe,EAAE,KAAK,CAAC,YAAY;YACnC,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,eAAe,EAAE,EAAE;YACnB,iBAAiB,EAAE,EAAE;YACrB,UAAU,EAAE,QAAiB;YAC7B,cAAc,EAAE,QAAiB;YACjC,SAAS,EAAE,EAAE;SACd;QACD,cAAc,EAAE;YACd,OAAO,EAAE,GAAG;SACb;QACD,UAAU,EAAE;YACV,KAAK,EAAE,SAAS;YAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,UAAU,EAAE,KAAc;SAC3B;QACD,eAAe,EAAE;YACf,eAAe,EAAE,KAAK,CAAC,YAAY;YACnC,WAAW,EAAE,CAAC;YACd,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,eAAe,EAAE,EAAE;YACnB,iBAAiB,EAAE,EAAE;YACrB,UAAU,EAAE,QAAiB;YAC7B,cAAc,EAAE,QAAiB;YACjC,SAAS,EAAE,EAAE;YACb,aAAa,EAAE,KAAc;YAC7B,GAAG,EAAE,CAAC;SACP;QACD,mBAAmB,EAAE;YACnB,KAAK,EAAE,KAAK,CAAC,SAAS;YACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,UAAU,EAAE,KAAc;SAC3B;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ export interface AuthThemeTokens {
3
+ colorPrimary: string;
4
+ colorDanger: string;
5
+ colorText: string;
6
+ colorTextMuted: string;
7
+ colorBackground: string;
8
+ colorBorder: string;
9
+ colorSurface: string;
10
+ borderRadius: number;
11
+ spacing: number;
12
+ fontSize: number;
13
+ fontSizeTitle: number;
14
+ }
15
+ export declare const defaultAuthTheme: AuthThemeTokens;
16
+ export interface AuthThemeProviderProps {
17
+ tokens?: Partial<AuthThemeTokens> | undefined;
18
+ children: React.ReactNode;
19
+ }
20
+ export declare function AuthThemeProvider({ tokens, children }: AuthThemeProviderProps): React.ReactElement;
21
+ export declare function useAuthTheme(): AuthThemeTokens;
22
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAA;AAExD,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,eAAe,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,eAY9B,CAAA;AAID,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS,CAAA;IAC7C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,sBAAsB,GAAG,KAAK,CAAC,YAAY,CAGlG;AAED,wBAAgB,YAAY,IAAI,eAAe,CAE9C"}
package/dist/theme.js ADDED
@@ -0,0 +1,23 @@
1
+ import React, { createContext, useContext } from "react";
2
+ export const defaultAuthTheme = {
3
+ colorPrimary: "#0f766e",
4
+ colorDanger: "#b91c1c",
5
+ colorText: "#111827",
6
+ colorTextMuted: "#6b7280",
7
+ colorBackground: "#ffffff",
8
+ colorBorder: "#d1d5db",
9
+ colorSurface: "#f9fafb",
10
+ borderRadius: 8,
11
+ spacing: 16,
12
+ fontSize: 14,
13
+ fontSizeTitle: 20,
14
+ };
15
+ const AuthThemeContext = createContext(defaultAuthTheme);
16
+ export function AuthThemeProvider({ tokens, children }) {
17
+ const value = { ...defaultAuthTheme, ...tokens };
18
+ return React.createElement(AuthThemeContext.Provider, { value }, children);
19
+ }
20
+ export function useAuthTheme() {
21
+ return useContext(AuthThemeContext);
22
+ }
23
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAgBxD,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C,YAAY,EAAE,SAAS;IACvB,WAAW,EAAE,SAAS;IACtB,SAAS,EAAE,SAAS;IACpB,cAAc,EAAE,SAAS;IACzB,eAAe,EAAE,SAAS;IAC1B,WAAW,EAAE,SAAS;IACtB,YAAY,EAAE,SAAS;IACvB,YAAY,EAAE,CAAC;IACf,OAAO,EAAE,EAAE;IACX,QAAQ,EAAE,EAAE;IACZ,aAAa,EAAE,EAAE;CAClB,CAAA;AAED,MAAM,gBAAgB,GAAG,aAAa,CAAkB,gBAAgB,CAAC,CAAA;AAOzE,MAAM,UAAU,iBAAiB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAA0B;IAC5E,MAAM,KAAK,GAAoB,EAAE,GAAG,gBAAgB,EAAE,GAAG,MAAM,EAAE,CAAA;IACjE,OAAO,KAAK,CAAC,aAAa,CAAC,gBAAgB,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,QAAQ,CAAC,CAAA;AAC5E,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,UAAU,CAAC,gBAAgB,CAAC,CAAA;AACrC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@supatype/react-native-auth",
3
+ "version": "0.1.6",
4
+ "description": "Pre-built React Native authentication UI for Supatype",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@supatype/client": "0.1.6",
16
+ "@supatype/react-native": "0.1.6",
17
+ "@supatype/react": "0.1.6"
18
+ },
19
+ "peerDependencies": {
20
+ "react": ">=18",
21
+ "react-native": ">=0.73",
22
+ "expo-web-browser": ">=13"
23
+ },
24
+ "peerDependenciesMeta": {
25
+ "react-native": {
26
+ "optional": true
27
+ },
28
+ "expo-web-browser": {
29
+ "optional": true
30
+ }
31
+ },
32
+ "devDependencies": {
33
+ "@types/react": "^18",
34
+ "@types/react-test-renderer": "^18",
35
+ "react": "^18",
36
+ "react-test-renderer": "^18",
37
+ "typescript": "^5",
38
+ "vitest": "^4.0.18"
39
+ },
40
+ "keywords": [
41
+ "supatype",
42
+ "react-native",
43
+ "auth",
44
+ "expo"
45
+ ],
46
+ "scripts": {
47
+ "build": "tsc --project tsconfig.json",
48
+ "dev": "tsc --project tsconfig.json --watch",
49
+ "typecheck": "tsc --project tsconfig.json --noEmit",
50
+ "test": "vitest run",
51
+ "clean": "rm -rf dist *.tsbuildinfo"
52
+ }
53
+ }
@@ -0,0 +1,43 @@
1
+ import React from "react"
2
+ import { ActivityIndicator, View, type StyleProp, type ViewStyle } from "react-native"
3
+ import { useAuth } from "@supatype/react"
4
+ import { useAuthTheme } from "./theme.js"
5
+
6
+ export interface AuthGateProps {
7
+ children: React.ReactNode
8
+ /** Rendered when signed out. Default: null. */
9
+ fallback?: React.ReactNode | undefined
10
+ /** Rendered while the initial session is loading. Default: ActivityIndicator. */
11
+ loadingFallback?: React.ReactNode | undefined
12
+ style?: StyleProp<ViewStyle> | undefined
13
+ }
14
+
15
+ /**
16
+ * Renders `children` only when a session exists. Must be inside `<SupatypeProvider>`.
17
+ */
18
+ export function AuthGate({
19
+ children,
20
+ fallback = null,
21
+ loadingFallback,
22
+ style,
23
+ }: AuthGateProps): React.ReactElement {
24
+ const { session, loading } = useAuth()
25
+ const theme = useAuthTheme()
26
+
27
+ if (loading) {
28
+ return (
29
+ <View
30
+ style={[{ flex: 1, alignItems: "center", justifyContent: "center" }, style]}
31
+ testID="st-auth-gate-loading"
32
+ >
33
+ {loadingFallback ?? <ActivityIndicator color={theme.colorPrimary} testID="st-auth-gate-spinner" />}
34
+ </View>
35
+ )
36
+ }
37
+
38
+ if (session === null) {
39
+ return <>{fallback}</>
40
+ }
41
+
42
+ return <>{children}</>
43
+ }
@@ -0,0 +1,142 @@
1
+ import React, { useState } from "react"
2
+ import {
3
+ KeyboardAvoidingView,
4
+ Platform,
5
+ Pressable,
6
+ ScrollView,
7
+ Text,
8
+ TextInput,
9
+ View,
10
+ type StyleProp,
11
+ type ViewStyle,
12
+ } from "react-native"
13
+ import type { Session, SupatypeError } from "@supatype/client"
14
+ import { useAuth } from "@supatype/react"
15
+ import { useAuthTheme } from "./theme.js"
16
+ import { fieldStyles } from "./styles.js"
17
+
18
+ export interface LoginFormLabels {
19
+ title?: string | undefined
20
+ email?: string | undefined
21
+ password?: string | undefined
22
+ submit?: string | undefined
23
+ errorPrefix?: string | undefined
24
+ }
25
+
26
+ export interface LoginFormProps {
27
+ onSuccess?: ((session: Session) => void) | undefined
28
+ onError?: ((error: SupatypeError) => void) | undefined
29
+ labels?: LoginFormLabels | undefined
30
+ style?: StyleProp<ViewStyle> | undefined
31
+ contentContainerStyle?: StyleProp<ViewStyle> | undefined
32
+ }
33
+
34
+ const DEFAULT_LABELS: Required<LoginFormLabels> = {
35
+ title: "Sign in",
36
+ email: "Email address",
37
+ password: "Password",
38
+ submit: "Sign in",
39
+ errorPrefix: "",
40
+ }
41
+
42
+ export function LoginForm({
43
+ onSuccess,
44
+ onError,
45
+ labels,
46
+ style,
47
+ contentContainerStyle,
48
+ }: LoginFormProps): React.ReactElement {
49
+ const { signIn } = useAuth()
50
+ const theme = useAuthTheme()
51
+ const styles = fieldStyles(theme)
52
+ const l = { ...DEFAULT_LABELS, ...labels }
53
+
54
+ const [email, setEmail] = useState("")
55
+ const [password, setPassword] = useState("")
56
+ const [loading, setLoading] = useState(false)
57
+ const [errorMessage, setErrorMessage] = useState<string | null>(null)
58
+
59
+ async function handleSubmit(): Promise<void> {
60
+ setErrorMessage(null)
61
+ setLoading(true)
62
+ try {
63
+ const { data, error } = await signIn({ email, password })
64
+ if (error !== null) {
65
+ const msg = `${l.errorPrefix}${error.message}`
66
+ setErrorMessage(msg)
67
+ onError?.(error)
68
+ } else if (data.session !== null) {
69
+ onSuccess?.(data.session)
70
+ }
71
+ } finally {
72
+ setLoading(false)
73
+ }
74
+ }
75
+
76
+ return (
77
+ <KeyboardAvoidingView
78
+ style={[{ flex: 1 }, style]}
79
+ behavior={Platform.OS === "ios" ? "padding" : undefined}
80
+ >
81
+ <ScrollView
82
+ keyboardShouldPersistTaps="handled"
83
+ contentContainerStyle={[styles.root, contentContainerStyle]}
84
+ >
85
+ <Text style={styles.title}>{l.title}</Text>
86
+
87
+ {errorMessage !== null && (
88
+ <Text style={styles.error} accessibilityRole="alert" accessibilityLiveRegion="polite">
89
+ {errorMessage}
90
+ </Text>
91
+ )}
92
+
93
+ <View>
94
+ <Text style={styles.label}>{l.email}</Text>
95
+ <TextInput
96
+ testID="st-login-email"
97
+ style={styles.input}
98
+ value={email}
99
+ onChangeText={setEmail}
100
+ autoCapitalize="none"
101
+ autoComplete="email"
102
+ keyboardType="email-address"
103
+ textContentType="emailAddress"
104
+ editable={!loading}
105
+ accessibilityLabel={l.email}
106
+ placeholderTextColor={theme.colorTextMuted}
107
+ />
108
+ </View>
109
+
110
+ <View>
111
+ <Text style={styles.label}>{l.password}</Text>
112
+ <TextInput
113
+ testID="st-login-password"
114
+ style={styles.input}
115
+ value={password}
116
+ onChangeText={setPassword}
117
+ secureTextEntry
118
+ autoComplete="password"
119
+ textContentType="password"
120
+ editable={!loading}
121
+ accessibilityLabel={l.password}
122
+ placeholderTextColor={theme.colorTextMuted}
123
+ />
124
+ </View>
125
+
126
+ <Pressable
127
+ testID="st-login-submit"
128
+ accessibilityRole="button"
129
+ accessibilityLabel={l.submit}
130
+ accessibilityState={{ disabled: loading }}
131
+ disabled={loading}
132
+ onPress={() => {
133
+ void handleSubmit()
134
+ }}
135
+ style={[styles.button, loading ? styles.buttonDisabled : null]}
136
+ >
137
+ <Text style={styles.buttonText}>{loading ? "Signing in…" : l.submit}</Text>
138
+ </Pressable>
139
+ </ScrollView>
140
+ </KeyboardAvoidingView>
141
+ )
142
+ }
@@ -0,0 +1,136 @@
1
+ import React, { useState } from "react"
2
+ import {
3
+ KeyboardAvoidingView,
4
+ Platform,
5
+ Pressable,
6
+ ScrollView,
7
+ Text,
8
+ TextInput,
9
+ View,
10
+ type StyleProp,
11
+ type ViewStyle,
12
+ } from "react-native"
13
+ import type { SupatypeError } from "@supatype/client"
14
+ import { useAuth } from "@supatype/react"
15
+ import { useAuthTheme } from "./theme.js"
16
+ import { fieldStyles } from "./styles.js"
17
+
18
+ export interface MagicLinkFormLabels {
19
+ title?: string | undefined
20
+ email?: string | undefined
21
+ submit?: string | undefined
22
+ successMessage?: string | undefined
23
+ }
24
+
25
+ export interface MagicLinkFormProps {
26
+ /** Deep link / redirect used as `emailRedirectTo` for the magic link. */
27
+ redirectTo?: string | undefined
28
+ onSent?: (() => void) | undefined
29
+ onError?: ((error: SupatypeError) => void) | undefined
30
+ labels?: MagicLinkFormLabels | undefined
31
+ style?: StyleProp<ViewStyle> | undefined
32
+ contentContainerStyle?: StyleProp<ViewStyle> | undefined
33
+ }
34
+
35
+ const DEFAULT_LABELS: Required<MagicLinkFormLabels> = {
36
+ title: "Sign in with email",
37
+ email: "Email address",
38
+ submit: "Send magic link",
39
+ successMessage: "Check your email for a sign-in link.",
40
+ }
41
+
42
+ export function MagicLinkForm({
43
+ redirectTo,
44
+ onSent,
45
+ onError,
46
+ labels,
47
+ style,
48
+ contentContainerStyle,
49
+ }: MagicLinkFormProps): React.ReactElement {
50
+ const { signInWithOtp } = useAuth()
51
+ const theme = useAuthTheme()
52
+ const styles = fieldStyles(theme)
53
+ const l = { ...DEFAULT_LABELS, ...labels }
54
+
55
+ const [email, setEmail] = useState("")
56
+ const [loading, setLoading] = useState(false)
57
+ const [errorMessage, setErrorMessage] = useState<string | null>(null)
58
+ const [sent, setSent] = useState(false)
59
+
60
+ async function handleSubmit(): Promise<void> {
61
+ setErrorMessage(null)
62
+ setLoading(true)
63
+ try {
64
+ const { error } = await signInWithOtp({
65
+ email,
66
+ ...(redirectTo !== undefined && { options: { emailRedirectTo: redirectTo } }),
67
+ })
68
+ if (error !== null) {
69
+ setErrorMessage(error.message)
70
+ onError?.(error)
71
+ } else {
72
+ setSent(true)
73
+ onSent?.()
74
+ }
75
+ } finally {
76
+ setLoading(false)
77
+ }
78
+ }
79
+
80
+ if (sent) {
81
+ return (
82
+ <View style={[styles.root, style]} testID="st-magic-link-sent">
83
+ <Text style={styles.muted} accessibilityLiveRegion="polite">
84
+ {l.successMessage}
85
+ </Text>
86
+ </View>
87
+ )
88
+ }
89
+
90
+ return (
91
+ <KeyboardAvoidingView
92
+ style={[{ flex: 1 }, style]}
93
+ behavior={Platform.OS === "ios" ? "padding" : undefined}
94
+ >
95
+ <ScrollView
96
+ keyboardShouldPersistTaps="handled"
97
+ contentContainerStyle={[styles.root, contentContainerStyle]}
98
+ >
99
+ <Text style={styles.title}>{l.title}</Text>
100
+
101
+ {errorMessage !== null && (
102
+ <Text style={styles.error} accessibilityRole="alert">
103
+ {errorMessage}
104
+ </Text>
105
+ )}
106
+
107
+ <View>
108
+ <Text style={styles.label}>{l.email}</Text>
109
+ <TextInput
110
+ testID="st-magic-link-email"
111
+ style={styles.input}
112
+ value={email}
113
+ onChangeText={setEmail}
114
+ autoCapitalize="none"
115
+ keyboardType="email-address"
116
+ editable={!loading}
117
+ accessibilityLabel={l.email}
118
+ placeholderTextColor={theme.colorTextMuted}
119
+ />
120
+ </View>
121
+
122
+ <Pressable
123
+ testID="st-magic-link-submit"
124
+ accessibilityRole="button"
125
+ disabled={loading}
126
+ onPress={() => {
127
+ void handleSubmit()
128
+ }}
129
+ style={[styles.button, loading ? styles.buttonDisabled : null]}
130
+ >
131
+ <Text style={styles.buttonText}>{loading ? "Sending…" : l.submit}</Text>
132
+ </Pressable>
133
+ </ScrollView>
134
+ </KeyboardAvoidingView>
135
+ )
136
+ }