Added login flow
This commit is contained in:
parent
4afe2f906d
commit
a627addad5
@ -47,10 +47,6 @@ selfservice:
|
|||||||
registration:
|
registration:
|
||||||
lifespan: 10m
|
lifespan: 10m
|
||||||
ui_url: http://127.0.0.1:3000/signup
|
ui_url: http://127.0.0.1:3000/signup
|
||||||
after:
|
|
||||||
password:
|
|
||||||
hooks:
|
|
||||||
- hook: session
|
|
||||||
|
|
||||||
log:
|
log:
|
||||||
level: debug
|
level: debug
|
||||||
|
@ -5,34 +5,66 @@ import FormField from "../common/form/FormField"
|
|||||||
import InlineLink from "../common/InlineLink"
|
import InlineLink from "../common/InlineLink"
|
||||||
import SubmitButton from "../common/form/SubmitButton"
|
import SubmitButton from "../common/form/SubmitButton"
|
||||||
|
|
||||||
interface LoginFormValues {
|
import * as validation from "../../config/validation"
|
||||||
username: string
|
import { z } from "zod"
|
||||||
password: string
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
}
|
import useLogInFlow from "../../hooks/useLogInFlow"
|
||||||
|
import Input from "../common/Input"
|
||||||
|
|
||||||
|
const LogInFormSchema = z.object({
|
||||||
|
csrfToken: z.string(),
|
||||||
|
password: validation.password,
|
||||||
|
email: z.string().email({ message: "Not a valid email address" }).trim(),
|
||||||
|
})
|
||||||
|
|
||||||
|
type LogInFormValues = z.infer<typeof LogInFormSchema>
|
||||||
|
|
||||||
|
const formFields = [
|
||||||
|
{ id: "email", label: "Email", type: "email" },
|
||||||
|
{ id: "password", label: "Password", type: "password" },
|
||||||
|
]
|
||||||
|
|
||||||
const LoginForm: FC = () => {
|
const LoginForm: FC = () => {
|
||||||
const { register, handleSubmit } = useForm<LoginFormValues>()
|
const logInFlow = useLogInFlow()
|
||||||
const onSubmit: SubmitHandler<LoginFormValues> = (data) => console.log(data)
|
const { register, handleSubmit, formState } = useForm<LogInFormValues>({
|
||||||
|
resolver: zodResolver(LogInFormSchema),
|
||||||
|
})
|
||||||
|
const onSubmit: SubmitHandler<LogInFormValues> = (data) =>
|
||||||
|
logInFlow.submitData({
|
||||||
|
csrf_token: data.csrfToken,
|
||||||
|
method: "password",
|
||||||
|
identifier: data.email,
|
||||||
|
password: data.password,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<FormField
|
{logInFlow.flow && (
|
||||||
label="Username"
|
<Input
|
||||||
className="py-2 px-2 outline-2 w-full"
|
hidden={true}
|
||||||
{...register("username")}
|
value={logInFlow.flow.ui.nodes[0].attributes.value}
|
||||||
/>
|
{...register("csrfToken")}
|
||||||
<FormField
|
/>
|
||||||
label="Password"
|
)}
|
||||||
type="password"
|
{formFields.map((field) => (
|
||||||
{...register("password")}
|
<FormField
|
||||||
className="py-2 px-2 outline-2 w-full"
|
key={field.id}
|
||||||
bottomElement={
|
id={field.id}
|
||||||
<InlineLink to="#" className="block mt-2">
|
type={field.type}
|
||||||
Trouble logging in?
|
label={field.label}
|
||||||
</InlineLink>
|
{...register(field.id as any)}
|
||||||
}
|
className="py-2 px-2 outline-2 w-full"
|
||||||
/>
|
bottomElement={
|
||||||
|
<p className="text-xs">
|
||||||
|
{formState.errors[(field.id as any) || ""]?.message}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
<SubmitButton className="w-full" value="Log In" />
|
<SubmitButton className="w-full" value="Log In" />
|
||||||
|
<div className="text-center">
|
||||||
|
<InlineLink to="#">Trouble logging in?</InlineLink>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -7,24 +7,14 @@ import FormField from "../common/form/FormField"
|
|||||||
import InlineLink from "../common/InlineLink"
|
import InlineLink from "../common/InlineLink"
|
||||||
import Input from "../common/Input"
|
import Input from "../common/Input"
|
||||||
import SubmitButton from "../common/form/SubmitButton"
|
import SubmitButton from "../common/form/SubmitButton"
|
||||||
import { PASSWORD_REGEX } from "../../config"
|
|
||||||
import useSignUpFlow from "../../hooks/useSignUpFlow"
|
import useSignUpFlow from "../../hooks/useSignUpFlow"
|
||||||
|
import * as validation from "../../config/validation"
|
||||||
|
|
||||||
const SignupFormSchema = z
|
const SignupFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
csrfToken: z.string(),
|
csrfToken: z.string(),
|
||||||
username: z
|
username: validation.username,
|
||||||
.string()
|
password: validation.password,
|
||||||
.trim()
|
|
||||||
.min(1, { message: "Username must be at least 1 character long." })
|
|
||||||
.max(16, { message: "Username can't be longer than 16 characters.." }),
|
|
||||||
password: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.regex(
|
|
||||||
PASSWORD_REGEX,
|
|
||||||
"Password must be 8-64 long and must contain a number, uppercase, lowercase and special character.",
|
|
||||||
),
|
|
||||||
passwordRepeat: z.string().trim(),
|
passwordRepeat: z.string().trim(),
|
||||||
email: z.string().email({ message: "Not a valid email address" }).trim(),
|
email: z.string().email({ message: "Not a valid email address" }).trim(),
|
||||||
})
|
})
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
import { UserIcon } from "@heroicons/react/24/outline"
|
import {
|
||||||
|
ArrowRightIcon,
|
||||||
|
ArrowRightOnRectangleIcon,
|
||||||
|
UserIcon,
|
||||||
|
} from "@heroicons/react/24/outline"
|
||||||
import { FC, useState } from "react"
|
import { FC, useState } from "react"
|
||||||
|
import { useLogout } from "../../hooks/useLogout"
|
||||||
|
import useSession from "../../hooks/useSession"
|
||||||
|
|
||||||
import Button from "../common/Button"
|
import Button from "../common/Button"
|
||||||
import Logo from "../common/Logo"
|
import Logo from "../common/Logo"
|
||||||
import LoginModal, { LoginModelProps } from "../login/LoginModal"
|
import LoginModal, { LoginModelProps } from "../login/LoginModal"
|
||||||
|
|
||||||
const NavBar: FC = () => {
|
const NavBar: FC = () => {
|
||||||
|
const logout = useLogout()
|
||||||
|
const session = useSession((state) => state.session)
|
||||||
const [modalProps, setModalProps] = useState<LoginModelProps>({
|
const [modalProps, setModalProps] = useState<LoginModelProps>({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
defaultPage: 0,
|
defaultPage: 0,
|
||||||
@ -23,6 +31,7 @@ const NavBar: FC = () => {
|
|||||||
isOpen: true,
|
isOpen: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log({ session })
|
||||||
return (
|
return (
|
||||||
<nav className="bg-zinc-800 w-screen font-semibold border-b border-b-black">
|
<nav className="bg-zinc-800 w-screen font-semibold border-b border-b-black">
|
||||||
<div className="flex flex-row justify-between items-center h-12 mx-2">
|
<div className="flex flex-row justify-between items-center h-12 mx-2">
|
||||||
@ -35,27 +44,41 @@ const NavBar: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ul className="justify-end flex flex-row space-x-3 items-center">
|
<ul className="justify-end flex flex-row space-x-3 items-center">
|
||||||
<li>
|
{session ? (
|
||||||
<Button
|
<>
|
||||||
className="text-sm px-3 py-2 bg-neutral-700"
|
<li>
|
||||||
onClick={showLoginTab}
|
<UserIcon className="h-5 w-5 inline-block" />
|
||||||
>
|
</li>
|
||||||
Log In
|
<li>
|
||||||
</Button>
|
<Button
|
||||||
</li>
|
variant="subtle"
|
||||||
<li>
|
className="p-[0.4rem]"
|
||||||
<Button
|
onClick={logout}
|
||||||
className="text-sm px-3 py-2 bg-violet-500"
|
>
|
||||||
onClick={showSignupTab}
|
<ArrowRightOnRectangleIcon className="h-5 w-5 inline-block" />
|
||||||
>
|
</Button>
|
||||||
Sign Up
|
</li>
|
||||||
</Button>
|
</>
|
||||||
</li>
|
) : (
|
||||||
<li>
|
<>
|
||||||
<Button variant="subtle" className="p-[0.4rem]">
|
<li>
|
||||||
<UserIcon className="h-5 w-5 inline-block" />
|
<Button
|
||||||
</Button>
|
className="text-sm px-3 py-2 bg-neutral-700"
|
||||||
</li>
|
onClick={showLoginTab}
|
||||||
|
>
|
||||||
|
Log In
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Button
|
||||||
|
className="text-sm px-3 py-2 bg-violet-500"
|
||||||
|
onClick={showSignupTab}
|
||||||
|
>
|
||||||
|
Sign Up
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1 @@
|
|||||||
export const KRATOS_URL = "http://127.0.0.1:4433"
|
export const KRATOS_URL = "http://127.0.0.1:4433"
|
||||||
export const PASSWORD_REGEX =
|
|
||||||
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,64}$/
|
|
||||||
|
18
client/config/validation.ts
Normal file
18
client/config/validation.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export const PASSWORD_REGEX =
|
||||||
|
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,64}$/
|
||||||
|
|
||||||
|
export const username = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(3, { message: "Username must be at least 3 character long." })
|
||||||
|
.max(16, { message: "Username can't be longer than 16 characters.." })
|
||||||
|
|
||||||
|
export const password = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(
|
||||||
|
PASSWORD_REGEX,
|
||||||
|
"Password must be 8-64 long and must contain a number, uppercase, lowercase and special character.",
|
||||||
|
)
|
53
client/hooks/useLogInFlow.ts
Normal file
53
client/hooks/useLogInFlow.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
SelfServiceRegistrationFlow,
|
||||||
|
SubmitSelfServiceLoginFlowBody,
|
||||||
|
} from "@ory/client"
|
||||||
|
import { useRouter } from "next/router"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import ory from "../services/ory"
|
||||||
|
import useSession from "./useSession"
|
||||||
|
|
||||||
|
const useLogInFlow = () => {
|
||||||
|
const router = useRouter()
|
||||||
|
const [flow, setFlow] = useState<SelfServiceRegistrationFlow>()
|
||||||
|
const { flow: flowId } = router.query
|
||||||
|
const updateSession = useSession((state) => state.update)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const func = async () => {
|
||||||
|
if (!router.isReady || flow) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let serviceFlow
|
||||||
|
if (flowId) {
|
||||||
|
serviceFlow = await ory.getSelfServiceLoginFlow(String(flowId))
|
||||||
|
} else {
|
||||||
|
serviceFlow = await ory.initializeSelfServiceLoginFlowForBrowsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
setFlow(serviceFlow.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func()
|
||||||
|
}, [flowId, router, router.isReady, flow])
|
||||||
|
|
||||||
|
const submitData = async (data: SubmitSelfServiceLoginFlowBody) => {
|
||||||
|
await router.push(`/login?flow=${flow?.id}`, undefined, {
|
||||||
|
shallow: true,
|
||||||
|
})
|
||||||
|
ory
|
||||||
|
.submitSelfServiceLoginFlow(String(flow?.id), undefined, data)
|
||||||
|
.then(async ({ data }) => {
|
||||||
|
updateSession(data.session)
|
||||||
|
await router.push(flow?.return_to || "/")
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log({ err })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { flow, submitData }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useLogInFlow
|
38
client/hooks/useLogout.ts
Normal file
38
client/hooks/useLogout.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { AxiosError } from "axios"
|
||||||
|
import { useRouter } from "next/router"
|
||||||
|
import { useState, useEffect, DependencyList } from "react"
|
||||||
|
|
||||||
|
import ory from "../services/ory"
|
||||||
|
import useSession from "./useSession"
|
||||||
|
|
||||||
|
export function useLogout(deps?: DependencyList) {
|
||||||
|
const session = useSession()
|
||||||
|
const [logoutToken, setLogoutToken] = useState<string>("")
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ory
|
||||||
|
.createSelfServiceLogoutFlowUrlForBrowsers()
|
||||||
|
.then(({ data }) => {
|
||||||
|
setLogoutToken(data.logout_token)
|
||||||
|
})
|
||||||
|
.catch((err: AxiosError) => {
|
||||||
|
switch (err.response?.status) {
|
||||||
|
case 401:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(err)
|
||||||
|
})
|
||||||
|
}, deps)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (logoutToken) {
|
||||||
|
session.drop()
|
||||||
|
ory
|
||||||
|
.submitSelfServiceLogoutFlow(logoutToken)
|
||||||
|
.then(() => router.push("/"))
|
||||||
|
.then(() => router.reload())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
client/hooks/useSession.ts
Normal file
31
client/hooks/useSession.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import create from "zustand"
|
||||||
|
|
||||||
|
import { Session } from "@ory/client"
|
||||||
|
import ory from "../services/ory"
|
||||||
|
import { AxiosError } from "axios"
|
||||||
|
|
||||||
|
export interface SessionState {
|
||||||
|
session?: Session
|
||||||
|
|
||||||
|
load: () => void
|
||||||
|
update: (data: Session) => void
|
||||||
|
drop: () => void
|
||||||
|
}
|
||||||
|
const useSession = create<SessionState>((set) => ({
|
||||||
|
session: undefined,
|
||||||
|
|
||||||
|
load: () => {
|
||||||
|
ory
|
||||||
|
.toSession()
|
||||||
|
.then(({ data }) => {
|
||||||
|
set({ session: data })
|
||||||
|
})
|
||||||
|
.catch((err: AxiosError) => {})
|
||||||
|
},
|
||||||
|
update: (session) => set({ session }),
|
||||||
|
drop: () => {
|
||||||
|
set({ session: undefined })
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
export default useSession
|
@ -1,15 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
SelfServiceRegistrationFlow,
|
SelfServiceRegistrationFlow,
|
||||||
|
Session,
|
||||||
SubmitSelfServiceRegistrationFlowBody,
|
SubmitSelfServiceRegistrationFlowBody,
|
||||||
} from "@ory/client"
|
} from "@ory/client"
|
||||||
import { useRouter } from "next/router"
|
import { useRouter } from "next/router"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import ory from "../services/ory"
|
import ory from "../services/ory"
|
||||||
|
import useSession from "./useSession"
|
||||||
|
|
||||||
export const useSignUpFlow = () => {
|
export const useSignUpFlow = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [flow, setFlow] = useState<SelfServiceRegistrationFlow>()
|
const [flow, setFlow] = useState<SelfServiceRegistrationFlow>()
|
||||||
const { flow: flowId, return_to: returnTo } = router.query
|
const { flow: flowId } = router.query
|
||||||
|
const updateSession = useSession((state) => state.update)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const func = async () => {
|
const func = async () => {
|
||||||
@ -22,30 +25,28 @@ export const useSignUpFlow = () => {
|
|||||||
serviceFlow = await ory.getSelfServiceRegistrationFlow(String(flowId))
|
serviceFlow = await ory.getSelfServiceRegistrationFlow(String(flowId))
|
||||||
} else {
|
} else {
|
||||||
serviceFlow =
|
serviceFlow =
|
||||||
await ory.initializeSelfServiceRegistrationFlowForBrowsers(
|
await ory.initializeSelfServiceRegistrationFlowForBrowsers()
|
||||||
returnTo ? String(returnTo) : undefined,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setFlow(serviceFlow.data)
|
setFlow(serviceFlow.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func()
|
func()
|
||||||
}, [flowId, router, router.isReady, returnTo, flow])
|
}, [flowId, router, router.isReady, flow])
|
||||||
|
|
||||||
const submitData = async (data: SubmitSelfServiceRegistrationFlowBody) => {
|
const submitData = async (data: SubmitSelfServiceRegistrationFlowBody) => {
|
||||||
await router.push(`/signup?flow=${flow?.id}`, undefined, {
|
await router.push(`/signup?flow=${flow?.id}`, undefined, {
|
||||||
shallow: true,
|
shallow: true,
|
||||||
})
|
})
|
||||||
try {
|
ory
|
||||||
const resp = await ory.submitSelfServiceRegistrationFlow(
|
.submitSelfServiceRegistrationFlow(String(flow?.id), data)
|
||||||
String(flow?.id),
|
.then(async ({ data }) => {
|
||||||
data,
|
updateSession(data.session as Session)
|
||||||
)
|
await router.push(flow?.return_to || "/")
|
||||||
// TODO: handle registration
|
})
|
||||||
} catch (e) {
|
.catch((err) => {
|
||||||
// TODO: handle errors
|
console.log({ err })
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return { flow, submitData }
|
return { flow, submitData }
|
||||||
|
@ -28,7 +28,8 @@
|
|||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-hook-form": "^7.37.0",
|
"react-hook-form": "^7.37.0",
|
||||||
"tailwind-scrollbar": "2.1.0-preview.0",
|
"tailwind-scrollbar": "2.1.0-preview.0",
|
||||||
"zod": "^3.19.1"
|
"zod": "^3.19.1",
|
||||||
|
"zustand": "^4.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@trivago/prettier-plugin-sort-imports": "^2.0.2",
|
"@trivago/prettier-plugin-sort-imports": "^2.0.2",
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
import '../styles/globals.css'
|
import "../styles/globals.css"
|
||||||
import type { AppProps } from 'next/app'
|
import type { AppProps } from "next/app"
|
||||||
|
import useSession from "../hooks/useSession"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
|
const loadSession = useSession((state) => state.load)
|
||||||
|
useEffect(() => {
|
||||||
|
loadSession()
|
||||||
|
}, [loadSession])
|
||||||
|
|
||||||
return <Component {...pageProps} />
|
return <Component {...pageProps} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
client/pages/index.tsx
Normal file
9
client/pages/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { NextPage } from "next"
|
||||||
|
import useSession from "../hooks/useSession"
|
||||||
|
|
||||||
|
const IndexPage: NextPage = () => {
|
||||||
|
const session = useSession()
|
||||||
|
return <div>{JSON.stringify(session.session || {})}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IndexPage
|
26
client/pnpm-lock.yaml
generated
26
client/pnpm-lock.yaml
generated
@ -26,6 +26,7 @@ specifiers:
|
|||||||
tailwindcss: ^3.1.8
|
tailwindcss: ^3.1.8
|
||||||
typescript: 4.4.4
|
typescript: 4.4.4
|
||||||
zod: ^3.19.1
|
zod: ^3.19.1
|
||||||
|
zustand: ^4.1.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@headlessui/react': 1.7.3_sfoxds7t5ydpegc3knd667wn6m
|
'@headlessui/react': 1.7.3_sfoxds7t5ydpegc3knd667wn6m
|
||||||
@ -41,6 +42,7 @@ dependencies:
|
|||||||
react-hook-form: 7.37.0_react@17.0.2
|
react-hook-form: 7.37.0_react@17.0.2
|
||||||
tailwind-scrollbar: 2.1.0-preview.0_tailwindcss@3.1.8
|
tailwind-scrollbar: 2.1.0-preview.0_tailwindcss@3.1.8
|
||||||
zod: 3.19.1
|
zod: 3.19.1
|
||||||
|
zustand: 4.1.2_react@17.0.2
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@trivago/prettier-plugin-sort-imports': 2.0.4_prettier@2.7.1
|
'@trivago/prettier-plugin-sort-imports': 2.0.4_prettier@2.7.1
|
||||||
@ -3479,6 +3481,14 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.1.1
|
punycode: 2.1.1
|
||||||
|
|
||||||
|
/use-sync-external-store/1.2.0_react@17.0.2:
|
||||||
|
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
react: 17.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/util-deprecate/1.0.2:
|
/util-deprecate/1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
@ -3572,3 +3582,19 @@ packages:
|
|||||||
/zod/3.19.1:
|
/zod/3.19.1:
|
||||||
resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==}
|
resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/zustand/4.1.2_react@17.0.2:
|
||||||
|
resolution: {integrity: sha512-gcRaKchcxFPbImrBb/BKgujOhHhik9YhVpIeP87ETT7uokEe2Szu7KkuZ9ghjtD+/KKkcrRNktR2AiLXPIbKIQ==}
|
||||||
|
engines: {node: '>=12.7.0'}
|
||||||
|
peerDependencies:
|
||||||
|
immer: '>=9.0'
|
||||||
|
react: '>=16.8'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
immer:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
react: 17.0.2
|
||||||
|
use-sync-external-store: 1.2.0_react@17.0.2
|
||||||
|
dev: false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user