update
This commit is contained in:
@@ -1,23 +1,45 @@
|
|||||||
import React, { forwardRef, useContext, useId } from 'react'
|
import type { ReactNode, ComponentPropsWithoutRef, ElementType } from 'react'
|
||||||
|
import {
|
||||||
|
createElement,
|
||||||
|
createContext,
|
||||||
|
forwardRef,
|
||||||
|
useContext,
|
||||||
|
useId,
|
||||||
|
} from 'react'
|
||||||
import { ExclamationCircleIcon } from '@heroicons/react/24/outline'
|
import { ExclamationCircleIcon } from '@heroicons/react/24/outline'
|
||||||
import { omit } from 'ramda'
|
import { omit } from 'ramda'
|
||||||
import Loader from './loader'
|
import { Loader } from './loader'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
|
||||||
const ControlContext = React.createContext({})
|
interface ControlContextProps {
|
||||||
|
id?: string
|
||||||
|
className?: string
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
function ControlProvider({ children, ...props }) {
|
const ControlContext = createContext<ControlContextProps>({})
|
||||||
|
|
||||||
|
interface ControlProviderProps extends ControlContextProps {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
function ControlProvider({ children, ...props }: ControlProviderProps) {
|
||||||
return (
|
return (
|
||||||
<ControlContext.Provider value={props}>{children}</ControlContext.Provider>
|
<ControlContext.Provider value={props}>{children}</ControlContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function useControl(props) {
|
function useControl<T extends ControlContextProps>(props?: T) {
|
||||||
const field = useContext(ControlContext)
|
const field = useContext(ControlContext)
|
||||||
return { ...field, ...props }
|
return { ...field, ...props } as T & ControlContextProps
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Control = forwardRef(function Control(
|
interface ControlProps extends ComponentPropsWithoutRef<'div'> {
|
||||||
|
as?: ElementType
|
||||||
|
children?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Control = forwardRef<HTMLElement, ControlProps>(function Control(
|
||||||
{ as = 'div', children, ...props },
|
{ as = 'div', children, ...props },
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
@@ -26,23 +48,29 @@ export const Control = forwardRef(function Control(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ControlProvider id={props?.id || id} {...props_}>
|
<ControlProvider id={props?.id || id} {...props_}>
|
||||||
{React.createElement(as, { ref, ...props }, children)}
|
{createElement(as, { ref, ...props }, children)}
|
||||||
</ControlProvider>
|
</ControlProvider>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
interface ButtonProps extends ComponentPropsWithoutRef<'button'> {
|
||||||
|
as?: ElementType
|
||||||
|
isLoading?: boolean
|
||||||
|
children?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
export function Button({
|
export function Button({
|
||||||
children,
|
children,
|
||||||
as = 'button',
|
as = 'button',
|
||||||
className,
|
className,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
...props
|
...props
|
||||||
}) {
|
}: ButtonProps) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
props['disabled'] = isLoading
|
props['disabled'] = isLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
return React.createElement(
|
return createElement(
|
||||||
as,
|
as,
|
||||||
{
|
{
|
||||||
className: clsx(
|
className: clsx(
|
||||||
@@ -53,20 +81,23 @@ export function Button({
|
|||||||
className,
|
className,
|
||||||
),
|
),
|
||||||
...props,
|
...props,
|
||||||
},
|
} as React.HTMLAttributes<HTMLElement>,
|
||||||
<>
|
<>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-green-secondary rounded-xl">
|
<div className="absolute inset-0 flex items-center justify-center bg-green-secondary rounded-xl">
|
||||||
<Loader className="w-5 text-white" />
|
<Loader className="w-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</>,
|
</>,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Label({ children, className, ...props }) {
|
interface LabelProps extends ComponentPropsWithoutRef<'label'> {
|
||||||
|
children?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Label({ children, className, ...props }: LabelProps) {
|
||||||
const { id, htmlFor, className: _, ...field } = useControl(props)
|
const { id, htmlFor, className: _, ...field } = useControl(props)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -83,21 +114,25 @@ export function Label({ children, className, ...props }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Input = forwardRef(function Input(
|
interface InputProps extends Omit<ComponentPropsWithoutRef<'input'>, 'size'> {
|
||||||
|
as?: ElementType
|
||||||
|
size?: 'base'
|
||||||
|
children?: ReactNode
|
||||||
|
}
|
||||||
|
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||||
{ as = 'input', size = 'base', className, children, ...props },
|
{ as = 'input', size = 'base', className, children, ...props },
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const { className: _, ...field } = useControl(props)
|
const { className: _, ...field } = useControl(props)
|
||||||
const sizes = { base: 'h-12' }
|
const sizes = { base: 'h-12' }
|
||||||
|
|
||||||
return React.createElement(
|
return createElement(
|
||||||
as,
|
as,
|
||||||
{
|
{
|
||||||
className: clsx(
|
className: clsx(
|
||||||
'bg-white outline-none px-4 rounded-lg transition',
|
'bg-white outline-none px-4 rounded-lg transition',
|
||||||
'border border-green-light dark:border-gray-700 dark:bg-gray-800',
|
'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',
|
'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',
|
'dark:focus:border-green-secondary',
|
||||||
'aria-[invalid=true]:border-red-400 aria-[invalid=true]:ring-red-400',
|
'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',
|
'dark:aria-[invalid=true]:border-red-500 dark:aria-[invalid=true]:ring-red-500',
|
||||||
@@ -108,34 +143,41 @@ export const Input = forwardRef(function Input(
|
|||||||
),
|
),
|
||||||
ref,
|
ref,
|
||||||
...field,
|
...field,
|
||||||
},
|
} as React.InputHTMLAttributes<HTMLInputElement>,
|
||||||
children,
|
children,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const Checkbox = forwardRef(function Checkbox(
|
interface CheckboxProps extends ComponentPropsWithoutRef<'input'> {
|
||||||
{ className, ...props },
|
className?: string
|
||||||
ref,
|
}
|
||||||
) {
|
|
||||||
const { className: _, ...field } = useControl(props)
|
|
||||||
|
|
||||||
return (
|
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||||
<input
|
function Checkbox({ className, ...props }, ref) {
|
||||||
type="checkbox"
|
const { className: _, ...field } = useControl(props)
|
||||||
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 (
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
interface ErrorProps {
|
||||||
|
children?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Error({ children }: ErrorProps) {
|
||||||
return (
|
return (
|
||||||
<div className="text-sm text-red-500 flex items-start gap-0.5">
|
<div className="text-sm text-red-500 flex items-start gap-0.5">
|
||||||
<ExclamationCircleIcon className="w-4 mt-[1.5px] flex-shrink-0" />
|
<ExclamationCircleIcon className="w-4 mt-[1.5px] flex-shrink-0" />
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
|
||||||
export default function Loader({ className, ...props }) {
|
export function Loader({ className, ...props }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
className={clsx('animate-spin', className)}
|
className={clsx('animate-spin', className)}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import React from 'react'
|
|
||||||
|
|
||||||
export function Regular({ ...props }) {
|
export function Regular({ ...props }) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import * as Auth from 'aws-amplify/auth'
|
import * as Auth from 'aws-amplify/auth'
|
||||||
|
|
||||||
interface AuthContextType {
|
export type AuthContextType = {
|
||||||
authUser: Auth.FetchUserAttributesOutput | null
|
authUser: Auth.FetchUserAttributesOutput | null
|
||||||
signIn: ({
|
signIn: ({
|
||||||
username,
|
username,
|
||||||
@@ -21,8 +21,14 @@ interface AuthContextType {
|
|||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | null>(null)
|
const AuthContext = createContext<AuthContextType | null>(null)
|
||||||
|
|
||||||
export function useAuth() {
|
export function useAuth(): AuthContextType {
|
||||||
return useContext(AuthContext)
|
const ctx = useContext(AuthContext)
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error('useAuth must be used within an AuthProvider')
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
|
import type { SubmitHandler } from 'react-hook-form'
|
||||||
|
import type { AuthContextType } from '~/hooks/use-auth'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import { useAuth } from '~/hooks/use-auth'
|
import { useAuth } from '~/hooks/use-auth'
|
||||||
import { Card } from '~/layouts/auth'
|
import { Card } from '~/layouts/auth'
|
||||||
import { Control, Label, Input, Button } from '~/components/form'
|
import { Control, Label, Input, Button } from '~/components/form'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router'
|
||||||
|
|
||||||
|
type Input = {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
export default function Signin() {
|
export default function Signin() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { register, handleSubmit, formState } = useForm()
|
const { register, handleSubmit, formState } = useForm<Input>()
|
||||||
const { signIn } = useAuth()
|
const { signIn } = useAuth()
|
||||||
|
|
||||||
const onSubmit = async (payload) => {
|
const onSubmit: SubmitHandler<Input> = async (data) => {
|
||||||
const { nextStep } = await signIn(payload)
|
const { nextStep } = await signIn(data)
|
||||||
return navigate('/')
|
return navigate('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
28
dashboard_js/package-lock.json
generated
28
dashboard_js/package-lock.json
generated
@@ -27,6 +27,7 @@
|
|||||||
"@react-router/dev": "^7.2.0",
|
"@react-router/dev": "^7.2.0",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
"@types/ramda": "^0.30.2",
|
||||||
"@types/react": "^19.0.1",
|
"@types/react": "^19.0.1",
|
||||||
"@types/react-dom": "^19.0.1",
|
"@types/react-dom": "^19.0.1",
|
||||||
"prettier": "^3.5.1",
|
"prettier": "^3.5.1",
|
||||||
@@ -4752,6 +4753,16 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ramda": {
|
||||||
|
"version": "0.30.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz",
|
||||||
|
"integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"types-ramda": "^0.30.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.0.10",
|
"version": "19.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
|
||||||
@@ -8610,6 +8621,13 @@
|
|||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-toolbelt": {
|
||||||
|
"version": "9.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz",
|
||||||
|
"integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/tsconfck": {
|
"node_modules/tsconfck": {
|
||||||
"version": "3.1.5",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz",
|
||||||
@@ -8663,6 +8681,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/types-ramda": {
|
||||||
|
"version": "0.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz",
|
||||||
|
"integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ts-toolbelt": "^9.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.7.3",
|
"version": "5.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"@react-router/dev": "^7.2.0",
|
"@react-router/dev": "^7.2.0",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
"@types/ramda": "^0.30.2",
|
||||||
"@types/react": "^19.0.1",
|
"@types/react": "^19.0.1",
|
||||||
"@types/react-dom": "^19.0.1",
|
"@types/react-dom": "^19.0.1",
|
||||||
"prettier": "^3.5.1",
|
"prettier": "^3.5.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user