add auth
This commit is contained in:
145
dashboard_js/app/components/form.jsx
Normal file
145
dashboard_js/app/components/form.jsx
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import React, { forwardRef, useContext, useId } from 'react'
|
||||||
|
import { ExclamationCircleIcon } from '@heroicons/react/24/outline'
|
||||||
|
import { omit } from 'ramda'
|
||||||
|
import Loader from './loader'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
|
const ControlContext = React.createContext({})
|
||||||
|
|
||||||
|
function ControlProvider({ children, ...props }) {
|
||||||
|
return (
|
||||||
|
<ControlContext.Provider value={props}>{children}</ControlContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function useControl(props) {
|
||||||
|
const field = useContext(ControlContext)
|
||||||
|
return { ...field, ...props }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Control = forwardRef(function Control(
|
||||||
|
{ as = 'div', children, ...props },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
const id = useId()
|
||||||
|
const props_ = omit(['id'], props)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ControlProvider id={props?.id || id} {...props_}>
|
||||||
|
{React.createElement(as, { ref, ...props }, children)}
|
||||||
|
</ControlProvider>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Button({
|
||||||
|
children,
|
||||||
|
as = 'button',
|
||||||
|
className,
|
||||||
|
isLoading = false,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
if (isLoading) {
|
||||||
|
props['disabled'] = isLoading
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.createElement(
|
||||||
|
as,
|
||||||
|
{
|
||||||
|
className: clsx(
|
||||||
|
'font-medium text-green-primary rounded-lg bg-green-secondary hover:bg-green-support',
|
||||||
|
'h-12 px-4 relative',
|
||||||
|
'disabled:bg-green-secondary/50 disabled:pointer-events-none',
|
||||||
|
'transition',
|
||||||
|
className,
|
||||||
|
),
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
<>
|
||||||
|
{isLoading && (
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center bg-green-secondary rounded-xl">
|
||||||
|
<Loader className="w-5 text-white" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Label({ children, className, ...props }) {
|
||||||
|
const { id, htmlFor, className: _, ...field } = useControl(props)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
className={clsx(
|
||||||
|
"black cursor-pointer aria-required:after:content-['*'] aria-required:after:ml-0.5 aria-required:after:text-red-500",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
htmlFor={htmlFor ?? id}
|
||||||
|
{...field}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Input = forwardRef(function Input(
|
||||||
|
{ as = 'input', size = 'base', className, children, ...props },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
const { className: _, ...field } = useControl(props)
|
||||||
|
const sizes = { base: 'h-12' }
|
||||||
|
|
||||||
|
return React.createElement(
|
||||||
|
as,
|
||||||
|
{
|
||||||
|
className: clsx(
|
||||||
|
'bg-white outline-none px-4 rounded-lg transition',
|
||||||
|
'border border-green-light dark:border-gray-700 dark:bg-gray-800',
|
||||||
|
'focus:ring-1 focus:border-green-secondary focus:ring-green-secondary focus:placeholder:text-transparent',
|
||||||
|
// Tailwind's won't inherit focus behavior; you must define it explicitly for both modes.
|
||||||
|
'dark:focus:border-green-secondary',
|
||||||
|
'aria-[invalid=true]:border-red-400 aria-[invalid=true]:ring-red-400',
|
||||||
|
'dark:aria-[invalid=true]:border-red-500 dark:aria-[invalid=true]:ring-red-500',
|
||||||
|
'disabled:text-gray-400 disabled:border-gray-300 disabled:bg-gray-200',
|
||||||
|
'dark:disabled:text-gray-500 dark:disabled:bg-gray-700',
|
||||||
|
sizes?.[size],
|
||||||
|
className,
|
||||||
|
),
|
||||||
|
ref,
|
||||||
|
...field,
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Checkbox = forwardRef(function Checkbox(
|
||||||
|
{ className, ...props },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
const { className: _, ...field } = useControl(props)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className={clsx(
|
||||||
|
'text-green-secondary border border-gray-300',
|
||||||
|
'focus:ring-2 focus:border-green-secondary focus:ring-green-secondary focus:ring-offset-0 focus:ring-opacity-30',
|
||||||
|
'dark:border-gray-700 dark:bg-gray-800 focus:dark:border-security dark:checked:bg-green-secondary dark:checked:border-security dark:disabled:bg-gray-700',
|
||||||
|
'disabled:bg-gray-200 outline-none rounded transition',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export function Error({ children }) {
|
||||||
|
return (
|
||||||
|
<div className="text-sm text-red-500 flex items-start gap-0.5">
|
||||||
|
<ExclamationCircleIcon className="w-4 mt-[1.5px] flex-shrink-0" />
|
||||||
|
<div>{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
27
dashboard_js/app/components/loader.jsx
Normal file
27
dashboard_js/app/components/loader.jsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
|
export default function Loader({ className, ...props }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={clsx('animate-spin', className)}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
className="opacity-25"
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="10"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="4"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="opacity-50"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ interface AuthContextType {
|
|||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
}) => Promise<Auth.SignInOutput>
|
}) => Promise<Auth.SignInOutput>
|
||||||
currentUser: () => Promise<Auth.FetchUserAttributesOutput | void>
|
signOut: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | null>(null)
|
const AuthContext = createContext<AuthContextType | null>(null)
|
||||||
@@ -29,17 +29,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const [authUser, setAuthUser] =
|
const [authUser, setAuthUser] =
|
||||||
useState<Auth.FetchUserAttributesOutput | null>(null)
|
useState<Auth.FetchUserAttributesOutput | null>(null)
|
||||||
|
|
||||||
const currentUser = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const currentUser = await Auth.fetchUserAttributes()
|
|
||||||
|
|
||||||
setAuthUser(currentUser)
|
|
||||||
return currentUser
|
|
||||||
} catch {
|
|
||||||
setAuthUser(null)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const signIn = useCallback(
|
const signIn = useCallback(
|
||||||
async ({
|
async ({
|
||||||
username,
|
username,
|
||||||
@@ -65,16 +54,22 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
|
||||||
const ctxValue = useMemo<AuthContextType>(
|
const signOut = useCallback(async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
return await Auth.signOut()
|
||||||
|
} catch {}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const authContext = useMemo<AuthContextType>(
|
||||||
() => ({
|
() => ({
|
||||||
authUser,
|
authUser,
|
||||||
signIn,
|
signIn,
|
||||||
currentUser,
|
signOut,
|
||||||
}),
|
}),
|
||||||
[authUser, signIn, currentUser],
|
[authUser, signIn, signOut],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={ctxValue}>{children}</AuthContext.Provider>
|
<AuthContext.Provider value={authContext}>{children}</AuthContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,47 @@
|
|||||||
import { useNavigation } from 'react-router'
|
import { useNavigation, redirect, useNavigate } from 'react-router'
|
||||||
|
import { fetchAuthSession } from 'aws-amplify/auth'
|
||||||
import { Link } from 'react-router'
|
import { Link } from 'react-router'
|
||||||
import { Outlet } from 'react-router'
|
import { Outlet } from 'react-router'
|
||||||
|
import { useAuth } from '~/hooks/use-auth'
|
||||||
|
|
||||||
|
export async function clientLoader() {
|
||||||
|
const session = await fetchAuthSession()
|
||||||
|
|
||||||
|
if (!session?.tokens?.idToken) {
|
||||||
|
throw redirect('/auth')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { signOut } = useAuth()
|
||||||
const isNavigating = Boolean(navigation.location)
|
const isNavigating = Boolean(navigation.location)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ul>
|
<nav>
|
||||||
<li>
|
<ul className="flex gap-1">
|
||||||
<Link to="/orders">Pagamentos</Link>
|
<li>
|
||||||
<Link to="/users">Usuários</Link>
|
<Link to="/orders">Pagamentos</Link>
|
||||||
<Link to="/enrollments">Matrículas</Link>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
</ul>
|
<Link to="/users">Usuários</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/enrollments">Matrículas</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
signOut()
|
||||||
|
navigate('/auth')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sair
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
{isNavigating ? <>Loading...</> : <Outlet />}
|
{isNavigating ? <>Loading...</> : <Outlet />}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -49,3 +49,17 @@ export default function Auth() {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Card({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<div className="space-y-2.5 xl:space-y-5 rounded-xl bg-yellow-50 dark:bg-gray-700/60 p-4 lg:p-8 drop-shadow-sm shadow-sm">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Container({ children }: { children: React.ReactNode }) {
|
||||||
|
return <div className="w-full 2xl:w-[26rem]">{children}</div>
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||||
import { Amplify } from 'aws-amplify'
|
import { Amplify } from 'aws-amplify'
|
||||||
import { AuthProvider } from '~/hooks/use-auth'
|
import { AuthProvider } from '~/hooks/use-auth'
|
||||||
|
import { Smallest as Logo } from '~/components/logo'
|
||||||
import amplifyconfig from './amplifyconfiguration.json'
|
import amplifyconfig from './amplifyconfiguration.json'
|
||||||
|
|
||||||
Amplify.configure(amplifyconfig)
|
Amplify.configure(amplifyconfig)
|
||||||
@@ -59,16 +60,7 @@ export default function App() {
|
|||||||
|
|
||||||
export function HydrateFallback() {
|
export function HydrateFallback() {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
<Logo className="w-14 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2" />
|
||||||
<div className="w-10 h-10 border-4 border-gray-300 border-t-4 border-t-blue-500 rounded-full animate-spin" />
|
|
||||||
|
|
||||||
<style>{`
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,35 @@
|
|||||||
|
import { useForm } from 'react-hook-form'
|
||||||
|
import { useAuth } from '~/hooks/use-auth'
|
||||||
|
import { Card } from '~/layouts/auth'
|
||||||
|
import { Control, Label, Input, Button } from '~/components/form'
|
||||||
|
import { useNavigate } from 'react-router'
|
||||||
|
|
||||||
export default function Signin() {
|
export default function Signin() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { register, handleSubmit, formState } = useForm()
|
||||||
|
const { signIn } = useAuth()
|
||||||
|
|
||||||
|
const onSubmit = async (payload) => {
|
||||||
|
const { nextStep } = await signIn(payload)
|
||||||
|
return navigate('/')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form>
|
<Card>
|
||||||
Username
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<input />
|
<Control>
|
||||||
</form>
|
<Label>Email ou CPF</Label>
|
||||||
|
<Input {...register('username')} />
|
||||||
|
</Control>
|
||||||
|
<Control>
|
||||||
|
<Label>Senha</Label>
|
||||||
|
<Input type="password" {...register('password')} />
|
||||||
|
</Control>
|
||||||
|
|
||||||
|
<Button type="submit" isLoading={formState.isSubmitting}>
|
||||||
|
Entrar
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
65
dashboard_js/package-lock.json
generated
65
dashboard_js/package-lock.json
generated
@@ -6,15 +6,21 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "dashboard_js",
|
"name": "dashboard_js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@heroicons/react": "^2.2.0",
|
||||||
|
"@hookform/error-message": "^2.0.1",
|
||||||
|
"@hookform/resolvers": "^4.1.0",
|
||||||
"@react-router/fs-routes": "^7.2.0",
|
"@react-router/fs-routes": "^7.2.0",
|
||||||
"@react-router/node": "^7.2.0",
|
"@react-router/node": "^7.2.0",
|
||||||
"@react-router/serve": "^7.2.0",
|
"@react-router/serve": "^7.2.0",
|
||||||
"@tanstack/react-query": "^5.66.9",
|
"@tanstack/react-query": "^5.66.9",
|
||||||
"aws-amplify": "^6.13.1",
|
"aws-amplify": "^6.13.1",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"isbot": "^5.1.17",
|
"isbot": "^5.1.17",
|
||||||
|
"ramda": "^0.30.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.54.2",
|
||||||
"react-router": "^7.2.0"
|
"react-router": "^7.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -2450,6 +2456,38 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@heroicons/react": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hookform/error-message": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hookform/error-message/-/error-message-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-U410sAr92xgxT1idlu9WWOVjndxLdgPUHEB8Schr27C9eh7/xUnITWpCMF93s+lGiG++D4JnbSnrb5A21AdSNg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0",
|
||||||
|
"react-hook-form": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@hookform/resolvers": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-fX/uHKb+OOCpACLc6enuTQsf0ZpRrKbeBBPETg5PCPLCIYV6osP2Bw6ezuclM61lH+wBF9eXcuC0+BFh9XOEnQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"caniuse-lite": "^1.0.30001698"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-hook-form": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@@ -5209,7 +5247,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -7649,6 +7686,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ramda": {
|
||||||
|
"version": "0.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz",
|
||||||
|
"integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/ramda"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/range-parser": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
@@ -7736,6 +7783,22 @@
|
|||||||
"react": "^19.0.0"
|
"react": "^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-hook-form": {
|
||||||
|
"version": "7.54.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.2.tgz",
|
||||||
|
"integrity": "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/react-hook-form"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-hotkeys-hook": {
|
"node_modules/react-hotkeys-hook": {
|
||||||
"version": "4.6.1",
|
"version": "4.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.6.1.tgz",
|
||||||
|
|||||||
@@ -9,15 +9,21 @@
|
|||||||
"typecheck": "react-router typegen && tsc"
|
"typecheck": "react-router typegen && tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@heroicons/react": "^2.2.0",
|
||||||
|
"@hookform/error-message": "^2.0.1",
|
||||||
|
"@hookform/resolvers": "^4.1.0",
|
||||||
"@react-router/fs-routes": "^7.2.0",
|
"@react-router/fs-routes": "^7.2.0",
|
||||||
"@react-router/node": "^7.2.0",
|
"@react-router/node": "^7.2.0",
|
||||||
"@react-router/serve": "^7.2.0",
|
"@react-router/serve": "^7.2.0",
|
||||||
"@tanstack/react-query": "^5.66.9",
|
"@tanstack/react-query": "^5.66.9",
|
||||||
"aws-amplify": "^6.13.1",
|
"aws-amplify": "^6.13.1",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"isbot": "^5.1.17",
|
"isbot": "^5.1.17",
|
||||||
|
"ramda": "^0.30.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.54.2",
|
||||||
"react-router": "^7.2.0"
|
"react-router": "^7.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Reference in New Issue
Block a user