From da0298ae3eab5b076942bd00ad8cdf9aa32f9f32 Mon Sep 17 00:00:00 2001 From: Praveen Yadav Date: Mon, 17 Jul 2023 23:10:05 +0530 Subject: [PATCH] chore: implement auth flow --- sdks/packages/shield-js/src/types.ts | 6 +- sdks/packages/shield-react/package.json | 7 +- .../shield-react/src/components/Container.tsx | 77 ++++++++++ .../shield-react/src/components/Header.tsx | 51 +++++++ .../shield-react/src/components/login.tsx | 140 +++++------------- .../src/components/magiclink-verify.tsx | 129 ++++++++++++++++ .../shield-react/src/components/magiclink.tsx | 112 +++++--------- .../shield-react/src/components/oidc.tsx | 15 +- .../src/components/organization.tsx | 123 +++++++++++++++ .../shield-react/src/components/signup.tsx | 67 +++++++++ .../src/contexts/ShieldContext.tsx | 22 ++- .../src/contexts/ShieldProvider.tsx | 8 +- .../contexts/useMaxAllowedInstancesGuard.tsx | 1 + sdks/packages/shield-react/src/index.ts | 3 + sdks/packages/shield-react/src/shield.ts | 6 + sdks/packages/shield-react/tsup.config.ts | 3 + sdks/pnpm-lock.yaml | 108 ++++++++++---- 17 files changed, 662 insertions(+), 216 deletions(-) create mode 100644 sdks/packages/shield-react/src/components/Container.tsx create mode 100644 sdks/packages/shield-react/src/components/Header.tsx create mode 100644 sdks/packages/shield-react/src/components/magiclink-verify.tsx create mode 100644 sdks/packages/shield-react/src/components/organization.tsx create mode 100644 sdks/packages/shield-react/src/components/signup.tsx diff --git a/sdks/packages/shield-js/src/types.ts b/sdks/packages/shield-js/src/types.ts index 943eed50f..49a6535d9 100644 --- a/sdks/packages/shield-js/src/types.ts +++ b/sdks/packages/shield-js/src/types.ts @@ -49,7 +49,9 @@ export interface Role { } export interface ShieldClientOptions { - endpoint: string; + endpoint?: string; + redirectSignup?: string; + redirectLogin?: string; } export interface InitialState { @@ -57,7 +59,7 @@ export interface InitialState { } export interface ShieldProviderProps { - endpoint: string; + config: ShieldClientOptions; children: React.ReactNode; initialState?: InitialState; } diff --git a/sdks/packages/shield-react/package.json b/sdks/packages/shield-react/package.json index 55c30f5f6..a5b06b755 100644 --- a/sdks/packages/shield-react/package.json +++ b/sdks/packages/shield-react/package.json @@ -1,6 +1,6 @@ { "name": "@raystack/shield-react", - "version": "0.0.1", + "version": "0.0.4", "description": "A react library for shield", "type": "module", "main": "dist/index.cjs", @@ -38,6 +38,7 @@ } ], "devDependencies": { + "@hookform/resolvers": "^3.1.1", "@raystack/shield": "workspace:^", "@size-limit/preset-small-lib": "^8.2.6", "@types/react": "^18.2.5", @@ -49,13 +50,15 @@ "prettier": "^2.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.45.2", "size-limit": "^8.2.6", "tsup": "^6.7.0", "typescript": "4.7", + "yup": "^1.2.0", "zustand": "^4.3.9" }, "dependencies": { - "@raystack/apsara": "^0.10.1", + "@raystack/apsara": "^0.10.3", "axios": "^1.4.0" }, "publishConfig": { diff --git a/sdks/packages/shield-react/src/components/Container.tsx b/sdks/packages/shield-react/src/components/Container.tsx new file mode 100644 index 000000000..1fafb9cdf --- /dev/null +++ b/sdks/packages/shield-react/src/components/Container.tsx @@ -0,0 +1,77 @@ +import { Flex } from "@raystack/apsara"; +import React, { ComponentPropsWithRef } from "react"; + +const styles = { + container: { + fontSize: "12px", + width: "100%", + minWidth: "220px", + maxWidth: "480px", + color: "var(--foreground-base)", + + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: "32px", + }, + logoContainer: {}, + titleContainer: { + fontWeight: "400", + }, + fieldset: { + width: "100%", + border: "1px solid transparent", + borderTopColor: "rgb(205, 211, 223)", + gridArea: "1 / 1", + padding: 0, + margin: "2px", + }, + legend: { + fontSize: "8px", + margin: "auto", + padding: "0 4px", + }, +}; + +const shadowOptions = { + none: "none", + xs: "0px 1px 2px 0px rgba(16, 24, 40, 0.06)", + sm: "0px 1px 4px 0px rgba(0, 0, 0, 0.09)", + md: "0px 4px 6px -2px rgba(16, 24, 40, 0.03), 0px 12px 16px -4px rgba(16, 24, 40, 0.08)", + lg: "0px 8px 8px -4px rgba(16, 24, 40, 0.03), 0px 20px 24px -4px rgba(16, 24, 40, 0.08)", +}; + +const borderRadiusOptions = { + none: "0", + xs: "4px", + sm: "8px", + md: "16px", + lg: "24px", +}; + +type ContainerProps = ComponentPropsWithRef<"div"> & { + children?: React.ReactNode; + shadow?: "none" | "xs" | "sm" | "md" | "lg"; + radius?: "none" | "xs" | "sm" | "md" | "lg"; +}; + +export const Container = ({ + children, + shadow = "none", + radius = "md", + style, +}: ContainerProps) => { + return ( + + {children} + + ); +}; diff --git a/sdks/packages/shield-react/src/components/Header.tsx b/sdks/packages/shield-react/src/components/Header.tsx new file mode 100644 index 000000000..5640a0a20 --- /dev/null +++ b/sdks/packages/shield-react/src/components/Header.tsx @@ -0,0 +1,51 @@ +import { Flex, Text } from "@raystack/apsara"; +import React, { ComponentPropsWithRef } from "react"; +import { useShield } from "../contexts/ShieldContext"; + +const styles = { + container: { + fontSize: "12px", + minWidth: "220px", + maxWidth: "100%", + color: "var(--foreground-base)", + + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: "32px", + }, + logoContainer: {}, + titleContainer: { + fontWeight: "400", + }, +}; + +const defaultLogo = ( + +); + +type HeaderProps = ComponentPropsWithRef<"div"> & { + title?: string; + logo?: React.ReactNode; +}; + +export const Header = ({ title, logo }: HeaderProps) => { + const { config } = useShield(); + + return ( + +
{logo ? logo : defaultLogo}
+
+ {title} +
+
+ ); +}; diff --git a/sdks/packages/shield-react/src/components/login.tsx b/sdks/packages/shield-react/src/components/login.tsx index 5ac816dc6..cda937ba5 100644 --- a/sdks/packages/shield-react/src/components/login.tsx +++ b/sdks/packages/shield-react/src/components/login.tsx @@ -1,73 +1,27 @@ -import { Text } from "@raystack/apsara"; +import { Flex, Link, Text } from "@raystack/apsara"; import React, { ComponentPropsWithRef, useCallback } from "react"; import { useShield } from "../contexts/ShieldContext"; +import { Container } from "./Container"; +import { Header } from "./Header"; import { MagicLink } from "./magiclink"; import { OIDCButton } from "./oidc"; const styles = { - container: { - fontSize: "12px", - - width: "28rem", - maxWidth: "100%", - padding: "1.5rem", - - color: "rgb(60, 74, 90)", - backgroundColor: "#FFF", - - display: "flex", - flexDirection: "column", - alignItems: "center", - gap: "8px", - }, - logoContainer: { - marginBottom: "1.5rem", - }, titleContainer: { - fontSize: "14px", - fontWeight: "bold", - marginBottom: "1rem", - }, - fieldset: { - width: "100%", - border: "1px solid transparent", - borderTopColor: "rgb(205, 211, 223)", - gridArea: "1 / 1", - padding: 0, - margin: "2px", + fontWeight: "400", }, - legend: { - fontSize: "8px", - margin: "auto", - padding: "0 4px", - }, -}; - -const shadowOptions = { - none: "none", - xs: "0px 1px 2px 0px rgba(16, 24, 40, 0.06)", - sm: "0px 1px 4px 0px rgba(0, 0, 0, 0.09)", - md: "0px 4px 6px -2px rgba(16, 24, 40, 0.03), 0px 12px 16px -4px rgba(16, 24, 40, 0.08)", - lg: "0px 8px 8px -4px rgba(16, 24, 40, 0.03), 0px 20px 24px -4px rgba(16, 24, 40, 0.08)", }; -const borderRadiusOptions = { - none: "0", - xs: "4px", - sm: "8px", - md: "16px", - lg: "24px", +type SignedInProps = ComponentPropsWithRef & { + logo?: React.ReactNode; + title?: string; }; - -const defaultLogo = ( - -); - -type SignedInProps = ComponentPropsWithRef; -export const SignedIn = (props: SignedInProps) => { +export const SignedIn = ({ + logo, + title = "Login to Raypoint", + ...props +}: SignedInProps) => { + const { config } = useShield(); const { client, strategies = [] } = useShield(); const clickHandler = useCallback( @@ -82,53 +36,31 @@ export const SignedIn = (props: SignedInProps) => { const mailotp = strategies.find((s) => s.name === "mailotp"); const filteredOIDC = strategies.filter((s) => s.name !== "mailotp"); - return ( - {mailotp && } - {mailotp && ( -
- or -
- )} - {filteredOIDC.map((s, index) => { - return ( - clickHandler(s.name)}> - {s.name} - - ); - })} +
+ + {filteredOIDC.map((s, index) => { + return ( + clickHandler(s.name)}> + {s.name} + + ); + })} + + {mailotp && } + +
+ + Don’t have an account?{" "} + + Signup + + +
); }; - -type ContainerProps = ComponentPropsWithRef<"div"> & { - children?: React.ReactNode; - shadow?: "none" | "xs" | "sm" | "md" | "lg"; - radius?: "none" | "xs" | "sm" | "md" | "lg"; - title?: string; - logo?: React.ReactNode; -}; - -export const Container = ({ - children, - shadow = "sm", - radius = "md", - title = "Sign in", - logo, -}: ContainerProps) => ( -
-
{logo ? logo : defaultLogo}
-
- {title} -
- {children} -
-); diff --git a/sdks/packages/shield-react/src/components/magiclink-verify.tsx b/sdks/packages/shield-react/src/components/magiclink-verify.tsx new file mode 100644 index 000000000..1c7b35bef --- /dev/null +++ b/sdks/packages/shield-react/src/components/magiclink-verify.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { Button, Flex, Link, Text, TextField } from "@raystack/apsara"; +import React, { + ComponentPropsWithRef, + useCallback, + useEffect, + useState, +} from "react"; +import { useShield } from "../contexts/ShieldContext"; +import { Container } from "./Container"; +import { Header } from "./Header"; +import { hasWindow } from "./helper"; + +const styles = { + wrapper: { + width: "80%", + }, +}; + +type MagicLinkVerifyProps = ComponentPropsWithRef & { + logo?: React.ReactNode; + title?: string; +}; + +export const MagicLinkVerify = ({ + logo, + title = "Check your email", + ...props +}: MagicLinkVerifyProps) => { + const [loading, setLoading] = useState(false); + const { client, config, strategies = [] } = useShield(); + const [visiable, setVisiable] = useState(false); + const [email, setEmail] = useState(""); + const [emailParam, setEmailParam] = useState(""); + const [stateParam, setStateParam] = useState(""); + const [codeParam, setCodeParam] = useState(""); + const [otp, setOTP] = useState(""); + const [submitError, setSubmitError] = useState(""); + + const handleOTPChange = (event: React.ChangeEvent) => { + setOTP(event.target.value); + }; + + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const emailParam = params.get("email"); + const stateParam = params.get("state"); + const codeParam = params.get("code"); + + emailParam && setEmailParam(emailParam); + stateParam && setStateParam(stateParam); + codeParam && setCodeParam(codeParam); + }, []); + + const OTPVerifyClickHandler = useCallback(async () => { + setLoading(true); + try { + await client.verifyMagicLinkAuthStrategyEndpoint(otp, stateParam!); + + const searchParams = new URLSearchParams( + hasWindow() ? window.location.search : `` + ); + const redirectURL = + searchParams.get("redirect_uri") || searchParams.get("redirectURL"); + + // @ts-ignore + window.location = redirectURL ? redirectURL : window.location.origin; + } catch (error) { + console.log(error); + setSubmitError("Please enter a valid verification code"); + } finally { + setLoading(false); + } + }, [otp]); + + return ( + + +
+ {emailParam && ( + + We have sent a temporary login link. Please check your inbox at + {emailParam} + + )} + + {!visiable ? ( + + ) : ( + + + + + {submitError && String(submitError)} + + + + + )} + + Back to login + + + ); +}; diff --git a/sdks/packages/shield-react/src/components/magiclink.tsx b/sdks/packages/shield-react/src/components/magiclink.tsx index eab5465f5..cbc484659 100644 --- a/sdks/packages/shield-react/src/components/magiclink.tsx +++ b/sdks/packages/shield-react/src/components/magiclink.tsx @@ -1,21 +1,13 @@ -import { Button, TextField } from "@raystack/apsara"; +import { Button, Separator, Text, TextField } from "@raystack/apsara"; import React, { useCallback, useState } from "react"; import { useShield } from "../contexts/ShieldContext"; -import { hasWindow } from "./helper"; const styles = { container: { width: "100%", display: "flex", alignItems: "center", - gap: "8px", - }, - - input: { - width: "100%", - padding: "4px 8px", - fontSize: "10px", - lineHeight: "20px", + gap: "var(--pd-16)", }, button: { @@ -28,10 +20,10 @@ type MagicLinkProps = { children?: React.ReactNode; }; export const MagicLink = ({ children, ...props }: MagicLinkProps) => { - const { client } = useShield(); + const { client, config } = useShield(); + const [visiable, setVisiable] = useState(false); const [loading, setLoading] = useState(false); const [email, setEmail] = useState(""); - const [otp, setOTP] = useState(""); const [state, setState] = useState(""); const magicLinkClickHandler = useCallback(async () => { @@ -40,78 +32,56 @@ export const MagicLink = ({ children, ...props }: MagicLinkProps) => { const { data: { state }, } = await client.getMagicLinkAuthStrategyEndpoint(email); - setState(state); + const searchParams = new URLSearchParams({ state, email }); + // @ts-ignore + window.location = `${ + config.redirectMagicLinkVerify + }?${searchParams.toString()}`; } finally { setLoading(false); } }, [email]); - const OTPVerifyClickHandler = useCallback(async () => { - setLoading(true); - await client.verifyMagicLinkAuthStrategyEndpoint(otp, state); - - const searchParams = new URLSearchParams( - hasWindow() ? window.location.search : `` - ); - const redirectURL = - searchParams.get("redirect_uri") || searchParams.get("redirectURL"); - - // @ts-ignore - window.location = redirectURL ? redirectURL : window.location.origin; - }, [otp, state]); - const handleChange = (event: React.ChangeEvent) => { setEmail(event.target.value); }; - const handleOTPChange = (event: React.ChangeEvent) => { - setOTP(event.target.value); - }; + if (!visiable) + return ( + + ); return (
- {state ? ( - - ) : ( - - )} + + - {loading ? ( - - ) : ( - - )} +
); }; diff --git a/sdks/packages/shield-react/src/components/oidc.tsx b/sdks/packages/shield-react/src/components/oidc.tsx index d199b3ebf..023413b45 100644 --- a/sdks/packages/shield-react/src/components/oidc.tsx +++ b/sdks/packages/shield-react/src/components/oidc.tsx @@ -10,8 +10,17 @@ type ButtonProps = React.HTMLProps & { children?: React.ReactNode; }; -export const OIDCButton = ({ children, type = "button", ...props }: ButtonProps) => ( - ); diff --git a/sdks/packages/shield-react/src/components/organization.tsx b/sdks/packages/shield-react/src/components/organization.tsx new file mode 100644 index 000000000..b0f61b2ca --- /dev/null +++ b/sdks/packages/shield-react/src/components/organization.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { yupResolver } from "@hookform/resolvers/yup"; +import { Button, Flex, InputField, Text, TextField } from "@raystack/apsara"; +import React, { ComponentPropsWithRef } from "react"; +import { Controller, useForm } from "react-hook-form"; +import * as yup from "yup"; +import { useShield } from "../contexts/ShieldContext"; +import { Container } from "./Container"; + +const styles = { + container: { + margin: "auto", + width: "100%", + maxWidth: "384px", + padding: "var(--pd-32)", + }, + titleContainer: { + fontWeight: "400", + }, +}; + +type CreateOrganizationProps = ComponentPropsWithRef & { + title?: string; + description?: string; +}; + +const schema = yup + .object({ + title: yup.string().required(), + name: yup.string().required(), + }) + .required(); + +export const CreateOrganization = ({ + title = "Create a new organization", + description = "Organizations are shared environments where team can work on assets, connections and data operations.", + ...props +}: CreateOrganizationProps) => { + const { + control, + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const { client } = useShield(); + + async function onSubmit(data: any) { + const { + data: { organization }, + } = await client.createOrganisation(data); + // @ts-ignore + window.location = `${window.location.origin}/${organization.name}`; + } + + return ( + + + + {title} + + {description} + + +
+ + + ( + + )} + control={control} + name="title" + /> + + + {errors.title && String(errors.title?.message)} + + + + ( + + )} + control={control} + name="name" + /> + + {errors.name && String(errors.name?.message)} + + + + + +
+
+
+ ); +}; diff --git a/sdks/packages/shield-react/src/components/signup.tsx b/sdks/packages/shield-react/src/components/signup.tsx new file mode 100644 index 000000000..179d6e56d --- /dev/null +++ b/sdks/packages/shield-react/src/components/signup.tsx @@ -0,0 +1,67 @@ +import { Flex, Link, Text } from "@raystack/apsara"; +import React, { ComponentPropsWithRef, useCallback } from "react"; +import { useShield } from "../contexts/ShieldContext"; +import { Container } from "./Container"; +import { Header } from "./Header"; +import { MagicLink } from "./magiclink"; +import { OIDCButton } from "./oidc"; + +const styles = { + titleContainer: { + fontWeight: "400", + }, +}; + +type SignupProps = ComponentPropsWithRef & { + logo?: React.ReactNode; + title?: string; +}; +export const Signup = ({ + logo, + title = "Create your account", + ...props +}: SignupProps) => { + const { config } = useShield(); + const { client, strategies = [] } = useShield(); + + const clickHandler = useCallback( + async (name: string) => { + const { + data: { endpoint }, + } = await client.getAuthStrategyEndpoint(name); + window.location.href = endpoint; + }, + [strategies] + ); + + const mailotp = strategies.find((s) => s.name === "mailotp"); + const filteredOIDC = strategies.filter((s) => s.name !== "mailotp"); + + return ( + +
+ + {filteredOIDC.map((s, index) => { + return ( + clickHandler(s.name)}> + {s.name} + + ); + })} + + {mailotp && } + +
+ + Already have an account?{" "} + + Login + + +
+ + ); +}; diff --git a/sdks/packages/shield-react/src/contexts/ShieldContext.tsx b/sdks/packages/shield-react/src/contexts/ShieldContext.tsx index 084f9f98a..8b4f30087 100644 --- a/sdks/packages/shield-react/src/contexts/ShieldContext.tsx +++ b/sdks/packages/shield-react/src/contexts/ShieldContext.tsx @@ -17,6 +17,7 @@ import React, { import Shield from "../shield"; interface ShieldContextProviderProps { + config: ShieldClientOptions; client: Shield; organizations: Organization[]; @@ -32,8 +33,16 @@ interface ShieldContextProviderProps { setUser: Dispatch>; } +const defaultConfig = { + endpoint: "http://localhost:8080", + redirectLogin: "http://localhost:3000", + redirectSignup: "http://localhost:3000/signup", + redirectMagicLinkVerify: "http://localhost:3000/magiclink-verify", +}; + const initialValues: ShieldContextProviderProps = { - client: Shield.getOrCreateInstance({ endpoint: "http://localhost:8080" }), + config: defaultConfig, + client: Shield.getOrCreateInstance(defaultConfig), organizations: [], setOrganizations: () => undefined, @@ -52,9 +61,13 @@ export const ShieldContext = createContext(initialValues); ShieldContext.displayName = "ShieldContext "; -export const ShieldContextProvider = (props: ShieldProviderProps) => { - const { children, initialState, ...options } = props; - const { shieldClient } = useShieldClient(options); +export const ShieldContextProvider = ({ + children, + config, + initialState, + ...options +}: ShieldProviderProps) => { + const { shieldClient } = useShieldClient(config); const [organizations, setOrganizations] = useState([]); const [groups, setGroups] = useState([]); @@ -134,6 +147,7 @@ export const ShieldContextProvider = (props: ShieldProviderProps) => { return ( { - const { children, initialState, ...options } = props; + const { children, initialState, config, ...options } = props; return ( - + {children} ); diff --git a/sdks/packages/shield-react/src/contexts/useMaxAllowedInstancesGuard.tsx b/sdks/packages/shield-react/src/contexts/useMaxAllowedInstancesGuard.tsx index 4784d54ca..b5d39df84 100644 --- a/sdks/packages/shield-react/src/contexts/useMaxAllowedInstancesGuard.tsx +++ b/sdks/packages/shield-react/src/contexts/useMaxAllowedInstancesGuard.tsx @@ -28,6 +28,7 @@ export function withMaxAllowedInstancesGuard

( const displayName = WrappedComponent.displayName || name || "Component"; const Hoc = (props: P) => { useMaxAllowedInstancesGuard(name, error); + // @ts-ignore return ; }; Hoc.displayName = `withMaxAllowedInstancesGuard(${displayName})`; diff --git a/sdks/packages/shield-react/src/index.ts b/sdks/packages/shield-react/src/index.ts index 169e919e4..6e0691a3e 100644 --- a/sdks/packages/shield-react/src/index.ts +++ b/sdks/packages/shield-react/src/index.ts @@ -1,5 +1,8 @@ import "@raystack/apsara/index.css"; export * from "./components/login"; +export * from "./components/magiclink-verify"; +export * from "./components/organization"; +export * from "./components/signup"; export { useShield } from "./contexts/ShieldContext"; export { ShieldProvider } from "./contexts/ShieldProvider"; diff --git a/sdks/packages/shield-react/src/shield.ts b/sdks/packages/shield-react/src/shield.ts index 7f30fa8f6..e52eb143c 100644 --- a/sdks/packages/shield-react/src/shield.ts +++ b/sdks/packages/shield-react/src/shield.ts @@ -72,4 +72,10 @@ export default class Shield { ): Promise> => { return await this.instance.get(`/v1beta1/users/${userId}/organizations`); }; + + public createOrganisation = async ( + data: any + ): Promise> => { + return await this.instance.post(`/v1beta1/organizations`, data); + }; } diff --git a/sdks/packages/shield-react/tsup.config.ts b/sdks/packages/shield-react/tsup.config.ts index fff13bbb2..eda9541c5 100644 --- a/sdks/packages/shield-react/tsup.config.ts +++ b/sdks/packages/shield-react/tsup.config.ts @@ -11,6 +11,9 @@ export const tsup: Options = { skipNodeModulesBundle: true, globalName: "Shield", target: "es6", + banner: { + js: "'use client'", + }, /** * Couple build options for UMD/iife build. * - externalGlobalPlugin to use window.React instead of trying to bundle it. diff --git a/sdks/pnpm-lock.yaml b/sdks/pnpm-lock.yaml index cf4b15dc6..7716eaf29 100644 --- a/sdks/pnpm-lock.yaml +++ b/sdks/pnpm-lock.yaml @@ -19,12 +19,12 @@ importers: version: 0.11.10 turbo: specifier: latest - version: 1.10.7 + version: 1.10.9 typescript: specifier: '4.7' version: 4.7.2 - packages/shield: + packages/shield-js: dependencies: '@types/react': specifier: ^18.2.5 @@ -61,15 +61,18 @@ importers: packages/shield-react: dependencies: '@raystack/apsara': - specifier: ^0.10.1 - version: 0.10.1 + specifier: ^0.10.3 + version: 0.10.3 axios: specifier: ^1.4.0 version: 1.4.0 devDependencies: + '@hookform/resolvers': + specifier: ^3.1.1 + version: 3.1.1(react-hook-form@7.45.2) '@raystack/shield': specifier: workspace:^ - version: link:../shield + version: link:../shield-js '@size-limit/preset-small-lib': specifier: ^8.2.6 version: 8.2.6(size-limit@8.2.6) @@ -100,6 +103,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-hook-form: + specifier: ^7.45.2 + version: 7.45.2(react@18.2.0) size-limit: specifier: ^8.2.6 version: 8.2.6 @@ -109,6 +115,9 @@ importers: typescript: specifier: '4.7' version: 4.7.2 + yup: + specifier: ^1.2.0 + version: 1.2.0 zustand: specifier: ^4.3.9 version: 4.3.9(react@18.2.0) @@ -547,6 +556,14 @@ packages: dev: true optional: true + /@hookform/resolvers@3.1.1(react-hook-form@7.45.2): + resolution: {integrity: sha512-tS16bAUkqjITNSvbJuO1x7MXbn7Oe8ZziDTJdA9mMvsoYthnOOiznOTGBYwbdlYBgU+tgpI/BtTU3paRbCuSlg==} + peerDependencies: + react-hook-form: ^7.0.0 + dependencies: + react-hook-form: 7.45.2(react@18.2.0) + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -609,9 +626,9 @@ packages: fastq: 1.15.0 dev: true - /@raystack/apsara@0.10.1: - resolution: {integrity: sha512-jyn8xcJV/tjZfPP7YHrBs1J8U1+d+TV2R4ESNJcaoVvcJBOC38XfSEMExF8uEf1VXfUMYO+j8W73Y4qtNZ3WeQ==} - engines: {node: 14.21.3} + /@raystack/apsara@0.10.3: + resolution: {integrity: sha512-qy/GcxdjNTGfZA+pHV+pRsbJa3wueggICyfWE4210CcOOqOrmWhRx1RRBH7795YRoxh57D6L+HBtaf7GHc8wiQ==} + engines: {node: '>=12.x.x'} dev: false /@samverschueren/stream-to-observable@0.3.1(rxjs@6.6.7): @@ -3349,6 +3366,10 @@ packages: engines: {node: '>= 0.6.0'} dev: true + /property-expr@2.0.5: + resolution: {integrity: sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==} + dev: true + /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false @@ -3401,6 +3422,15 @@ packages: scheduler: 0.23.0 dev: true + /react-hook-form@7.45.2(react@18.2.0): + resolution: {integrity: sha512-9s45OdTaKN+4NSTbXVqeDITd/nwIg++nxJGL8+OD5uf1DxvhsXQ641kaYHk5K28cpIOTYm71O/fYk7rFaygb3A==} + engines: {node: '>=12.22.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + dependencies: + react: 18.2.0 + dev: true + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -3955,6 +3985,10 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true + /tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + dev: true + /title-case@2.1.1: resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} dependencies: @@ -3986,6 +4020,10 @@ packages: is-number: 7.0.0 dev: true + /toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + dev: true + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false @@ -4085,65 +4123,65 @@ packages: - ts-node dev: true - /turbo-darwin-64@1.10.7: - resolution: {integrity: sha512-N2MNuhwrl6g7vGuz4y3fFG2aR1oCs0UZ5HKl8KSTn/VC2y2YIuLGedQ3OVbo0TfEvygAlF3QGAAKKtOCmGPNKA==} + /turbo-darwin-64@1.10.9: + resolution: {integrity: sha512-Avz3wsYYb8/vjyHPVRFbNbowIiaF33vcBRklIUkPchTLvZekrT5x3ltQBCflyoi2zJV9g08hK4xXTGuCxeVvPA==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-darwin-arm64@1.10.7: - resolution: {integrity: sha512-WbJkvjU+6qkngp7K4EsswOriO3xrNQag7YEGRtfLoDdMTk4O4QTeU6sfg2dKfDsBpTidTvEDwgIYJhYVGzrz9Q==} + /turbo-darwin-arm64@1.10.9: + resolution: {integrity: sha512-HyggdSPc/v2HuYrJF75smhIlurn8bY2cWpZYCjOL5Pj2DpLyhBs+nk+JirZl7XQiaUEVFj6eTbsejXyDP2Ritw==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-linux-64@1.10.7: - resolution: {integrity: sha512-x1CF2CDP1pDz/J8/B2T0hnmmOQI2+y11JGIzNP0KtwxDM7rmeg3DDTtDM/9PwGqfPotN9iVGgMiMvBuMFbsLhg==} + /turbo-linux-64@1.10.9: + resolution: {integrity: sha512-qvdEgJKzDjOYY8o/HlnSwD+TIXiAML+3l6wUG4Ojuh/6cIhemLMRaHmEG+LygRW7GRw3dDv3hpp9OtiKmyxFdQ==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-linux-arm64@1.10.7: - resolution: {integrity: sha512-JtnBmaBSYbs7peJPkXzXxsRGSGBmBEIb6/kC8RRmyvPAMyqF8wIex0pttsI+9plghREiGPtRWv/lfQEPRlXnNQ==} + /turbo-linux-arm64@1.10.9: + resolution: {integrity: sha512-gva8H3CS8F6HlXL6YTDJAPrvPXVjBCxdd4DKABghjAxdknV5mZV1WWwMuGf0Z2W8qtmNG1XS0Dt2Wrb1ERFnLw==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-windows-64@1.10.7: - resolution: {integrity: sha512-7A/4CByoHdolWS8dg3DPm99owfu1aY/W0V0+KxFd0o2JQMTQtoBgIMSvZesXaWM57z3OLsietFivDLQPuzE75w==} + /turbo-windows-64@1.10.9: + resolution: {integrity: sha512-OZ+bkSBJIkyl4JBDk8FX2/bOqtrElfXQV/KQ8/ibddB8Clzn/owx9FS1eXGdvttRZ9IJWzPrdFv+k4vbWQfE7w==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /turbo-windows-arm64@1.10.7: - resolution: {integrity: sha512-D36K/3b6+hqm9IBAymnuVgyePktwQ+F0lSXr2B9JfAdFPBktSqGmp50JNC7pahxhnuCLj0Vdpe9RqfnJw5zATA==} + /turbo-windows-arm64@1.10.9: + resolution: {integrity: sha512-WhhhioGaePkGdGOIlrOB8LF8400FJUAQcVf8yCTvjzDB+OWn3dJQ3nalFjxH0PlZ17l6TPGt1WvWQiDVXUE4pw==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /turbo@1.10.7: - resolution: {integrity: sha512-xm0MPM28TWx1e6TNC3wokfE5eaDqlfi0G24kmeHupDUZt5Wd0OzHFENEHMPqEaNKJ0I+AMObL6nbSZonZBV2HA==} + /turbo@1.10.9: + resolution: {integrity: sha512-s1ZRRD89NelCYHty1SpV1Elpv2LRrktgcddbZm9oTq1RPNpJFSrrEOAJhNz/w0fxTSjSN1Ey3TWZghjUjgKuzg==} hasBin: true requiresBuild: true optionalDependencies: - turbo-darwin-64: 1.10.7 - turbo-darwin-arm64: 1.10.7 - turbo-linux-64: 1.10.7 - turbo-linux-arm64: 1.10.7 - turbo-windows-64: 1.10.7 - turbo-windows-arm64: 1.10.7 + turbo-darwin-64: 1.10.9 + turbo-darwin-arm64: 1.10.9 + turbo-linux-64: 1.10.9 + turbo-linux-arm64: 1.10.9 + turbo-windows-64: 1.10.9 + turbo-windows-arm64: 1.10.9 dev: true /type-fest@0.10.0: @@ -4181,6 +4219,11 @@ packages: engines: {node: '>=8'} dev: true + /type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + dev: true + /typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} dependencies: @@ -4424,6 +4467,15 @@ packages: engines: {node: '>=10'} dev: true + /yup@1.2.0: + resolution: {integrity: sha512-PPqYKSAXjpRCgLgLKVGPA33v5c/WgEx3wi6NFjIiegz90zSwyMpvTFp/uGcVnnbx6to28pgnzp/q8ih3QRjLMQ==} + dependencies: + property-expr: 2.0.5 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + dev: true + /zustand@4.3.9(react@18.2.0): resolution: {integrity: sha512-Tat5r8jOMG1Vcsj8uldMyqYKC5IZvQif8zetmLHs9WoZlntTHmIoNM8TpLRY31ExncuUvUOXehd0kvahkuHjDw==} engines: {node: '>=12.7.0'}