From c2e8493ad78a26e9d2fb3fe696b6594233fa707c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 15:41:06 -0300 Subject: [PATCH 001/286] chore: create login page file --- src/features/home/pages/login.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/features/home/pages/login.tsx diff --git a/src/features/home/pages/login.tsx b/src/features/home/pages/login.tsx new file mode 100644 index 0000000..e69de29 From 7b7902606fe09119fffac8896c7ab147c3832f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 15:49:32 -0300 Subject: [PATCH 002/286] feat: add route to login page --- src/routes/app-routes.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/app-routes.tsx b/src/routes/app-routes.tsx index fc6264e..f82de37 100644 --- a/src/routes/app-routes.tsx +++ b/src/routes/app-routes.tsx @@ -1,4 +1,5 @@ import { HomePage } from '@/features/home/pages/home-page' +import { LoginPage } from '@/features/home/pages/login' import { Page } from '@/shared/components/page' import { Suspense } from 'react' import { Routes, Route } from 'react-router-dom' @@ -9,6 +10,7 @@ export function AppRoutes() { }> } /> + } /> From b2ae1acfd5824a57fdd7841a41d9aed29ae32602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 15:50:29 -0300 Subject: [PATCH 003/286] feat: add initial page structure --- src/features/home/pages/login.tsx | 68 +++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/features/home/pages/login.tsx b/src/features/home/pages/login.tsx index e69de29..82ecf76 100644 --- a/src/features/home/pages/login.tsx +++ b/src/features/home/pages/login.tsx @@ -0,0 +1,68 @@ +import { Background } from '@/shared/components/background' +import { Layout } from '@/shared/components/layout' +import { motion } from 'framer-motion' +import { Button } from '@/shared/components/button' +import { useState } from 'react' + +const transition = { duration: 1, ease: [0.25, 0.1, 0.25, 1] } +const variants = { + hidden: { filter: 'blur(10px)', transform: 'translateY(20%)', opacity: 0 }, + visible: { filter: 'blur(0)', transform: 'translateY(0)', opacity: 1 } +} + +export function LoginPage() { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + // Adicione aqui a lógica de autenticação + } + + return ( + + + + + Login to Knowly + + + setEmail(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + + + + + + ) +} \ No newline at end of file From b7743960d21dba4e22cfc5f36425c2a90762ab17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 15:52:56 -0300 Subject: [PATCH 004/286] style: translate to portuguese --- src/features/home/pages/login.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/home/pages/login.tsx b/src/features/home/pages/login.tsx index 82ecf76..9e48084 100644 --- a/src/features/home/pages/login.tsx +++ b/src/features/home/pages/login.tsx @@ -33,7 +33,7 @@ export function LoginPage() { transition={transition} variants={variants} > - Login to Knowly + Seja bem-vindo! setEmail(e.target.value)} @@ -51,7 +51,7 @@ export function LoginPage() { /> setPassword(e.target.value)} From 98d42b3ec3c1a0e782fe7bd1f1042800c0402a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 16:01:42 -0300 Subject: [PATCH 005/286] feat: add 'forgot my password' and 'register here' text --- src/features/home/pages/login.tsx | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/features/home/pages/login.tsx b/src/features/home/pages/login.tsx index 9e48084..9329fd8 100644 --- a/src/features/home/pages/login.tsx +++ b/src/features/home/pages/login.tsx @@ -49,17 +49,32 @@ export function LoginPage() { onChange={e => setEmail(e.target.value)} required /> - setPassword(e.target.value)} - required - /> +
+ setPassword(e.target.value)} + required + /> + + Esqueci minha senha + +
+
+ Ainda não tem uma conta?{' '} + + Cadastre-se aqui + +
From fae544ccfe305af17c8823e326b598943cb9ded6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 16:33:37 -0300 Subject: [PATCH 006/286] feat: add e-mail validation --- src/features/home/pages/login.tsx | 37 +++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/features/home/pages/login.tsx b/src/features/home/pages/login.tsx index 9329fd8..192ffa7 100644 --- a/src/features/home/pages/login.tsx +++ b/src/features/home/pages/login.tsx @@ -13,9 +13,19 @@ const variants = { export function LoginPage() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') + const [emailError, setEmailError] = useState('') + + const validateEmail = (value: string) => { + return /^(\S+)@((?:(?:(?!-)[a-zA-Z0-9-]{1,62}[a-zA-Z0-9])\.)+[a-zA-Z0-9]{2,12})$/.test(value) + } const handleSubmit = (e: React.FormEvent) => { e.preventDefault() + if (!validateEmail(email)) { + setEmailError('Digite um e-mail válido') + return + } + setEmailError('') // Adicione aqui a lógica de autenticação } @@ -40,15 +50,38 @@ export function LoginPage() { onSubmit={handleSubmit} transition={transition} variants={variants} + noValidate > setEmail(e.target.value)} + onChange={e => { + setEmail(e.target.value) + if (!validateEmail(e.target.value)) { + setEmailError('Digite um e-mail válido') + } + else { + setEmailError('') + } + }} + onBlur={e => { + setEmail(e.target.value) + if (!validateEmail(e.target.value)) { + setEmailError('Digite um e-mail válido') + } + else { + setEmailError('') + } + }} required /> + {emailError && ( + + {emailError} + + )}
Date: Sat, 24 May 2025 16:44:08 -0300 Subject: [PATCH 007/286] refactor: add error message variable and adjust error text position --- src/features/home/pages/login.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/features/home/pages/login.tsx b/src/features/home/pages/login.tsx index 192ffa7..42e117a 100644 --- a/src/features/home/pages/login.tsx +++ b/src/features/home/pages/login.tsx @@ -11,6 +11,8 @@ const variants = { } export function LoginPage() { + const EMAIL_ERROR_MESSAGE = 'Digite um e-mail válido' + const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [emailError, setEmailError] = useState('') @@ -22,7 +24,7 @@ export function LoginPage() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (!validateEmail(email)) { - setEmailError('Digite um e-mail válido') + setEmailError(EMAIL_ERROR_MESSAGE) return } setEmailError('') @@ -46,21 +48,22 @@ export function LoginPage() { Seja bem-vindo! +
{ setEmail(e.target.value) if (!validateEmail(e.target.value)) { - setEmailError('Digite um e-mail válido') + setEmailError(EMAIL_ERROR_MESSAGE) } else { setEmailError('') @@ -69,7 +72,7 @@ export function LoginPage() { onBlur={e => { setEmail(e.target.value) if (!validateEmail(e.target.value)) { - setEmailError('Digite um e-mail válido') + setEmailError(EMAIL_ERROR_MESSAGE) } else { setEmailError('') @@ -78,10 +81,11 @@ export function LoginPage() { required /> {emailError && ( - + {emailError} )} +
Date: Sat, 24 May 2025 16:51:38 -0300 Subject: [PATCH 008/286] feat: add navigation from home-page to login --- src/shared/components/navbar.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shared/components/navbar.tsx b/src/shared/components/navbar.tsx index d176064..0875d30 100644 --- a/src/shared/components/navbar.tsx +++ b/src/shared/components/navbar.tsx @@ -78,7 +78,9 @@ export function Navbar() { - +
From fa64354b2973c08c19c2bc4bfb91fd272d48485c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 17:07:12 -0300 Subject: [PATCH 009/286] feat: add uncensor option to password --- src/features/home/pages/login.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/features/home/pages/login.tsx b/src/features/home/pages/login.tsx index 42e117a..8152057 100644 --- a/src/features/home/pages/login.tsx +++ b/src/features/home/pages/login.tsx @@ -3,6 +3,7 @@ import { Layout } from '@/shared/components/layout' import { motion } from 'framer-motion' import { Button } from '@/shared/components/button' import { useState } from 'react' +import { Eye, EyeOff } from 'lucide-react' const transition = { duration: 1, ease: [0.25, 0.1, 0.25, 1] } const variants = { @@ -16,6 +17,7 @@ export function LoginPage() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [emailError, setEmailError] = useState('') + const [showPassword, setShowPassword] = useState(false) const validateEmail = (value: string) => { return /^(\S+)@((?:(?:(?!-)[a-zA-Z0-9-]{1,62}[a-zA-Z0-9])\.)+[a-zA-Z0-9]{2,12})$/.test(value) @@ -88,13 +90,26 @@ export function LoginPage() {
setPassword(e.target.value)} required /> + Date: Sat, 24 May 2025 17:21:59 -0300 Subject: [PATCH 010/286] feat: add login error message --- src/features/home/pages/login.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/features/home/pages/login.tsx b/src/features/home/pages/login.tsx index 8152057..7d05148 100644 --- a/src/features/home/pages/login.tsx +++ b/src/features/home/pages/login.tsx @@ -12,12 +12,14 @@ const variants = { } export function LoginPage() { - const EMAIL_ERROR_MESSAGE = 'Digite um e-mail válido' + const EMAIL_ERROR_MESSAGE = 'Digite um e-mail válido' + const LOGIN_ERROR_MESSAGE = 'Login ou senha inválidos' const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [emailError, setEmailError] = useState('') const [showPassword, setShowPassword] = useState(false) + const [authError, setAuthError] = useState('') const validateEmail = (value: string) => { return /^(\S+)@((?:(?:(?!-)[a-zA-Z0-9-]{1,62}[a-zA-Z0-9])\.)+[a-zA-Z0-9]{2,12})$/.test(value) @@ -30,6 +32,7 @@ export function LoginPage() { return } setEmailError('') + setAuthError(LOGIN_ERROR_MESSAGE) // Simulação de erro de autenticação // Adicione aqui a lógica de autenticação } @@ -56,6 +59,11 @@ export function LoginPage() { variants={variants} noValidate > + {authError && ( +
+ {authError} +
+ )}
Date: Sat, 24 May 2025 17:25:09 -0300 Subject: [PATCH 011/286] fix: navbar itens href --- src/shared/components/navbar.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shared/components/navbar.tsx b/src/shared/components/navbar.tsx index 0875d30..49cfb40 100644 --- a/src/shared/components/navbar.tsx +++ b/src/shared/components/navbar.tsx @@ -54,10 +54,10 @@ export function Navbar() { transition={transition} variants={variants} > - Our product - Pricing - FAQ - About us + Our product + Pricing + FAQ + About us {theme === 'dark' ? ( From b623176204c4b4c31be428e32156a27be36e182c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 17:51:08 -0300 Subject: [PATCH 012/286] chore: add forgot-password page file --- src/features/home/pages/forgot-password.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/features/home/pages/forgot-password.tsx diff --git a/src/features/home/pages/forgot-password.tsx b/src/features/home/pages/forgot-password.tsx new file mode 100644 index 0000000..e69de29 From f41521cd40f76aa0834ab808f8b47a5c7a1fbe36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 17:58:15 -0300 Subject: [PATCH 013/286] feat: add route to forgot-password page --- src/routes/app-routes.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/app-routes.tsx b/src/routes/app-routes.tsx index fc6264e..948db63 100644 --- a/src/routes/app-routes.tsx +++ b/src/routes/app-routes.tsx @@ -1,4 +1,5 @@ import { HomePage } from '@/features/home/pages/home-page' +import { ForgotPasswordPage } from '@/features/home/pages/forgot-password' import { Page } from '@/shared/components/page' import { Suspense } from 'react' import { Routes, Route } from 'react-router-dom' @@ -9,6 +10,7 @@ export function AppRoutes() { }> } /> + } /> From ff63845ff892717478a1d2a14fa6946978692c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 17:58:59 -0300 Subject: [PATCH 014/286] feat: add page initial structure --- src/features/home/pages/forgot-password.tsx | 111 ++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/src/features/home/pages/forgot-password.tsx b/src/features/home/pages/forgot-password.tsx index e69de29..a9b3351 100644 --- a/src/features/home/pages/forgot-password.tsx +++ b/src/features/home/pages/forgot-password.tsx @@ -0,0 +1,111 @@ +import { Background } from '@/shared/components/background' +import { Layout } from '@/shared/components/layout' +import { motion } from 'framer-motion' +import { Button } from '@/shared/components/button' +import { useState } from 'react' + +const transition = { duration: 1, ease: [0.25, 0.1, 0.25, 1] } +const variants = { + hidden: { filter: 'blur(10px)', transform: 'translateY(20%)', opacity: 0 }, + visible: { filter: 'blur(0)', transform: 'translateY(0)', opacity: 1 } +} + +export function ForgotPasswordPage() { + const EMAIL_ERROR_MESSAGE = 'Digite um e-mail válido' + + const [email, setEmail] = useState('') + const [emailError, setEmailError] = useState('') + const [successMessage, setSuccessMessage] = useState('') + + const validateEmail = (value: string) => { + return /^(\S+)@((?:(?:(?!-)[a-zA-Z0-9-]{1,62}[a-zA-Z0-9])\.)+[a-zA-Z0-9]{2,12})$/.test( + value + ) + } + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!validateEmail(email)) { + setEmailError(EMAIL_ERROR_MESSAGE) + setSuccessMessage('') + return + } + setEmailError('') + // Aqui você pode adicionar a lógica de envio de e-mail de recuperação + setSuccessMessage( + 'Se o e-mail estiver cadastrado, você receberá as instruções para redefinir sua senha.' + ) + } + + return ( + + + + + Esqueci minha senha + + + {successMessage && ( +
+ {successMessage} +
+ )} +
+ { + setEmail(e.target.value) + if (emailError && validateEmail(e.target.value)) + setEmailError('') + }} + onBlur={(e) => { + if (!validateEmail(e.target.value)) { + setEmailError(EMAIL_ERROR_MESSAGE) + } else { + setEmailError('') + } + }} + required + /> + {emailError && ( + + {emailError} + + )} +
+ +
+ Lembrou sua senha?{' '} + + Voltar para login + +
+
+
+
+
+ ) +} From 15abf5a999d66b30b47420a8488207025ef90018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Galhardi?= Date: Sat, 24 May 2025 18:04:22 -0300 Subject: [PATCH 015/286] style: change field text --- src/features/home/pages/forgot-password.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/features/home/pages/forgot-password.tsx b/src/features/home/pages/forgot-password.tsx index a9b3351..eea306d 100644 --- a/src/features/home/pages/forgot-password.tsx +++ b/src/features/home/pages/forgot-password.tsx @@ -12,7 +12,6 @@ const variants = { export function ForgotPasswordPage() { const EMAIL_ERROR_MESSAGE = 'Digite um e-mail válido' - const [email, setEmail] = useState('') const [emailError, setEmailError] = useState('') const [successMessage, setSuccessMessage] = useState('') @@ -68,7 +67,7 @@ export function ForgotPasswordPage() {
{ From 438853189b3d394bad217708846d128800462417 Mon Sep 17 00:00:00 2001 From: Izabel Sampaio Goes Date: Sat, 24 May 2025 18:46:56 -0300 Subject: [PATCH 016/286] feat: add path to user-info page using user ID --- src/routes/app-routes.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/app-routes.tsx b/src/routes/app-routes.tsx index fc6264e..d073c34 100644 --- a/src/routes/app-routes.tsx +++ b/src/routes/app-routes.tsx @@ -1,4 +1,5 @@ import { HomePage } from '@/features/home/pages/home-page' +import { UserInfoPage } from '@/features/home/pages/user-info' import { Page } from '@/shared/components/page' import { Suspense } from 'react' import { Routes, Route } from 'react-router-dom' @@ -9,6 +10,7 @@ export function AppRoutes() { }> } /> + } /> From 6c242d10c99c1b2ab90983a7af35acdc8f2841d2 Mon Sep 17 00:00:00 2001 From: Izabel Sampaio Goes Date: Sat, 24 May 2025 19:19:12 -0300 Subject: [PATCH 017/286] feat: add sidebar component --- src/shared/components/sidebar.tsx | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/shared/components/sidebar.tsx diff --git a/src/shared/components/sidebar.tsx b/src/shared/components/sidebar.tsx new file mode 100644 index 0000000..6e5360e --- /dev/null +++ b/src/shared/components/sidebar.tsx @@ -0,0 +1,52 @@ +import { Button } from '@/shared/components/button' +import { LogOut } from 'lucide-react' +import React from 'react' + +export type SidebarItem = { + label: string + icon: React.ReactNode + key: string +} + +type SidebarProps = { + items: SidebarItem[] + selected: string + setSelected: (key: string) => void + onLogout: () => void +} + +export function Sidebar({ + items, + selected, + setSelected, + onLogout +}: SidebarProps) { + return ( + + ) +} From 63dc807434f41701afeb3c21ed7a64c4b2efec7f Mon Sep 17 00:00:00 2001 From: Izabel Sampaio Goes Date: Sat, 24 May 2025 19:19:34 -0300 Subject: [PATCH 018/286] style: adjust text styles --- src/features/home/pages/user-info.tsx | 94 +++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/features/home/pages/user-info.tsx diff --git a/src/features/home/pages/user-info.tsx b/src/features/home/pages/user-info.tsx new file mode 100644 index 0000000..3367e4b --- /dev/null +++ b/src/features/home/pages/user-info.tsx @@ -0,0 +1,94 @@ +import { useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { Layout } from '@/shared/components/layout' +import { motion } from 'framer-motion' +import { Database, User } from 'lucide-react' +import { Sidebar, SidebarItem } from '@/shared/components/sidebar' +import { Button } from '@/shared/components/button' + +// Mock de usuários para testes +const mockUsers = [ + { id: '1', nome: 'Maria da Silva', email: 'maria@email.com' }, + { id: '2', nome: 'João Souza', email: 'joao@email.com' } +] + +const sidebarItems: SidebarItem[] = [ + { label: 'Dados de cadastro', icon: , key: 'dados' }, + { label: 'Minhas bases', icon: , key: 'bases' } +] + +export function UserInfoPage() { + const { userId } = useParams<{ userId: string }>() + const [selected, setSelected] = useState('dados') + const navigate = useNavigate() + + // Simula busca do usuário pelo id da URL + const user = mockUsers.find((u) => u.id === userId) || mockUsers[0] + + return ( + + navigate('/')} + /> +
+ + {selected === 'dados' && ( +
+

+ Dados de cadastro +

+
+
+ + +
+
+ + +
+ +
+
+ )} + {selected === 'bases' && ( +
+

+ Minhas bases +

+

+ Aqui você verá suas bases cadastradas. +

+ {/* Adicione aqui a listagem das bases do usuário */} +
+ )} +
+
+
+ ) +} + +export default UserInfoPage From ab179e93dc6e9b9140e43735dc679029afd02c7d Mon Sep 17 00:00:00 2001 From: Izabel Sampaio Goes Date: Sat, 24 May 2025 19:21:56 -0300 Subject: [PATCH 019/286] fix: adjust sidebar's height based on navebar --- src/shared/components/sidebar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/components/sidebar.tsx b/src/shared/components/sidebar.tsx index 6e5360e..064b08f 100644 --- a/src/shared/components/sidebar.tsx +++ b/src/shared/components/sidebar.tsx @@ -22,7 +22,7 @@ export function Sidebar({ onLogout }: SidebarProps) { return ( - ) -} \ No newline at end of file +} From c1739f8aaabd507531a4a0213e137ba3cf25d50b Mon Sep 17 00:00:00 2001 From: Izabel Sampaio Goes Date: Sat, 24 May 2025 19:36:30 -0300 Subject: [PATCH 021/286] style: change logout hover color --- src/shared/components/sidebar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/components/sidebar.tsx b/src/shared/components/sidebar.tsx index 7fe41df..4129129 100644 --- a/src/shared/components/sidebar.tsx +++ b/src/shared/components/sidebar.tsx @@ -22,7 +22,7 @@ export function Sidebar({ onLogout }: SidebarProps) { return ( -
- +
+ setEmail(e.target.value)} + /> + +
+ {editName ? ( + + ) : ( + + )}
@@ -81,23 +136,42 @@ export function UserInfoPage() {
setEmail(e.target.value)} + onChange={handleEmailChange} /> - + {editEmail ? ( + + ) : ( + + )}
- From ce0c490d885c06c838ef41bfc4bf7c2d4519056b Mon Sep 17 00:00:00 2001 From: Enzo Sakamoto Date: Mon, 26 May 2025 21:28:35 -0300 Subject: [PATCH 024/286] chore: add new libs --- package-lock.json | 665 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 12 +- 2 files changed, 670 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index b49ce14..058f16e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,14 @@ "name": "knowly", "version": "1.0.0", "dependencies": { + "@hookform/resolvers": "^5.0.1", "@lottiefiles/dotlottie-react": "^0.13.5", + "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dropdown-menu": "^2.1.14", - "@radix-ui/react-slot": "^1.2.2", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.5", "@tanstack/react-query": "^5.75.1", "axios": "^1.9.0", @@ -18,10 +23,13 @@ "lucide-react": "^0.511.0", "motion": "^12.10.1", "react": "^19.0.0", + "react-day-picker": "^9.4.3", "react-dom": "^19.0.0", + "react-hook-form": "^7.56.4", "react-icons": "^5.5.0", "react-router-dom": "^7.5.3", - "tailwindcss": "^4.1.5" + "tailwindcss": "^4.1.5", + "zod": "^3.25.28" }, "devDependencies": { "@tanstack/eslint-plugin-query": "^5.74.7", @@ -363,6 +371,12 @@ "node": ">=6.9.0" } }, + "node_modules/@date-fns/tz": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz", + "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", @@ -904,6 +918,18 @@ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, + "node_modules/@hookform/resolvers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz", + "integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1115,6 +1141,59 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.2.tgz", + "integrity": "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.6.tgz", @@ -1141,6 +1220,24 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz", + "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -1300,6 +1397,52 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.14", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.14.tgz", @@ -1340,6 +1483,215 @@ } } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz", + "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz", + "integrity": "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.6.tgz", @@ -1443,6 +1795,24 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz", + "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.9.tgz", @@ -1475,9 +1845,9 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz", - "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1492,6 +1862,169 @@ } } }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz", + "integrity": "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", @@ -1577,6 +2110,21 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", @@ -1613,6 +2161,52 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", @@ -1879,6 +2473,12 @@ "win32" ] }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.5.tgz", @@ -3053,6 +3653,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -5816,6 +6426,26 @@ "node": ">=0.10.0" } }, + "node_modules/react-day-picker": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.4.3.tgz", + "integrity": "sha512-kQmn7lBR6fHCjaDhW6ByzTMeZUgsBSvIKJLaUKq5xTuJcg2UqHiFAjrPLk/owQS/NzsnSgyGL/bhoi0Z7g+r3w==", + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.2.0", + "date-fns": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-dom": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", @@ -5828,6 +6458,22 @@ "react": "^19.1.0" } }, + "node_modules/react-hook-form": { + "version": "7.56.4", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.4.tgz", + "integrity": "sha512-Rob7Ftz2vyZ/ZGsQZPaRdIefkgOSrQSPXfqBdvOPwJfoGnjwRJUs7EM7Kc1mcoDv3NOtqBzPGbcMB8CGn9CKgw==", + "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-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -7134,6 +7780,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.28", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.28.tgz", + "integrity": "sha512-/nt/67WYKnr5by3YS7LroZJbtcCBurDKKPBPWWzaxvVCGuG/NOsiKkrjoOhI8mJ+SQUXEbUzeB3S+6XDUEEj7Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 119302a..49f6f30 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,14 @@ "preview": "vite preview" }, "dependencies": { + "@hookform/resolvers": "^5.0.1", "@lottiefiles/dotlottie-react": "^0.13.5", + "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dropdown-menu": "^2.1.14", - "@radix-ui/react-slot": "^1.2.2", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.5", "@tanstack/react-query": "^5.75.1", "axios": "^1.9.0", @@ -20,10 +25,13 @@ "lucide-react": "^0.511.0", "motion": "^12.10.1", "react": "^19.0.0", + "react-day-picker": "^9.4.3", "react-dom": "^19.0.0", + "react-hook-form": "^7.56.4", "react-icons": "^5.5.0", "react-router-dom": "^7.5.3", - "tailwindcss": "^4.1.5" + "tailwindcss": "^4.1.5", + "zod": "^3.25.28" }, "devDependencies": { "@tanstack/eslint-plugin-query": "^5.74.7", From 03ada90f7171a36a881d0309e1cb0ac1c217aaf9 Mon Sep 17 00:00:00 2001 From: Enzo Sakamoto Date: Mon, 26 May 2025 21:28:50 -0300 Subject: [PATCH 025/286] feat: add new components --- src/shared/components/calendar.tsx | 512 +++++++++++++++++++++++++++++ src/shared/components/checkbox.tsx | 27 ++ src/shared/components/form.tsx | 175 ++++++++++ src/shared/components/input.tsx | 21 ++ src/shared/components/label.tsx | 23 ++ src/shared/components/popover.tsx | 28 ++ src/shared/components/tooltip.tsx | 27 ++ 7 files changed, 813 insertions(+) create mode 100644 src/shared/components/calendar.tsx create mode 100644 src/shared/components/checkbox.tsx create mode 100644 src/shared/components/form.tsx create mode 100644 src/shared/components/label.tsx create mode 100644 src/shared/components/popover.tsx create mode 100644 src/shared/components/tooltip.tsx diff --git a/src/shared/components/calendar.tsx b/src/shared/components/calendar.tsx new file mode 100644 index 0000000..3269725 --- /dev/null +++ b/src/shared/components/calendar.tsx @@ -0,0 +1,512 @@ +import { Button, buttonVariants } from '@/shared/components/button' +import { cn } from '@/shared/utils/cn' +import { differenceInCalendarDays } from 'date-fns' +import { ChevronLeft, ChevronRight } from 'lucide-react' +import * as React from 'react' +import { + DayPicker, + labelNext, + labelPrevious, + useDayPicker, + type DayPickerProps +} from 'react-day-picker' + +export type CalendarProps = DayPickerProps & { + /** + * In the year view, the number of years to display at once. + * @default 12 + */ + yearRange?: number + + /** + * Wether to show the year switcher in the caption. + * @default true + */ + showYearSwitcher?: boolean + + monthsClassName?: string + monthCaptionClassName?: string + weekdaysClassName?: string + weekdayClassName?: string + monthClassName?: string + captionClassName?: string + captionLabelClassName?: string + buttonNextClassName?: string + buttonPreviousClassName?: string + navClassName?: string + monthGridClassName?: string + weekClassName?: string + dayClassName?: string + dayButtonClassName?: string + rangeStartClassName?: string + rangeEndClassName?: string + selectedClassName?: string + todayClassName?: string + outsideClassName?: string + disabledClassName?: string + rangeMiddleClassName?: string + hiddenClassName?: string +} + +type NavView = 'days' | 'years' + +/** + * A custom calendar component built on top of react-day-picker. + * @param props The props for the calendar. + * @default yearRange 12 + * @returns + */ +function Calendar({ + className, + showOutsideDays = true, + showYearSwitcher = true, + yearRange = 12, + numberOfMonths, + components, + ...props +}: CalendarProps) { + const [navView, setNavView] = React.useState('days') + const [displayYears, setDisplayYears] = React.useState<{ + from: number + to: number + }>( + React.useMemo(() => { + const currentYear = new Date().getFullYear() + return { + from: currentYear - Math.floor(yearRange / 2 - 1), + to: currentYear + Math.ceil(yearRange / 2) + } + }, [yearRange]) + ) + + const { onNextClick, onPrevClick, startMonth, endMonth } = props + + const columnsDisplayed = navView === 'years' ? 1 : numberOfMonths + + const _monthsClassName = cn('relative flex', props.monthsClassName) + const _monthCaptionClassName = cn( + 'relative mx-10 flex h-7 items-center justify-center', + props.monthCaptionClassName + ) + const _weekdaysClassName = cn('flex flex-row', props.weekdaysClassName) + const _weekdayClassName = cn( + 'w-8 text-sm font-normal text-muted-foreground', + props.weekdayClassName + ) + const _monthClassName = cn('w-full', props.monthClassName) + const _captionClassName = cn( + 'relative flex items-center justify-center pt-1', + props.captionClassName + ) + const _captionLabelClassName = cn( + 'truncate text-sm font-medium', + props.captionLabelClassName + ) + const buttonNavClassName = buttonVariants({ + variant: 'outline', + className: + 'absolute h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100' + }) + const _buttonNextClassName = cn( + buttonNavClassName, + 'right-0', + props.buttonNextClassName + ) + const _buttonPreviousClassName = cn( + buttonNavClassName, + 'left-0', + props.buttonPreviousClassName + ) + const _navClassName = cn('flex items-start', props.navClassName) + const _monthGridClassName = cn('mx-auto mt-4', props.monthGridClassName) + const _weekClassName = cn('mt-2 flex w-max items-start', props.weekClassName) + const _dayClassName = cn( + 'flex size-8 flex-1 items-center justify-center p-0 text-sm', + props.dayClassName + ) + const _dayButtonClassName = cn( + buttonVariants({ variant: 'ghost' }), + 'size-8 rounded-md p-0 font-normal transition-none aria-selected:opacity-100', + props.dayButtonClassName + ) + const buttonRangeClassName = + 'bg-accent [&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary [&>button]:hover:text-primary-foreground' + const _rangeStartClassName = cn( + buttonRangeClassName, + 'day-range-start rounded-s-md', + props.rangeStartClassName + ) + const _rangeEndClassName = cn( + buttonRangeClassName, + 'day-range-end rounded-e-md', + props.rangeEndClassName + ) + const _rangeMiddleClassName = cn( + 'bg-accent !text-foreground [&>button]:bg-transparent [&>button]:!text-foreground [&>button]:hover:bg-transparent [&>button]:hover:!text-foreground', + props.rangeMiddleClassName + ) + const _selectedClassName = cn( + '[&>button]:bg-primary [&>button]:text-primary-foreground [&>button]:hover:bg-primary [&>button]:hover:text-primary-foreground', + props.selectedClassName + ) + const _todayClassName = cn( + '[&>button]:bg-accent [&>button]:text-accent-foreground', + props.todayClassName + ) + const _outsideClassName = cn( + 'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30', + props.outsideClassName + ) + const _disabledClassName = cn( + 'text-muted-foreground opacity-50', + props.disabledClassName + ) + const _hiddenClassName = cn('invisible flex-1', props.hiddenClassName) + + return ( + { + const Icon = orientation === 'left' ? ChevronLeft : ChevronRight + return + }, + Nav: ({ className }) => ( +