### What problem does this PR solve? feat: Add next login page with shadcn/ui #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.14.0
| history: { | history: { | ||||
| type: 'browser', | type: 'browser', | ||||
| }, | }, | ||||
| plugins: ['@react-dev-inspector/umi4-plugin'], | |||||
| plugins: [ | |||||
| '@react-dev-inspector/umi4-plugin', | |||||
| '@umijs/plugins/dist/tailwindcss', | |||||
| ], | |||||
| jsMinifier: 'terser', | jsMinifier: 'terser', | ||||
| lessLoader: { | lessLoader: { | ||||
| modifyVars: { | modifyVars: { | ||||
| // pathRewrite: { '^/v1': '/v1' }, | // pathRewrite: { '^/v1': '/v1' }, | ||||
| }, | }, | ||||
| ], | ], | ||||
| chainWebpack(memo, args) { | chainWebpack(memo, args) { | ||||
| memo.module.rule('markdown').test(/\.md$/).type('asset/source'); | memo.module.rule('markdown').test(/\.md$/).type('asset/source'); | ||||
| return memo; | return memo; | ||||
| }, | }, | ||||
| tailwindcss: {}, | |||||
| }); | }); |
| "@ant-design/pro-components": "^2.6.46", | "@ant-design/pro-components": "^2.6.46", | ||||
| "@ant-design/pro-layout": "^7.17.16", | "@ant-design/pro-layout": "^7.17.16", | ||||
| "@antv/g6": "^5.0.10", | "@antv/g6": "^5.0.10", | ||||
| "@hookform/resolvers": "^3.9.1", | |||||
| "@js-preview/excel": "^1.7.8", | "@js-preview/excel": "^1.7.8", | ||||
| "@monaco-editor/react": "^4.6.0", | "@monaco-editor/react": "^4.6.0", | ||||
| "@radix-ui/react-checkbox": "^1.1.2", | |||||
| "@radix-ui/react-dropdown-menu": "^2.1.2", | |||||
| "@radix-ui/react-icons": "^1.3.1", | |||||
| "@radix-ui/react-label": "^2.1.0", | |||||
| "@radix-ui/react-select": "^2.1.2", | |||||
| "@radix-ui/react-separator": "^1.1.0", | |||||
| "@radix-ui/react-slot": "^1.1.0", | |||||
| "@radix-ui/react-switch": "^1.1.1", | |||||
| "@radix-ui/react-toast": "^1.2.2", | |||||
| "@tanstack/react-query": "^5.40.0", | "@tanstack/react-query": "^5.40.0", | ||||
| "@tanstack/react-query-devtools": "^5.51.5", | "@tanstack/react-query-devtools": "^5.51.5", | ||||
| "@uiw/react-markdown-preview": "^5.1.3", | "@uiw/react-markdown-preview": "^5.1.3", | ||||
| "ahooks": "^3.7.10", | "ahooks": "^3.7.10", | ||||
| "antd": "^5.12.7", | "antd": "^5.12.7", | ||||
| "axios": "^1.6.3", | "axios": "^1.6.3", | ||||
| "class-variance-authority": "^0.7.0", | |||||
| "classnames": "^2.5.1", | "classnames": "^2.5.1", | ||||
| "clsx": "^2.1.1", | |||||
| "dayjs": "^1.11.10", | "dayjs": "^1.11.10", | ||||
| "dompurify": "^3.1.6", | "dompurify": "^3.1.6", | ||||
| "eventsource-parser": "^1.1.2", | "eventsource-parser": "^1.1.2", | ||||
| "i18next": "^23.7.16", | "i18next": "^23.7.16", | ||||
| "i18next-browser-languagedetector": "^8.0.0", | "i18next-browser-languagedetector": "^8.0.0", | ||||
| "immer": "^10.1.1", | "immer": "^10.1.1", | ||||
| "input-otp": "^1.4.1", | |||||
| "js-base64": "^3.7.5", | "js-base64": "^3.7.5", | ||||
| "jsencrypt": "^3.3.2", | "jsencrypt": "^3.3.2", | ||||
| "lodash": "^4.17.21", | "lodash": "^4.17.21", | ||||
| "lucide-react": "^0.454.0", | |||||
| "mammoth": "^1.7.2", | "mammoth": "^1.7.2", | ||||
| "openai-speech-stream-player": "^1.0.8", | "openai-speech-stream-player": "^1.0.8", | ||||
| "rc-tween-one": "^3.0.6", | "rc-tween-one": "^3.0.6", | ||||
| "react-copy-to-clipboard": "^5.1.0", | "react-copy-to-clipboard": "^5.1.0", | ||||
| "react-error-boundary": "^4.0.13", | "react-error-boundary": "^4.0.13", | ||||
| "react-force-graph": "^1.44.4", | "react-force-graph": "^1.44.4", | ||||
| "react-hook-form": "^7.53.1", | |||||
| "react-i18next": "^14.0.0", | "react-i18next": "^14.0.0", | ||||
| "react-markdown": "^9.0.1", | "react-markdown": "^9.0.1", | ||||
| "react-pdf-highlighter": "^6.1.0", | "react-pdf-highlighter": "^6.1.0", | ||||
| "recharts": "^2.12.4", | "recharts": "^2.12.4", | ||||
| "rehype-raw": "^7.0.0", | "rehype-raw": "^7.0.0", | ||||
| "remark-gfm": "^4.0.0", | "remark-gfm": "^4.0.0", | ||||
| "tailwind-merge": "^2.5.4", | |||||
| "tailwindcss-animate": "^1.0.7", | |||||
| "umi": "^4.0.90", | "umi": "^4.0.90", | ||||
| "umi-request": "^1.4.0", | "umi-request": "^1.4.0", | ||||
| "unist-util-visit-parents": "^6.0.1", | "unist-util-visit-parents": "^6.0.1", | ||||
| "uuid": "^9.0.1", | "uuid": "^9.0.1", | ||||
| "zod": "^3.23.8", | |||||
| "zustand": "^4.5.2" | "zustand": "^4.5.2" | ||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { | ||||
| "prettier-plugin-packagejson": "^2.4.9", | "prettier-plugin-packagejson": "^2.4.9", | ||||
| "react-dev-inspector": "^2.0.1", | "react-dev-inspector": "^2.0.1", | ||||
| "remark-loader": "^6.0.0", | "remark-loader": "^6.0.0", | ||||
| "tailwindcss": "^3", | |||||
| "ts-node": "^10.9.2", | "ts-node": "^10.9.2", | ||||
| "typescript": "^5.0.3", | "typescript": "^5.0.3", | ||||
| "umi-plugin-icons": "^0.1.1" | "umi-plugin-icons": "^0.1.1" |
| import weekYear from 'dayjs/plugin/weekYear'; | import weekYear from 'dayjs/plugin/weekYear'; | ||||
| import weekday from 'dayjs/plugin/weekday'; | import weekday from 'dayjs/plugin/weekday'; | ||||
| import React, { ReactNode, useEffect, useState } from 'react'; | import React, { ReactNode, useEffect, useState } from 'react'; | ||||
| import { ThemeProvider } from './components/theme-provider'; | |||||
| import storage from './utils/authorization-util'; | import storage from './utils/authorization-util'; | ||||
| dayjs.extend(customParseFormat); | dayjs.extend(customParseFormat); | ||||
| return ( | return ( | ||||
| <QueryClientProvider client={queryClient}> | <QueryClientProvider client={queryClient}> | ||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| fontFamily: 'Inter', | |||||
| }, | |||||
| }} | |||||
| locale={locale} | |||||
| > | |||||
| <App> {children}</App> | |||||
| </ConfigProvider> | |||||
| <ReactQueryDevtools buttonPosition={'top-left'} /> | |||||
| <ThemeProvider defaultTheme="light" storageKey="ragflow-ui-theme"> | |||||
| <ConfigProvider | |||||
| theme={{ | |||||
| token: { | |||||
| fontFamily: 'Inter', | |||||
| }, | |||||
| }} | |||||
| locale={locale} | |||||
| > | |||||
| <App> {children}</App> | |||||
| </ConfigProvider> | |||||
| <ReactQueryDevtools buttonPosition={'top-left'} /> | |||||
| </ThemeProvider> | |||||
| </QueryClientProvider> | </QueryClientProvider> | ||||
| ); | ); | ||||
| }; | }; |
| 'use client'; | |||||
| // Inspired by react-hot-toast library | |||||
| import * as React from 'react'; | |||||
| import type { ToastActionElement, ToastProps } from '@/components/ui/toast'; | |||||
| const TOAST_LIMIT = 1; | |||||
| const TOAST_REMOVE_DELAY = 1000000; | |||||
| type ToasterToast = ToastProps & { | |||||
| id: string; | |||||
| title?: React.ReactNode; | |||||
| description?: React.ReactNode; | |||||
| action?: ToastActionElement; | |||||
| }; | |||||
| const actionTypes = { | |||||
| ADD_TOAST: 'ADD_TOAST', | |||||
| UPDATE_TOAST: 'UPDATE_TOAST', | |||||
| DISMISS_TOAST: 'DISMISS_TOAST', | |||||
| REMOVE_TOAST: 'REMOVE_TOAST', | |||||
| } as const; | |||||
| let count = 0; | |||||
| function genId() { | |||||
| count = (count + 1) % Number.MAX_SAFE_INTEGER; | |||||
| return count.toString(); | |||||
| } | |||||
| type ActionType = typeof actionTypes; | |||||
| type Action = | |||||
| | { | |||||
| type: ActionType['ADD_TOAST']; | |||||
| toast: ToasterToast; | |||||
| } | |||||
| | { | |||||
| type: ActionType['UPDATE_TOAST']; | |||||
| toast: Partial<ToasterToast>; | |||||
| } | |||||
| | { | |||||
| type: ActionType['DISMISS_TOAST']; | |||||
| toastId?: ToasterToast['id']; | |||||
| } | |||||
| | { | |||||
| type: ActionType['REMOVE_TOAST']; | |||||
| toastId?: ToasterToast['id']; | |||||
| }; | |||||
| interface State { | |||||
| toasts: ToasterToast[]; | |||||
| } | |||||
| const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>(); | |||||
| const addToRemoveQueue = (toastId: string) => { | |||||
| if (toastTimeouts.has(toastId)) { | |||||
| return; | |||||
| } | |||||
| const timeout = setTimeout(() => { | |||||
| toastTimeouts.delete(toastId); | |||||
| dispatch({ | |||||
| type: 'REMOVE_TOAST', | |||||
| toastId: toastId, | |||||
| }); | |||||
| }, TOAST_REMOVE_DELAY); | |||||
| toastTimeouts.set(toastId, timeout); | |||||
| }; | |||||
| export const reducer = (state: State, action: Action): State => { | |||||
| switch (action.type) { | |||||
| case 'ADD_TOAST': | |||||
| return { | |||||
| ...state, | |||||
| toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), | |||||
| }; | |||||
| case 'UPDATE_TOAST': | |||||
| return { | |||||
| ...state, | |||||
| toasts: state.toasts.map((t) => | |||||
| t.id === action.toast.id ? { ...t, ...action.toast } : t, | |||||
| ), | |||||
| }; | |||||
| case 'DISMISS_TOAST': { | |||||
| const { toastId } = action; | |||||
| // ! Side effects ! - This could be extracted into a dismissToast() action, | |||||
| // but I'll keep it here for simplicity | |||||
| if (toastId) { | |||||
| addToRemoveQueue(toastId); | |||||
| } else { | |||||
| state.toasts.forEach((toast) => { | |||||
| addToRemoveQueue(toast.id); | |||||
| }); | |||||
| } | |||||
| return { | |||||
| ...state, | |||||
| toasts: state.toasts.map((t) => | |||||
| t.id === toastId || toastId === undefined | |||||
| ? { | |||||
| ...t, | |||||
| open: false, | |||||
| } | |||||
| : t, | |||||
| ), | |||||
| }; | |||||
| } | |||||
| case 'REMOVE_TOAST': | |||||
| if (action.toastId === undefined) { | |||||
| return { | |||||
| ...state, | |||||
| toasts: [], | |||||
| }; | |||||
| } | |||||
| return { | |||||
| ...state, | |||||
| toasts: state.toasts.filter((t) => t.id !== action.toastId), | |||||
| }; | |||||
| } | |||||
| }; | |||||
| const listeners: Array<(state: State) => void> = []; | |||||
| let memoryState: State = { toasts: [] }; | |||||
| function dispatch(action: Action) { | |||||
| memoryState = reducer(memoryState, action); | |||||
| listeners.forEach((listener) => { | |||||
| listener(memoryState); | |||||
| }); | |||||
| } | |||||
| type Toast = Omit<ToasterToast, 'id'>; | |||||
| function toast({ ...props }: Toast) { | |||||
| const id = genId(); | |||||
| const update = (props: ToasterToast) => | |||||
| dispatch({ | |||||
| type: 'UPDATE_TOAST', | |||||
| toast: { ...props, id }, | |||||
| }); | |||||
| const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id }); | |||||
| dispatch({ | |||||
| type: 'ADD_TOAST', | |||||
| toast: { | |||||
| ...props, | |||||
| id, | |||||
| open: true, | |||||
| onOpenChange: (open) => { | |||||
| if (!open) dismiss(); | |||||
| }, | |||||
| }, | |||||
| }); | |||||
| return { | |||||
| id: id, | |||||
| dismiss, | |||||
| update, | |||||
| }; | |||||
| } | |||||
| function useToast() { | |||||
| const [state, setState] = React.useState<State>(memoryState); | |||||
| React.useEffect(() => { | |||||
| listeners.push(setState); | |||||
| return () => { | |||||
| const index = listeners.indexOf(setState); | |||||
| if (index > -1) { | |||||
| listeners.splice(index, 1); | |||||
| } | |||||
| }; | |||||
| }, [state]); | |||||
| return { | |||||
| ...state, | |||||
| toast, | |||||
| dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }), | |||||
| }; | |||||
| } | |||||
| export { toast, useToast }; |
| import { createContext, useContext, useEffect, useState } from 'react'; | |||||
| type Theme = 'dark' | 'light' | 'system'; | |||||
| type ThemeProviderProps = { | |||||
| children: React.ReactNode; | |||||
| defaultTheme?: Theme; | |||||
| storageKey?: string; | |||||
| }; | |||||
| type ThemeProviderState = { | |||||
| theme: Theme; | |||||
| setTheme: (theme: Theme) => void; | |||||
| }; | |||||
| const initialState: ThemeProviderState = { | |||||
| theme: 'system', | |||||
| setTheme: () => null, | |||||
| }; | |||||
| const ThemeProviderContext = createContext<ThemeProviderState>(initialState); | |||||
| export function ThemeProvider({ | |||||
| children, | |||||
| defaultTheme = 'system', | |||||
| storageKey = 'vite-ui-theme', | |||||
| ...props | |||||
| }: ThemeProviderProps) { | |||||
| const [theme, setTheme] = useState<Theme>( | |||||
| () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, | |||||
| ); | |||||
| useEffect(() => { | |||||
| const root = window.document.documentElement; | |||||
| root.classList.remove('light', 'dark'); | |||||
| if (theme === 'system') { | |||||
| const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') | |||||
| .matches | |||||
| ? 'dark' | |||||
| : 'light'; | |||||
| root.classList.add(systemTheme); | |||||
| return; | |||||
| } | |||||
| root.classList.add(theme); | |||||
| }, [theme]); | |||||
| const value = { | |||||
| theme, | |||||
| setTheme: (theme: Theme) => { | |||||
| localStorage.setItem(storageKey, theme); | |||||
| setTheme(theme); | |||||
| }, | |||||
| }; | |||||
| return ( | |||||
| <ThemeProviderContext.Provider {...props} value={value}> | |||||
| {children} | |||||
| </ThemeProviderContext.Provider> | |||||
| ); | |||||
| } | |||||
| export const useTheme = () => { | |||||
| const context = useContext(ThemeProviderContext); | |||||
| if (context === undefined) | |||||
| throw new Error('useTheme must be used within a ThemeProvider'); | |||||
| return context; | |||||
| }; |
| import { Slot } from '@radix-ui/react-slot'; | |||||
| import { cva, type VariantProps } from 'class-variance-authority'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const buttonVariants = cva( | |||||
| 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', | |||||
| { | |||||
| variants: { | |||||
| variant: { | |||||
| default: 'bg-primary text-primary-foreground hover:bg-primary/90', | |||||
| destructive: | |||||
| 'bg-destructive text-destructive-foreground hover:bg-destructive/90', | |||||
| outline: | |||||
| 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', | |||||
| secondary: | |||||
| 'bg-secondary text-secondary-foreground hover:bg-secondary/80', | |||||
| ghost: 'hover:bg-accent hover:text-accent-foreground', | |||||
| link: 'text-primary underline-offset-4 hover:underline', | |||||
| }, | |||||
| size: { | |||||
| default: 'h-10 px-4 py-2', | |||||
| sm: 'h-9 rounded-md px-3', | |||||
| lg: 'h-11 rounded-md px-8', | |||||
| icon: 'h-10 w-10', | |||||
| }, | |||||
| }, | |||||
| defaultVariants: { | |||||
| variant: 'default', | |||||
| size: 'default', | |||||
| }, | |||||
| }, | |||||
| ); | |||||
| export interface ButtonProps | |||||
| extends React.ButtonHTMLAttributes<HTMLButtonElement>, | |||||
| VariantProps<typeof buttonVariants> { | |||||
| asChild?: boolean; | |||||
| } | |||||
| const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( | |||||
| ({ className, variant, size, asChild = false, ...props }, ref) => { | |||||
| const Comp = asChild ? Slot : 'button'; | |||||
| return ( | |||||
| <Comp | |||||
| className={cn(buttonVariants({ variant, size, className }))} | |||||
| ref={ref} | |||||
| {...props} | |||||
| /> | |||||
| ); | |||||
| }, | |||||
| ); | |||||
| Button.displayName = 'Button'; | |||||
| export { Button, buttonVariants }; |
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const Card = React.forwardRef< | |||||
| HTMLDivElement, | |||||
| React.HTMLAttributes<HTMLDivElement> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <div | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'rounded-lg border bg-card text-card-foreground shadow-sm', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| Card.displayName = 'Card'; | |||||
| const CardHeader = React.forwardRef< | |||||
| HTMLDivElement, | |||||
| React.HTMLAttributes<HTMLDivElement> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <div | |||||
| ref={ref} | |||||
| className={cn('flex flex-col space-y-1.5 p-6', className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| CardHeader.displayName = 'CardHeader'; | |||||
| const CardTitle = React.forwardRef< | |||||
| HTMLDivElement, | |||||
| React.HTMLAttributes<HTMLDivElement> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <div | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'text-2xl font-semibold leading-none tracking-tight', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| CardTitle.displayName = 'CardTitle'; | |||||
| const CardDescription = React.forwardRef< | |||||
| HTMLDivElement, | |||||
| React.HTMLAttributes<HTMLDivElement> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <div | |||||
| ref={ref} | |||||
| className={cn('text-sm text-muted-foreground', className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| CardDescription.displayName = 'CardDescription'; | |||||
| const CardContent = React.forwardRef< | |||||
| HTMLDivElement, | |||||
| React.HTMLAttributes<HTMLDivElement> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <div ref={ref} className={cn('p-6 pt-0', className)} {...props} /> | |||||
| )); | |||||
| CardContent.displayName = 'CardContent'; | |||||
| const CardFooter = React.forwardRef< | |||||
| HTMLDivElement, | |||||
| React.HTMLAttributes<HTMLDivElement> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <div | |||||
| ref={ref} | |||||
| className={cn('flex items-center p-6 pt-0', className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| CardFooter.displayName = 'CardFooter'; | |||||
| export { | |||||
| Card, | |||||
| CardContent, | |||||
| CardDescription, | |||||
| CardFooter, | |||||
| CardHeader, | |||||
| CardTitle, | |||||
| }; |
| 'use client'; | |||||
| import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; | |||||
| import { Check } from 'lucide-react'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const Checkbox = React.forwardRef< | |||||
| React.ElementRef<typeof CheckboxPrimitive.Root>, | |||||
| React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <CheckboxPrimitive.Root | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| > | |||||
| <CheckboxPrimitive.Indicator | |||||
| className={cn('flex items-center justify-center text-current')} | |||||
| > | |||||
| <Check className="h-4 w-4" /> | |||||
| </CheckboxPrimitive.Indicator> | |||||
| </CheckboxPrimitive.Root> | |||||
| )); | |||||
| Checkbox.displayName = CheckboxPrimitive.Root.displayName; | |||||
| export { Checkbox }; |
| 'use client'; | |||||
| import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; | |||||
| import { Check, ChevronRight, Circle } from 'lucide-react'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const DropdownMenu = DropdownMenuPrimitive.Root; | |||||
| const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; | |||||
| const DropdownMenuGroup = DropdownMenuPrimitive.Group; | |||||
| const DropdownMenuPortal = DropdownMenuPrimitive.Portal; | |||||
| const DropdownMenuSub = DropdownMenuPrimitive.Sub; | |||||
| const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; | |||||
| const DropdownMenuSubTrigger = React.forwardRef< | |||||
| React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, | |||||
| React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { | |||||
| inset?: boolean; | |||||
| } | |||||
| >(({ className, inset, children, ...props }, ref) => ( | |||||
| <DropdownMenuPrimitive.SubTrigger | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', | |||||
| inset && 'pl-8', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| > | |||||
| {children} | |||||
| <ChevronRight className="ml-auto" /> | |||||
| </DropdownMenuPrimitive.SubTrigger> | |||||
| )); | |||||
| DropdownMenuSubTrigger.displayName = | |||||
| DropdownMenuPrimitive.SubTrigger.displayName; | |||||
| const DropdownMenuSubContent = React.forwardRef< | |||||
| React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, | |||||
| React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <DropdownMenuPrimitive.SubContent | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| DropdownMenuSubContent.displayName = | |||||
| DropdownMenuPrimitive.SubContent.displayName; | |||||
| const DropdownMenuContent = React.forwardRef< | |||||
| React.ElementRef<typeof DropdownMenuPrimitive.Content>, | |||||
| React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> | |||||
| >(({ className, sideOffset = 4, ...props }, ref) => ( | |||||
| <DropdownMenuPrimitive.Portal> | |||||
| <DropdownMenuPrimitive.Content | |||||
| ref={ref} | |||||
| sideOffset={sideOffset} | |||||
| className={cn( | |||||
| 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| </DropdownMenuPrimitive.Portal> | |||||
| )); | |||||
| DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; | |||||
| const DropdownMenuItem = React.forwardRef< | |||||
| React.ElementRef<typeof DropdownMenuPrimitive.Item>, | |||||
| React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { | |||||
| inset?: boolean; | |||||
| } | |||||
| >(({ className, inset, ...props }, ref) => ( | |||||
| <DropdownMenuPrimitive.Item | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', | |||||
| inset && 'pl-8', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; | |||||
| const DropdownMenuCheckboxItem = React.forwardRef< | |||||
| React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, | |||||
| React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> | |||||
| >(({ className, children, checked, ...props }, ref) => ( | |||||
| <DropdownMenuPrimitive.CheckboxItem | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', | |||||
| className, | |||||
| )} | |||||
| checked={checked} | |||||
| {...props} | |||||
| > | |||||
| <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> | |||||
| <DropdownMenuPrimitive.ItemIndicator> | |||||
| <Check className="h-4 w-4" /> | |||||
| </DropdownMenuPrimitive.ItemIndicator> | |||||
| </span> | |||||
| {children} | |||||
| </DropdownMenuPrimitive.CheckboxItem> | |||||
| )); | |||||
| DropdownMenuCheckboxItem.displayName = | |||||
| DropdownMenuPrimitive.CheckboxItem.displayName; | |||||
| const DropdownMenuRadioItem = React.forwardRef< | |||||
| React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, | |||||
| React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> | |||||
| >(({ className, children, ...props }, ref) => ( | |||||
| <DropdownMenuPrimitive.RadioItem | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| > | |||||
| <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> | |||||
| <DropdownMenuPrimitive.ItemIndicator> | |||||
| <Circle className="h-2 w-2 fill-current" /> | |||||
| </DropdownMenuPrimitive.ItemIndicator> | |||||
| </span> | |||||
| {children} | |||||
| </DropdownMenuPrimitive.RadioItem> | |||||
| )); | |||||
| DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; | |||||
| const DropdownMenuLabel = React.forwardRef< | |||||
| React.ElementRef<typeof DropdownMenuPrimitive.Label>, | |||||
| React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { | |||||
| inset?: boolean; | |||||
| } | |||||
| >(({ className, inset, ...props }, ref) => ( | |||||
| <DropdownMenuPrimitive.Label | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'px-2 py-1.5 text-sm font-semibold', | |||||
| inset && 'pl-8', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; | |||||
| const DropdownMenuSeparator = React.forwardRef< | |||||
| React.ElementRef<typeof DropdownMenuPrimitive.Separator>, | |||||
| React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <DropdownMenuPrimitive.Separator | |||||
| ref={ref} | |||||
| className={cn('-mx-1 my-1 h-px bg-muted', className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; | |||||
| const DropdownMenuShortcut = ({ | |||||
| className, | |||||
| ...props | |||||
| }: React.HTMLAttributes<HTMLSpanElement>) => { | |||||
| return ( | |||||
| <span | |||||
| className={cn('ml-auto text-xs tracking-widest opacity-60', className)} | |||||
| {...props} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; | |||||
| export { | |||||
| DropdownMenu, | |||||
| DropdownMenuCheckboxItem, | |||||
| DropdownMenuContent, | |||||
| DropdownMenuGroup, | |||||
| DropdownMenuItem, | |||||
| DropdownMenuLabel, | |||||
| DropdownMenuPortal, | |||||
| DropdownMenuRadioGroup, | |||||
| DropdownMenuRadioItem, | |||||
| DropdownMenuSeparator, | |||||
| DropdownMenuShortcut, | |||||
| DropdownMenuSub, | |||||
| DropdownMenuSubContent, | |||||
| DropdownMenuSubTrigger, | |||||
| DropdownMenuTrigger, | |||||
| }; |
| 'use client'; | |||||
| import * as LabelPrimitive from '@radix-ui/react-label'; | |||||
| import { Slot } from '@radix-ui/react-slot'; | |||||
| import * as React from 'react'; | |||||
| import { | |||||
| Controller, | |||||
| ControllerProps, | |||||
| FieldPath, | |||||
| FieldValues, | |||||
| FormProvider, | |||||
| useFormContext, | |||||
| } from 'react-hook-form'; | |||||
| import { Label } from '@/components/ui/label'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const Form = FormProvider; | |||||
| type FormItemContextValue = { | |||||
| id: string; | |||||
| }; | |||||
| const FormItemContext = React.createContext<FormItemContextValue>( | |||||
| {} as FormItemContextValue, | |||||
| ); | |||||
| type FormFieldContextValue< | |||||
| TFieldValues extends FieldValues = FieldValues, | |||||
| TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, | |||||
| > = { | |||||
| name: TName; | |||||
| }; | |||||
| const FormFieldContext = React.createContext<FormFieldContextValue>( | |||||
| {} as FormFieldContextValue, | |||||
| ); | |||||
| const FormField = < | |||||
| TFieldValues extends FieldValues = FieldValues, | |||||
| TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, | |||||
| >({ | |||||
| ...props | |||||
| }: ControllerProps<TFieldValues, TName>) => { | |||||
| return ( | |||||
| <FormFieldContext.Provider value={{ name: props.name }}> | |||||
| <Controller {...props} /> | |||||
| </FormFieldContext.Provider> | |||||
| ); | |||||
| }; | |||||
| const useFormField = () => { | |||||
| const fieldContext = React.useContext(FormFieldContext); | |||||
| const itemContext = React.useContext(FormItemContext); | |||||
| const { getFieldState, formState } = useFormContext(); | |||||
| const fieldState = getFieldState(fieldContext.name, formState); | |||||
| if (!fieldContext) { | |||||
| throw new Error('useFormField should be used within <FormField>'); | |||||
| } | |||||
| const { id } = itemContext; | |||||
| return { | |||||
| id, | |||||
| name: fieldContext.name, | |||||
| formItemId: `${id}-form-item`, | |||||
| formDescriptionId: `${id}-form-item-description`, | |||||
| formMessageId: `${id}-form-item-message`, | |||||
| ...fieldState, | |||||
| }; | |||||
| }; | |||||
| const FormItem = React.forwardRef< | |||||
| HTMLDivElement, | |||||
| React.HTMLAttributes<HTMLDivElement> | |||||
| >(({ className, ...props }, ref) => { | |||||
| const id = React.useId(); | |||||
| return ( | |||||
| <FormItemContext.Provider value={{ id }}> | |||||
| <div ref={ref} className={cn('space-y-2', className)} {...props} /> | |||||
| </FormItemContext.Provider> | |||||
| ); | |||||
| }); | |||||
| FormItem.displayName = 'FormItem'; | |||||
| const FormLabel = React.forwardRef< | |||||
| React.ElementRef<typeof LabelPrimitive.Root>, | |||||
| React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> | |||||
| >(({ className, ...props }, ref) => { | |||||
| const { error, formItemId } = useFormField(); | |||||
| return ( | |||||
| <Label | |||||
| ref={ref} | |||||
| className={cn(error && 'text-destructive', className)} | |||||
| htmlFor={formItemId} | |||||
| {...props} | |||||
| /> | |||||
| ); | |||||
| }); | |||||
| FormLabel.displayName = 'FormLabel'; | |||||
| const FormControl = React.forwardRef< | |||||
| React.ElementRef<typeof Slot>, | |||||
| React.ComponentPropsWithoutRef<typeof Slot> | |||||
| >(({ ...props }, ref) => { | |||||
| const { error, formItemId, formDescriptionId, formMessageId } = | |||||
| useFormField(); | |||||
| return ( | |||||
| <Slot | |||||
| ref={ref} | |||||
| id={formItemId} | |||||
| aria-describedby={ | |||||
| !error | |||||
| ? `${formDescriptionId}` | |||||
| : `${formDescriptionId} ${formMessageId}` | |||||
| } | |||||
| aria-invalid={!!error} | |||||
| {...props} | |||||
| /> | |||||
| ); | |||||
| }); | |||||
| FormControl.displayName = 'FormControl'; | |||||
| const FormDescription = React.forwardRef< | |||||
| HTMLParagraphElement, | |||||
| React.HTMLAttributes<HTMLParagraphElement> | |||||
| >(({ className, ...props }, ref) => { | |||||
| const { formDescriptionId } = useFormField(); | |||||
| return ( | |||||
| <p | |||||
| ref={ref} | |||||
| id={formDescriptionId} | |||||
| className={cn('text-sm text-muted-foreground', className)} | |||||
| {...props} | |||||
| /> | |||||
| ); | |||||
| }); | |||||
| FormDescription.displayName = 'FormDescription'; | |||||
| const FormMessage = React.forwardRef< | |||||
| HTMLParagraphElement, | |||||
| React.HTMLAttributes<HTMLParagraphElement> | |||||
| >(({ className, children, ...props }, ref) => { | |||||
| const { error, formMessageId } = useFormField(); | |||||
| const body = error ? String(error?.message) : children; | |||||
| if (!body) { | |||||
| return null; | |||||
| } | |||||
| return ( | |||||
| <p | |||||
| ref={ref} | |||||
| id={formMessageId} | |||||
| className={cn('text-sm font-medium text-destructive', className)} | |||||
| {...props} | |||||
| > | |||||
| {body} | |||||
| </p> | |||||
| ); | |||||
| }); | |||||
| FormMessage.displayName = 'FormMessage'; | |||||
| export { | |||||
| Form, | |||||
| FormControl, | |||||
| FormDescription, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| FormMessage, | |||||
| useFormField, | |||||
| }; |
| 'use client'; | |||||
| import { OTPInput, OTPInputContext } from 'input-otp'; | |||||
| import { Dot } from 'lucide-react'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const InputOTP = React.forwardRef< | |||||
| React.ElementRef<typeof OTPInput>, | |||||
| React.ComponentPropsWithoutRef<typeof OTPInput> | |||||
| >(({ className, containerClassName, ...props }, ref) => ( | |||||
| <OTPInput | |||||
| ref={ref} | |||||
| containerClassName={cn( | |||||
| 'flex items-center gap-2 has-[:disabled]:opacity-50', | |||||
| containerClassName, | |||||
| )} | |||||
| className={cn('disabled:cursor-not-allowed', className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| InputOTP.displayName = 'InputOTP'; | |||||
| const InputOTPGroup = React.forwardRef< | |||||
| React.ElementRef<'div'>, | |||||
| React.ComponentPropsWithoutRef<'div'> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <div ref={ref} className={cn('flex items-center', className)} {...props} /> | |||||
| )); | |||||
| InputOTPGroup.displayName = 'InputOTPGroup'; | |||||
| const InputOTPSlot = React.forwardRef< | |||||
| React.ElementRef<'div'>, | |||||
| React.ComponentPropsWithoutRef<'div'> & { index: number } | |||||
| >(({ index, className, ...props }, ref) => { | |||||
| const inputOTPContext = React.useContext(OTPInputContext); | |||||
| const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]; | |||||
| return ( | |||||
| <div | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md', | |||||
| isActive && 'z-10 ring-2 ring-ring ring-offset-background', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| > | |||||
| {char} | |||||
| {hasFakeCaret && ( | |||||
| <div className="pointer-events-none absolute inset-0 flex items-center justify-center"> | |||||
| <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" /> | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| }); | |||||
| InputOTPSlot.displayName = 'InputOTPSlot'; | |||||
| const InputOTPSeparator = React.forwardRef< | |||||
| React.ElementRef<'div'>, | |||||
| React.ComponentPropsWithoutRef<'div'> | |||||
| >(({ ...props }, ref) => ( | |||||
| <div ref={ref} role="separator" {...props}> | |||||
| <Dot /> | |||||
| </div> | |||||
| )); | |||||
| InputOTPSeparator.displayName = 'InputOTPSeparator'; | |||||
| export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot }; |
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| export interface InputProps | |||||
| extends React.InputHTMLAttributes<HTMLInputElement> {} | |||||
| const Input = React.forwardRef<HTMLInputElement, InputProps>( | |||||
| ({ className, type, ...props }, ref) => { | |||||
| return ( | |||||
| <input | |||||
| type={type} | |||||
| className={cn( | |||||
| 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', | |||||
| className, | |||||
| )} | |||||
| ref={ref} | |||||
| {...props} | |||||
| /> | |||||
| ); | |||||
| }, | |||||
| ); | |||||
| Input.displayName = 'Input'; | |||||
| export { Input }; |
| 'use client'; | |||||
| import * as LabelPrimitive from '@radix-ui/react-label'; | |||||
| import { cva, type VariantProps } from 'class-variance-authority'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const labelVariants = cva( | |||||
| 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', | |||||
| ); | |||||
| const Label = React.forwardRef< | |||||
| React.ElementRef<typeof LabelPrimitive.Root>, | |||||
| React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & | |||||
| VariantProps<typeof labelVariants> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <LabelPrimitive.Root | |||||
| ref={ref} | |||||
| className={cn(labelVariants(), className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| Label.displayName = LabelPrimitive.Root.displayName; | |||||
| export { Label }; |
| 'use client'; | |||||
| import * as SelectPrimitive from '@radix-ui/react-select'; | |||||
| import { Check, ChevronDown, ChevronUp } from 'lucide-react'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const Select = SelectPrimitive.Root; | |||||
| const SelectGroup = SelectPrimitive.Group; | |||||
| const SelectValue = SelectPrimitive.Value; | |||||
| const SelectTrigger = React.forwardRef< | |||||
| React.ElementRef<typeof SelectPrimitive.Trigger>, | |||||
| React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> | |||||
| >(({ className, children, ...props }, ref) => ( | |||||
| <SelectPrimitive.Trigger | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| > | |||||
| {children} | |||||
| <SelectPrimitive.Icon asChild> | |||||
| <ChevronDown className="h-4 w-4 opacity-50" /> | |||||
| </SelectPrimitive.Icon> | |||||
| </SelectPrimitive.Trigger> | |||||
| )); | |||||
| SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; | |||||
| const SelectScrollUpButton = React.forwardRef< | |||||
| React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, | |||||
| React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <SelectPrimitive.ScrollUpButton | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'flex cursor-default items-center justify-center py-1', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| > | |||||
| <ChevronUp className="h-4 w-4" /> | |||||
| </SelectPrimitive.ScrollUpButton> | |||||
| )); | |||||
| SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; | |||||
| const SelectScrollDownButton = React.forwardRef< | |||||
| React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, | |||||
| React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <SelectPrimitive.ScrollDownButton | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'flex cursor-default items-center justify-center py-1', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| > | |||||
| <ChevronDown className="h-4 w-4" /> | |||||
| </SelectPrimitive.ScrollDownButton> | |||||
| )); | |||||
| SelectScrollDownButton.displayName = | |||||
| SelectPrimitive.ScrollDownButton.displayName; | |||||
| const SelectContent = React.forwardRef< | |||||
| React.ElementRef<typeof SelectPrimitive.Content>, | |||||
| React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> | |||||
| >(({ className, children, position = 'popper', ...props }, ref) => ( | |||||
| <SelectPrimitive.Portal> | |||||
| <SelectPrimitive.Content | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', | |||||
| position === 'popper' && | |||||
| 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1', | |||||
| className, | |||||
| )} | |||||
| position={position} | |||||
| {...props} | |||||
| > | |||||
| <SelectScrollUpButton /> | |||||
| <SelectPrimitive.Viewport | |||||
| className={cn( | |||||
| 'p-1', | |||||
| position === 'popper' && | |||||
| 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]', | |||||
| )} | |||||
| > | |||||
| {children} | |||||
| </SelectPrimitive.Viewport> | |||||
| <SelectScrollDownButton /> | |||||
| </SelectPrimitive.Content> | |||||
| </SelectPrimitive.Portal> | |||||
| )); | |||||
| SelectContent.displayName = SelectPrimitive.Content.displayName; | |||||
| const SelectLabel = React.forwardRef< | |||||
| React.ElementRef<typeof SelectPrimitive.Label>, | |||||
| React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <SelectPrimitive.Label | |||||
| ref={ref} | |||||
| className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| SelectLabel.displayName = SelectPrimitive.Label.displayName; | |||||
| const SelectItem = React.forwardRef< | |||||
| React.ElementRef<typeof SelectPrimitive.Item>, | |||||
| React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> | |||||
| >(({ className, children, ...props }, ref) => ( | |||||
| <SelectPrimitive.Item | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| > | |||||
| <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> | |||||
| <SelectPrimitive.ItemIndicator> | |||||
| <Check className="h-4 w-4" /> | |||||
| </SelectPrimitive.ItemIndicator> | |||||
| </span> | |||||
| <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> | |||||
| </SelectPrimitive.Item> | |||||
| )); | |||||
| SelectItem.displayName = SelectPrimitive.Item.displayName; | |||||
| const SelectSeparator = React.forwardRef< | |||||
| React.ElementRef<typeof SelectPrimitive.Separator>, | |||||
| React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <SelectPrimitive.Separator | |||||
| ref={ref} | |||||
| className={cn('-mx-1 my-1 h-px bg-muted', className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| SelectSeparator.displayName = SelectPrimitive.Separator.displayName; | |||||
| export { | |||||
| Select, | |||||
| SelectContent, | |||||
| SelectGroup, | |||||
| SelectItem, | |||||
| SelectLabel, | |||||
| SelectScrollDownButton, | |||||
| SelectScrollUpButton, | |||||
| SelectSeparator, | |||||
| SelectTrigger, | |||||
| SelectValue, | |||||
| }; |
| 'use client'; | |||||
| import * as SeparatorPrimitive from '@radix-ui/react-separator'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const Separator = React.forwardRef< | |||||
| React.ElementRef<typeof SeparatorPrimitive.Root>, | |||||
| React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> | |||||
| >( | |||||
| ( | |||||
| { className, orientation = 'horizontal', decorative = true, ...props }, | |||||
| ref, | |||||
| ) => ( | |||||
| <SeparatorPrimitive.Root | |||||
| ref={ref} | |||||
| decorative={decorative} | |||||
| orientation={orientation} | |||||
| className={cn( | |||||
| 'shrink-0 bg-border', | |||||
| orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| ), | |||||
| ); | |||||
| Separator.displayName = SeparatorPrimitive.Root.displayName; | |||||
| export { Separator }; |
| 'use client'; | |||||
| import * as SwitchPrimitives from '@radix-ui/react-switch'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const Switch = React.forwardRef< | |||||
| React.ElementRef<typeof SwitchPrimitives.Root>, | |||||
| React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <SwitchPrimitives.Root | |||||
| className={cn( | |||||
| 'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| ref={ref} | |||||
| > | |||||
| <SwitchPrimitives.Thumb | |||||
| className={cn( | |||||
| 'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0', | |||||
| )} | |||||
| /> | |||||
| </SwitchPrimitives.Root> | |||||
| )); | |||||
| Switch.displayName = SwitchPrimitives.Root.displayName; | |||||
| export { Switch }; |
| 'use client'; | |||||
| import * as ToastPrimitives from '@radix-ui/react-toast'; | |||||
| import { cva, type VariantProps } from 'class-variance-authority'; | |||||
| import { X } from 'lucide-react'; | |||||
| import * as React from 'react'; | |||||
| import { cn } from '@/lib/utils'; | |||||
| const ToastProvider = ToastPrimitives.Provider; | |||||
| const ToastViewport = React.forwardRef< | |||||
| React.ElementRef<typeof ToastPrimitives.Viewport>, | |||||
| React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <ToastPrimitives.Viewport | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| ToastViewport.displayName = ToastPrimitives.Viewport.displayName; | |||||
| const toastVariants = cva( | |||||
| 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full', | |||||
| { | |||||
| variants: { | |||||
| variant: { | |||||
| default: 'border bg-background text-foreground', | |||||
| destructive: | |||||
| 'destructive group border-destructive bg-destructive text-destructive-foreground', | |||||
| }, | |||||
| }, | |||||
| defaultVariants: { | |||||
| variant: 'default', | |||||
| }, | |||||
| }, | |||||
| ); | |||||
| const Toast = React.forwardRef< | |||||
| React.ElementRef<typeof ToastPrimitives.Root>, | |||||
| React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & | |||||
| VariantProps<typeof toastVariants> | |||||
| >(({ className, variant, ...props }, ref) => { | |||||
| return ( | |||||
| <ToastPrimitives.Root | |||||
| ref={ref} | |||||
| className={cn(toastVariants({ variant }), className)} | |||||
| {...props} | |||||
| /> | |||||
| ); | |||||
| }); | |||||
| Toast.displayName = ToastPrimitives.Root.displayName; | |||||
| const ToastAction = React.forwardRef< | |||||
| React.ElementRef<typeof ToastPrimitives.Action>, | |||||
| React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <ToastPrimitives.Action | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive', | |||||
| className, | |||||
| )} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| ToastAction.displayName = ToastPrimitives.Action.displayName; | |||||
| const ToastClose = React.forwardRef< | |||||
| React.ElementRef<typeof ToastPrimitives.Close>, | |||||
| React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <ToastPrimitives.Close | |||||
| ref={ref} | |||||
| className={cn( | |||||
| 'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600', | |||||
| className, | |||||
| )} | |||||
| toast-close="" | |||||
| {...props} | |||||
| > | |||||
| <X className="h-4 w-4" /> | |||||
| </ToastPrimitives.Close> | |||||
| )); | |||||
| ToastClose.displayName = ToastPrimitives.Close.displayName; | |||||
| const ToastTitle = React.forwardRef< | |||||
| React.ElementRef<typeof ToastPrimitives.Title>, | |||||
| React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <ToastPrimitives.Title | |||||
| ref={ref} | |||||
| className={cn('text-sm font-semibold', className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| ToastTitle.displayName = ToastPrimitives.Title.displayName; | |||||
| const ToastDescription = React.forwardRef< | |||||
| React.ElementRef<typeof ToastPrimitives.Description>, | |||||
| React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description> | |||||
| >(({ className, ...props }, ref) => ( | |||||
| <ToastPrimitives.Description | |||||
| ref={ref} | |||||
| className={cn('text-sm opacity-90', className)} | |||||
| {...props} | |||||
| /> | |||||
| )); | |||||
| ToastDescription.displayName = ToastPrimitives.Description.displayName; | |||||
| type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>; | |||||
| type ToastActionElement = React.ReactElement<typeof ToastAction>; | |||||
| export { | |||||
| Toast, | |||||
| ToastAction, | |||||
| ToastClose, | |||||
| ToastDescription, | |||||
| ToastProvider, | |||||
| ToastTitle, | |||||
| ToastViewport, | |||||
| type ToastActionElement, | |||||
| type ToastProps, | |||||
| }; |
| 'use client'; | |||||
| import { useToast } from '@/components/hooks/use-toast'; | |||||
| import { | |||||
| Toast, | |||||
| ToastClose, | |||||
| ToastDescription, | |||||
| ToastProvider, | |||||
| ToastTitle, | |||||
| ToastViewport, | |||||
| } from '@/components/ui/toast'; | |||||
| export function Toaster() { | |||||
| const { toasts } = useToast(); | |||||
| return ( | |||||
| <ToastProvider> | |||||
| {toasts.map(function ({ id, title, description, action, ...props }: any) { | |||||
| return ( | |||||
| <Toast key={id} {...props}> | |||||
| <div className="grid gap-1"> | |||||
| {title && <ToastTitle>{title}</ToastTitle>} | |||||
| {description && ( | |||||
| <ToastDescription>{description}</ToastDescription> | |||||
| )} | |||||
| </div> | |||||
| {action} | |||||
| <ToastClose /> | |||||
| </Toast> | |||||
| ); | |||||
| })} | |||||
| <ToastViewport /> | |||||
| </ToastProvider> | |||||
| ); | |||||
| } |
| import { clsx, type ClassValue } from 'clsx'; | |||||
| import { twMerge } from 'tailwind-merge'; | |||||
| export function cn(...inputs: ClassValue[]) { | |||||
| return twMerge(clsx(inputs)); | |||||
| } |
| import { useTheme } from '@/components/theme-provider'; | |||||
| import { Button } from '@/components/ui/button'; | |||||
| import { | |||||
| DropdownMenu, | |||||
| DropdownMenuContent, | |||||
| DropdownMenuItem, | |||||
| DropdownMenuTrigger, | |||||
| } from '@/components/ui/dropdown-menu'; | |||||
| import { Moon, Sun } from 'lucide-react'; | |||||
| export function ModeToggle() { | |||||
| const { setTheme } = useTheme(); | |||||
| return ( | |||||
| <DropdownMenu> | |||||
| <DropdownMenuTrigger asChild> | |||||
| <Button variant="outline" size="icon"> | |||||
| <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /> | |||||
| <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /> | |||||
| <span className="sr-only">Toggle theme</span> | |||||
| </Button> | |||||
| </DropdownMenuTrigger> | |||||
| <DropdownMenuContent align="end"> | |||||
| <DropdownMenuItem onClick={() => setTheme('light')}> | |||||
| Light | |||||
| </DropdownMenuItem> | |||||
| <DropdownMenuItem onClick={() => setTheme('dark')}> | |||||
| Dark | |||||
| </DropdownMenuItem> | |||||
| <DropdownMenuItem onClick={() => setTheme('system')}> | |||||
| System | |||||
| </DropdownMenuItem> | |||||
| </DropdownMenuContent> | |||||
| </DropdownMenu> | |||||
| ); | |||||
| } | |||||
| const Demo = () => { | |||||
| return ( | |||||
| <div> | |||||
| <div> | |||||
| <ModeToggle></ModeToggle> | |||||
| </div> | |||||
| <Button>Destructive</Button> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default Demo; |
| 'use client'; | |||||
| import { toast } from '@/components/hooks/use-toast'; | |||||
| import { Button } from '@/components/ui/button'; | |||||
| import { Checkbox } from '@/components/ui/checkbox'; | |||||
| import { | |||||
| Form, | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| FormMessage, | |||||
| } from '@/components/ui/form'; | |||||
| import { Input } from '@/components/ui/input'; | |||||
| import { | |||||
| InputOTP, | |||||
| InputOTPGroup, | |||||
| InputOTPSlot, | |||||
| } from '@/components/ui/input-otp'; | |||||
| import { useTranslate } from '@/hooks/common-hooks'; | |||||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||||
| import { useForm } from 'react-hook-form'; | |||||
| import { z } from 'zod'; | |||||
| export function SignUpForm() { | |||||
| const { t } = useTranslate('login'); | |||||
| const FormSchema = z.object({ | |||||
| email: z.string().email({ | |||||
| message: t('emailPlaceholder'), | |||||
| }), | |||||
| nickname: z.string({ required_error: t('nicknamePlaceholder') }), | |||||
| password: z.string({ required_error: t('passwordPlaceholder') }), | |||||
| }); | |||||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||||
| resolver: zodResolver(FormSchema), | |||||
| defaultValues: { | |||||
| email: '', | |||||
| }, | |||||
| }); | |||||
| function onSubmit(data: z.infer<typeof FormSchema>) { | |||||
| console.log('🚀 ~ onSubmit ~ data:', data); | |||||
| toast({ | |||||
| title: 'You submitted the following values:', | |||||
| description: ( | |||||
| <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4"> | |||||
| <code className="text-white">{JSON.stringify(data, null, 2)}</code> | |||||
| </pre> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| return ( | |||||
| <Form {...form}> | |||||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="email" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('emailLabel')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input placeholder={t('emailPlaceholder')} {...field} /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="nickname" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('nicknameLabel')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input placeholder={t('nicknamePlaceholder')} {...field} /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="password" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('passwordLabel')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input | |||||
| type={'password'} | |||||
| placeholder={t('passwordPlaceholder')} | |||||
| {...field} | |||||
| /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <Button type="submit" className="w-full"> | |||||
| {t('signUp')} | |||||
| </Button> | |||||
| </form> | |||||
| </Form> | |||||
| ); | |||||
| } | |||||
| export function SignInForm() { | |||||
| const { t } = useTranslate('login'); | |||||
| const FormSchema = z.object({ | |||||
| email: z.string().email({ | |||||
| message: t('emailPlaceholder'), | |||||
| }), | |||||
| password: z.string({ required_error: t('passwordPlaceholder') }), | |||||
| }); | |||||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||||
| resolver: zodResolver(FormSchema), | |||||
| defaultValues: { | |||||
| email: '', | |||||
| }, | |||||
| }); | |||||
| function onSubmit(data: z.infer<typeof FormSchema>) { | |||||
| console.log('🚀 ~ onSubmit ~ data:', data); | |||||
| toast({ | |||||
| title: 'You submitted the following values:', | |||||
| description: ( | |||||
| <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4"> | |||||
| <code className="text-white">{JSON.stringify(data, null, 2)}</code> | |||||
| </pre> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| return ( | |||||
| <Form {...form}> | |||||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="email" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('emailLabel')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input placeholder={t('emailPlaceholder')} {...field} /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="password" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('passwordLabel')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input | |||||
| type={'password'} | |||||
| placeholder={t('passwordPlaceholder')} | |||||
| {...field} | |||||
| /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <div className="flex items-center space-x-2"> | |||||
| <Checkbox id="terms" /> | |||||
| <label | |||||
| htmlFor="terms" | |||||
| className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" | |||||
| > | |||||
| {t('rememberMe')} | |||||
| </label> | |||||
| </div> | |||||
| <Button type="submit" className="w-full"> | |||||
| {t('login')} | |||||
| </Button> | |||||
| </form> | |||||
| </Form> | |||||
| ); | |||||
| } | |||||
| export function VerifyEmailForm() { | |||||
| const FormSchema = z.object({ | |||||
| pin: z.string().min(6, { | |||||
| message: 'Your one-time password must be 6 characters.', | |||||
| }), | |||||
| }); | |||||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||||
| resolver: zodResolver(FormSchema), | |||||
| defaultValues: { | |||||
| pin: '', | |||||
| }, | |||||
| }); | |||||
| function onSubmit(data: z.infer<typeof FormSchema>) { | |||||
| console.log('🚀 ~ onSubmit ~ data:', data); | |||||
| toast({ | |||||
| title: 'You submitted the following values:', | |||||
| description: ( | |||||
| <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4"> | |||||
| <code className="text-white">{JSON.stringify(data, null, 2)}</code> | |||||
| </pre> | |||||
| ), | |||||
| }); | |||||
| } | |||||
| return ( | |||||
| <Form {...form}> | |||||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="pin" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>One-Time Password</FormLabel> | |||||
| <FormControl> | |||||
| <InputOTP maxLength={6} {...field}> | |||||
| <InputOTPGroup> | |||||
| <InputOTPSlot index={0} /> | |||||
| <InputOTPSlot index={1} /> | |||||
| <InputOTPSlot index={2} /> | |||||
| <InputOTPSlot index={3} /> | |||||
| <InputOTPSlot index={4} /> | |||||
| <InputOTPSlot index={5} /> | |||||
| </InputOTPGroup> | |||||
| </InputOTP> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <Button type="submit" className="w-full"> | |||||
| Verify | |||||
| </Button> | |||||
| </form> | |||||
| </Form> | |||||
| ); | |||||
| } |
| import { Button } from '@/components/ui/button'; | |||||
| import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | |||||
| import { Separator } from '@/components/ui/separator'; | |||||
| import { useTranslate } from '@/hooks/common-hooks'; | |||||
| import { DiscordLogoIcon, GitHubLogoIcon } from '@radix-ui/react-icons'; | |||||
| import { SignInForm, SignUpForm, VerifyEmailForm } from './form'; | |||||
| function LoginFooter() { | |||||
| return ( | |||||
| <section className="pt-[30px]"> | |||||
| <Separator /> | |||||
| <p className="text-center pt-[20px]">or continue with</p> | |||||
| <div className="flex gap-4 justify-center pt-[20px]"> | |||||
| <GitHubLogoIcon className="w-8 h-8"></GitHubLogoIcon> | |||||
| <DiscordLogoIcon className="w-8 h-8"></DiscordLogoIcon> | |||||
| </div> | |||||
| </section> | |||||
| ); | |||||
| } | |||||
| export function SignUpCard() { | |||||
| const { t } = useTranslate('login'); | |||||
| return ( | |||||
| <Card className="w-[400px]"> | |||||
| <CardHeader> | |||||
| <CardTitle>{t('signUp')}</CardTitle> | |||||
| </CardHeader> | |||||
| <CardContent> | |||||
| <SignUpForm></SignUpForm> | |||||
| <LoginFooter></LoginFooter> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ); | |||||
| } | |||||
| export function SignInCard() { | |||||
| const { t } = useTranslate('login'); | |||||
| return ( | |||||
| <Card className="w-[400px]"> | |||||
| <CardHeader> | |||||
| <CardTitle>{t('login')}</CardTitle> | |||||
| </CardHeader> | |||||
| <CardContent> | |||||
| <SignInForm></SignInForm> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ); | |||||
| } | |||||
| export function VerifyEmailCard() { | |||||
| // const { t } = useTranslate('login'); | |||||
| return ( | |||||
| <Card className="w-[400px]"> | |||||
| <CardHeader> | |||||
| <CardTitle>Verify email</CardTitle> | |||||
| </CardHeader> | |||||
| <CardContent> | |||||
| <section className="flex gap-y-6 flex-col"> | |||||
| <div className="flex items-center space-x-4"> | |||||
| <div className="flex-1 space-y-1"> | |||||
| <p className="text-sm font-medium leading-none"> | |||||
| We’ve sent a 6-digit code to | |||||
| </p> | |||||
| <p className="text-sm text-blue-500">yifanwu92@gmail.com.</p> | |||||
| </div> | |||||
| <Button>Resend</Button> | |||||
| </div> | |||||
| <VerifyEmailForm></VerifyEmailForm> | |||||
| </section> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ); | |||||
| } | |||||
| const Login = () => { | |||||
| return ( | |||||
| <> | |||||
| <SignUpCard></SignUpCard> | |||||
| <SignInCard></SignInCard> | |||||
| <VerifyEmailCard></VerifyEmailCard> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default Login; |
| component: '@/pages/login', | component: '@/pages/login', | ||||
| layout: false, | layout: false, | ||||
| }, | }, | ||||
| { | |||||
| path: '/login-next', | |||||
| component: '@/pages/login-next', | |||||
| layout: false, | |||||
| }, | |||||
| { | { | ||||
| path: '/chat/share', | path: '/chat/share', | ||||
| component: '@/pages/chat/share', | component: '@/pages/chat/share', | ||||
| component: '@/pages/404', | component: '@/pages/404', | ||||
| layout: false, | layout: false, | ||||
| }, | }, | ||||
| { | |||||
| path: '/demo', | |||||
| component: '@/pages/demo', | |||||
| layout: false, | |||||
| }, | |||||
| ]; | ]; | ||||
| export default routes; | export default routes; |
| const { fontFamily } = require('tailwindcss/defaultTheme'); | |||||
| /** @type {import('tailwindcss').Config} */ | |||||
| module.exports = { | |||||
| darkMode: ['class'], | |||||
| content: [ | |||||
| './src/pages/**/*.tsx', | |||||
| './src/components/**/*.tsx', | |||||
| './src/layouts/**/*.tsx', | |||||
| ], | |||||
| theme: { | |||||
| container: { | |||||
| center: true, | |||||
| padding: '2rem', | |||||
| screens: { | |||||
| '2xl': '1400px', | |||||
| }, | |||||
| }, | |||||
| extend: { | |||||
| colors: { | |||||
| border: 'hsl(var(--border))', | |||||
| input: 'hsl(var(--input))', | |||||
| ring: 'hsl(var(--ring))', | |||||
| background: 'hsl(var(--background))', | |||||
| foreground: 'hsl(var(--foreground))', | |||||
| primary: { | |||||
| DEFAULT: 'hsl(var(--primary))', | |||||
| foreground: 'hsl(var(--primary-foreground))', | |||||
| }, | |||||
| secondary: { | |||||
| DEFAULT: 'hsl(var(--secondary))', | |||||
| foreground: 'hsl(var(--secondary-foreground))', | |||||
| }, | |||||
| destructive: { | |||||
| DEFAULT: 'hsl(var(--destructive))', | |||||
| foreground: 'hsl(var(--destructive-foreground))', | |||||
| }, | |||||
| muted: { | |||||
| DEFAULT: 'hsl(var(--muted))', | |||||
| foreground: 'hsl(var(--muted-foreground))', | |||||
| }, | |||||
| accent: { | |||||
| DEFAULT: 'hsl(var(--accent))', | |||||
| foreground: 'hsl(var(--accent-foreground))', | |||||
| }, | |||||
| popover: { | |||||
| DEFAULT: 'hsl(var(--popover))', | |||||
| foreground: 'hsl(var(--popover-foreground))', | |||||
| }, | |||||
| card: { | |||||
| DEFAULT: 'hsl(var(--card))', | |||||
| foreground: 'hsl(var(--card-foreground))', | |||||
| }, | |||||
| }, | |||||
| borderRadius: { | |||||
| lg: `var(--radius)`, | |||||
| md: `calc(var(--radius) - 2px)`, | |||||
| sm: 'calc(var(--radius) - 4px)', | |||||
| }, | |||||
| fontFamily: { | |||||
| sans: ['var(--font-sans)', ...fontFamily.sans], | |||||
| }, | |||||
| keyframes: { | |||||
| 'accordion-down': { | |||||
| from: { height: '0' }, | |||||
| to: { height: 'var(--radix-accordion-content-height)' }, | |||||
| }, | |||||
| 'accordion-up': { | |||||
| from: { height: 'var(--radix-accordion-content-height)' }, | |||||
| to: { height: '0' }, | |||||
| }, | |||||
| 'caret-blink': { | |||||
| '0%,70%,100%': { opacity: '1' }, | |||||
| '20%,50%': { opacity: '0' }, | |||||
| }, | |||||
| }, | |||||
| animation: { | |||||
| 'accordion-down': 'accordion-down 0.2s ease-out', | |||||
| 'accordion-up': 'accordion-up 0.2s ease-out', | |||||
| 'caret-blink': 'caret-blink 1.25s ease-out infinite', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| plugins: [require('tailwindcss-animate')], | |||||
| }; |
| @tailwind base; | |||||
| @tailwind components; | |||||
| @tailwind utilities; | |||||
| @layer base { | |||||
| :root { | |||||
| --background: 0 0% 100%; | |||||
| --foreground: 222.2 47.4% 11.2%; | |||||
| --muted: 210 40% 96.1%; | |||||
| --muted-foreground: 215.4 16.3% 46.9%; | |||||
| --popover: 0 0% 100%; | |||||
| --popover-foreground: 222.2 47.4% 11.2%; | |||||
| --border: 214.3 31.8% 91.4%; | |||||
| --input: 214.3 31.8% 91.4%; | |||||
| --card: 0 0% 100%; | |||||
| --card-foreground: 222.2 47.4% 11.2%; | |||||
| --primary: 222.2 47.4% 11.2%; | |||||
| --primary-foreground: 210 40% 98%; | |||||
| --secondary: 210 40% 96.1%; | |||||
| --secondary-foreground: 222.2 47.4% 11.2%; | |||||
| --accent: 210 40% 96.1%; | |||||
| --accent-foreground: 222.2 47.4% 11.2%; | |||||
| --destructive: 0 100% 50%; | |||||
| --destructive-foreground: 210 40% 98%; | |||||
| --ring: 215 20.2% 65.1%; | |||||
| --radius: 0.5rem; | |||||
| } | |||||
| .dark { | |||||
| --background: 224 71% 4%; | |||||
| --foreground: 213 31% 91%; | |||||
| --muted: 223 47% 11%; | |||||
| --muted-foreground: 215.4 16.3% 56.9%; | |||||
| --accent: 216 34% 17%; | |||||
| --accent-foreground: 210 40% 98%; | |||||
| --popover: 224 71% 4%; | |||||
| --popover-foreground: 215 20.2% 65.1%; | |||||
| --border: 216 34% 17%; | |||||
| --input: 216 34% 17%; | |||||
| --card: 224 71% 4%; | |||||
| --card-foreground: 213 31% 91%; | |||||
| --primary: 210 40% 98%; | |||||
| --primary-foreground: 222.2 47.4% 1.2%; | |||||
| --secondary: 222.2 47.4% 11.2%; | |||||
| --secondary-foreground: 210 40% 98%; | |||||
| --destructive: 0 63% 31%; | |||||
| --destructive-foreground: 210 40% 98%; | |||||
| --ring: 216 34% 17%; | |||||
| --radius: 0.5rem; | |||||
| } | |||||
| } | |||||
| @layer base { | |||||
| * { | |||||
| @apply border-border; | |||||
| } | |||||
| body { | |||||
| @apply bg-background text-foreground; | |||||
| font-feature-settings: | |||||
| 'rlig' 1, | |||||
| 'calt' 1; | |||||
| } | |||||
| } |