@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.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +14 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/README.md +66 -0
- package/dist/AuthGate.d.ts +15 -0
- package/dist/AuthGate.d.ts.map +1 -0
- package/dist/AuthGate.js +19 -0
- package/dist/AuthGate.js.map +1 -0
- package/dist/LoginForm.d.ts +19 -0
- package/dist/LoginForm.d.ts.map +1 -0
- package/dist/LoginForm.js +45 -0
- package/dist/LoginForm.js.map +1 -0
- package/dist/MagicLinkForm.d.ts +20 -0
- package/dist/MagicLinkForm.d.ts.map +1 -0
- package/dist/MagicLinkForm.js +50 -0
- package/dist/MagicLinkForm.js.map +1 -0
- package/dist/OAuthButton.d.ts +27 -0
- package/dist/OAuthButton.d.ts.map +1 -0
- package/dist/OAuthButton.js +54 -0
- package/dist/OAuthButton.js.map +1 -0
- package/dist/ResetPassword.d.ts +28 -0
- package/dist/ResetPassword.d.ts.map +1 -0
- package/dist/ResetPassword.js +73 -0
- package/dist/ResetPassword.js.map +1 -0
- package/dist/SignUpForm.d.ts +20 -0
- package/dist/SignUpForm.d.ts.map +1 -0
- package/dist/SignUpForm.js +55 -0
- package/dist/SignUpForm.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.d.ts +76 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +77 -0
- package/dist/styles.js.map +1 -0
- package/dist/theme.d.ts +22 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +23 -0
- package/dist/theme.js.map +1 -0
- package/package.json +53 -0
- package/src/AuthGate.tsx +43 -0
- package/src/LoginForm.tsx +142 -0
- package/src/MagicLinkForm.tsx +136 -0
- package/src/OAuthButton.tsx +99 -0
- package/src/ResetPassword.tsx +191 -0
- package/src/SignUpForm.tsx +160 -0
- package/src/index.ts +24 -0
- package/src/react-native.d.ts +103 -0
- package/src/styles.ts +78 -0
- package/src/theme.tsx +45 -0
- package/tests/auth-ui.test.tsx +186 -0
- package/tests/mocks/react-native.ts +23 -0
- package/tsconfig.json +17 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +16 -0
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/styles.d.ts
ADDED
|
@@ -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"}
|
package/dist/theme.d.ts
ADDED
|
@@ -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
|
+
}
|
package/src/AuthGate.tsx
ADDED
|
@@ -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
|
+
}
|