diff --git a/apps/admin.saladeaula.digital/app/components/data-table.tsx b/apps/admin.saladeaula.digital/app/components/data-table.tsx index 3c09d75..0a538c5 100644 --- a/apps/admin.saladeaula.digital/app/components/data-table.tsx +++ b/apps/admin.saladeaula.digital/app/components/data-table.tsx @@ -75,11 +75,15 @@ export function DataTable({ ) const [columnVisibility, setColumnVisibility] = useState(hiddenColumn_) + const [rowSelection, setRowSelection] = useState({}) + const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), + onRowSelectionChange: setRowSelection, state: { + rowSelection, columnVisibility, pagination: { pageIndex, diff --git a/apps/admin.saladeaula.digital/app/components/nav-main.tsx b/apps/admin.saladeaula.digital/app/components/nav-main.tsx index 8097b84..8edd5bf 100644 --- a/apps/admin.saladeaula.digital/app/components/nav-main.tsx +++ b/apps/admin.saladeaula.digital/app/components/nav-main.tsx @@ -10,6 +10,7 @@ import { useSidebar } from '@repo/ui/components/ui/sidebar' import { useIsMobile } from '@repo/ui/hooks/use-mobile' + import { type LucideIcon } from 'lucide-react' import { NavLink, useParams } from 'react-router' diff --git a/apps/admin.saladeaula.digital/app/components/org-switcher.tsx b/apps/admin.saladeaula.digital/app/components/org-switcher.tsx index d7d4424..5cbc35a 100644 --- a/apps/admin.saladeaula.digital/app/components/org-switcher.tsx +++ b/apps/admin.saladeaula.digital/app/components/org-switcher.tsx @@ -21,7 +21,6 @@ import { SidebarRail, useSidebar } from '@repo/ui/components/ui/sidebar' - import { initials } from '@repo/ui/lib/utils' type Org = { diff --git a/apps/admin.saladeaula.digital/app/context.ts b/apps/admin.saladeaula.digital/app/context.ts deleted file mode 100644 index 0f2b509..0000000 --- a/apps/admin.saladeaula.digital/app/context.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { User } from '@/lib/auth' -import { createContext } from 'react-router' - -export const userContext = createContext(null) -export const requestIdContext = createContext(null) diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.courses._index/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.courses._index/route.tsx index 5968693..d1033ed 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.courses._index/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.courses._index/route.tsx @@ -92,8 +92,8 @@ export default function Route({ loaderData: { data } }) { - Pressione / para - filtrar... + Digite / para + pesquisar } defaultValue={term} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments._index/columns.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments._index/columns.tsx index 3f68f01..9cedb20 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments._index/columns.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments._index/columns.tsx @@ -13,6 +13,7 @@ import { import { Avatar, AvatarFallback } from '@repo/ui/components/ui/avatar' import { Badge } from '@repo/ui/components/ui/badge' +import { Checkbox } from '@repo/ui/components/ui/checkbox' import { Progress } from '@repo/ui/components/ui/progress' import { cn, initials } from '@repo/ui/lib/utils' @@ -83,6 +84,26 @@ const statusTranslate: Record = { } export const columns: ColumnDef[] = [ + { + id: 'select', + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Selecionar tudo" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Selecionar linha" + /> + ) + }, { header: 'Colaborador', enableHiding: false, @@ -145,30 +166,35 @@ export const columns: ColumnDef[] = [ { accessorKey: 'created_at', header: 'Matriculado em', + enableSorting: true, enableHiding: true, cell: cellDate }, { accessorKey: 'started_at', header: 'Iniciado em', + enableSorting: true, enableHiding: true, cell: cellDate }, { accessorKey: 'completed_at', header: 'Aprovado em', + enableSorting: true, enableHiding: true, cell: cellDate }, { accessorKey: 'failed_at', header: 'Reprovado em', + enableSorting: true, enableHiding: true, cell: cellDate }, { accessorKey: 'canceled_at', header: 'Cancelado em', + enableSorting: true, enableHiding: true, cell: cellDate } diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments._index/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments._index/route.tsx index 341c5e4..3fe4e8a 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments._index/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments._index/route.tsx @@ -113,8 +113,8 @@ export default function Route({ loaderData: { data } }) { defaultValue={searchParams.get('q') || ''} placeholder={ <> - Pressione / para - pesquisar... + Digite / para + pesquisar } onChange={(value) => diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.users._index/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.users._index/route.tsx index 714eead..a17ccfd 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.users._index/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.users._index/route.tsx @@ -72,8 +72,8 @@ export default function Route({ loaderData: { data } }) { - Pressione / para - pesquisar... + Digite / para + pesquisar } defaultValue={searchParams.get('q') || ''} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid/route.tsx index 4acaa7d..da283a9 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid/route.tsx @@ -1,6 +1,10 @@ import type { Route } from './+types' -import { Outlet, type ShouldRevalidateFunctionArgs } from 'react-router' +import { + createCookie, + Outlet, + type ShouldRevalidateFunctionArgs +} from 'react-router' import { AppSidebar } from '@/components/app-sidebar' import { request as req } from '@/lib/request' @@ -14,13 +18,14 @@ import { SidebarProvider, SidebarTrigger } from '@repo/ui/components/ui/sidebar' -import { useIsMobile } from '@repo/ui/hooks/use-mobile' export const middleware: Route.MiddlewareFunction[] = [authMiddleware] export async function loader({ params, context, request }: Route.ActionArgs) { + const rawCookie = request.headers.get('cookie') const user = context.get(userContext) + const sidebarState = await createCookie('sidebar_state').parse(rawCookie) const r = await req({ url: `/users/${user.sub}/orgs?limit=25`, request, @@ -39,7 +44,7 @@ export async function loader({ params, context, request }: Route.ActionArgs) { const exists = orgs.some(({ id }) => id === params.orgid) if (exists) { - return { user, orgs } + return { user, orgs, sidebarState } } throw new Response(null, { status: 401 }) @@ -52,12 +57,12 @@ export function shouldRevalidate({ return currentParams.orgid !== nextParams.orgid } -export default function Layout({ loaderData }: Route.ComponentProps) { - const { user, orgs } = loaderData - const isMobile = useIsMobile() +export default function Route({ loaderData }: Route.ComponentProps) { + const { user, orgs, sidebarState } = loaderData + console.log(sidebarState) return ( - + @@ -66,7 +71,8 @@ export default function Layout({ loaderData }: Route.ComponentProps) { px-4 py-2 lg:py-4 sticky top-0 z-5" >
- {isMobile ? : } + +
diff --git a/apps/admin.saladeaula.digital/package.json b/apps/admin.saladeaula.digital/package.json index b12719b..c962c54 100644 --- a/apps/admin.saladeaula.digital/package.json +++ b/apps/admin.saladeaula.digital/package.json @@ -13,8 +13,8 @@ }, "dependencies": { "@react-router/fs-routes": "^7.9.5", - "@repo/ui": "*", "@repo/auth": "*", + "@repo/ui": "*", "@tanstack/react-table": "^8.21.3", "date-fns": "^4.1.0", "fuse.js": "^7.1.0", diff --git a/apps/saladeaula.digital/app/components/search-form.tsx b/apps/saladeaula.digital/app/components/search-form.tsx index 8bac56e..d3f8461 100644 --- a/apps/saladeaula.digital/app/components/search-form.tsx +++ b/apps/saladeaula.digital/app/components/search-form.tsx @@ -1,9 +1,9 @@ +import { useKeyPress } from '@/hooks/use-keypress' import { InputGroup, InputGroupAddon, InputGroupInput } from '@repo/ui/components/ui/input-group' -import { useKeyPress } from '@/hooks/use-keypress' import clsx from 'clsx' import { debounce } from 'lodash' import { SearchIcon } from 'lucide-react' diff --git a/apps/saladeaula.digital/app/hooks/use-keypress.tsx b/apps/saladeaula.digital/app/hooks/use-keypress.tsx deleted file mode 100644 index f3a68d5..0000000 --- a/apps/saladeaula.digital/app/hooks/use-keypress.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { throttle } from 'lodash' -import { useEffect } from 'react' - -export function useKeyPress(targetKey, callback) { - useEffect(() => { - const onKeyDown = throttle((event) => { - if (event.key === targetKey) { - event.preventDefault() - callback(event) - } - }, 300) - - window.addEventListener('keydown', onKeyDown) - - return () => { - window.removeEventListener('keydown', onKeyDown) - onKeyDown.cancel?.() - } - }, [targetKey, callback]) - - return null -} diff --git a/apps/saladeaula.digital/app/lib/auth.ts b/apps/saladeaula.digital/app/lib/auth.ts deleted file mode 100644 index 35bd563..0000000 --- a/apps/saladeaula.digital/app/lib/auth.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { OAuth2Tokens } from 'arctic' -import { decodeJwt } from 'jose' -import { Authenticator } from 'remix-auth' -import { CodeChallengeMethod, OAuth2Strategy } from 'remix-auth-oauth2' - -export type User = { - sub: string - email: string - name: string - scope: string - email_verified: boolean - accessToken: string - refreshToken: string -} - -export function createAuth(env: Env) { - const authenticator = new Authenticator() - const strategy = new OAuth2Strategy( - { - clientId: env.CLIENT_ID, - clientSecret: env.CLIENT_SECRET, - redirectURI: env.REDIRECT_URI, - authorizationEndpoint: `${env.ISSUER_URL}/authorize`, - tokenEndpoint: `${env.ISSUER_URL}/token`, - tokenRevocationEndpoint: `${env.ISSUER_URL}/revoke`, - scopes: env.SCOPE.split(' '), - codeChallengeMethod: CodeChallengeMethod.S256 - }, - async ({ tokens }: { tokens: OAuth2Tokens }) => { - const user = decodeJwt(tokens.idToken()) - - return { - ...user, - accessToken: tokens.accessToken(), - refreshToken: tokens.hasRefreshToken() ? tokens.refreshToken() : null - } - } - ) - - authenticator.use(strategy, 'oidc') - - return authenticator -} diff --git a/apps/saladeaula.digital/app/lib/request.ts b/apps/saladeaula.digital/app/lib/request.ts index 003f322..d9e1b6d 100644 --- a/apps/saladeaula.digital/app/lib/request.ts +++ b/apps/saladeaula.digital/app/lib/request.ts @@ -1,4 +1,4 @@ -import { userContext } from '@/context' +import { userContext } from '@repo/auth/context' import type { LoaderFunctionArgs } from 'react-router' enum Method { diff --git a/apps/saladeaula.digital/app/lib/session.ts b/apps/saladeaula.digital/app/lib/session.ts deleted file mode 100644 index ce38e93..0000000 --- a/apps/saladeaula.digital/app/lib/session.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createCookieSessionStorage } from 'react-router' - -export function createSessionStorage(env: Env) { - const sessionStorage = createCookieSessionStorage({ - cookie: { - name: '__session', - httpOnly: true, - secure: false, - secrets: [env.SESSION_SECRET], - sameSite: 'lax', - path: '/', - maxAge: 86400 * 7 // 7 days - } - }) - return sessionStorage -} diff --git a/apps/saladeaula.digital/app/routes.ts b/apps/saladeaula.digital/app/routes.ts index 57d25b6..3b51a11 100644 --- a/apps/saladeaula.digital/app/routes.ts +++ b/apps/saladeaula.digital/app/routes.ts @@ -8,6 +8,7 @@ import { export default [ layout('routes/layout.tsx', [ index('routes/index.tsx'), + route('certs', 'routes/certs.tsx'), route('payments', 'routes/payments.tsx'), route('settings', 'routes/settings.tsx'), route('player/:course', 'routes/player.tsx'), diff --git a/apps/saladeaula.digital/app/routes/index.tsx b/apps/saladeaula.digital/app/routes/index.tsx index 16f02cd..a1b36fd 100644 --- a/apps/saladeaula.digital/app/routes/index.tsx +++ b/apps/saladeaula.digital/app/routes/index.tsx @@ -10,10 +10,10 @@ import { } from '@repo/ui/components/ui/empty' import { BanIcon, - BookCopyIcon, CircleCheckIcon, CircleIcon, CircleOffIcon, + CirclePlusIcon, CircleXIcon, TimerIcon, type LucideIcon @@ -131,7 +131,7 @@ export default function Component({ />
{ setSearchParams((searchParams) => { diff --git a/apps/saladeaula.digital/app/routes/layout.tsx b/apps/saladeaula.digital/app/routes/layout.tsx index 0896cc6..4c03961 100644 --- a/apps/saladeaula.digital/app/routes/layout.tsx +++ b/apps/saladeaula.digital/app/routes/layout.tsx @@ -1,12 +1,18 @@ import type { Route } from './+types' -import { Outlet } from 'react-router' +import { Link, NavLink, Outlet } from 'react-router' import { userContext } from '@repo/auth/context' import { authMiddleware } from '@repo/auth/middleware/auth' - import { ModeToggle, ThemedImage } from '@repo/ui/components/dark-mode' import { NavUser } from '@repo/ui/components/nav-user' +import { + NavigationMenu, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList +} from '@repo/ui/components/ui/navigation-menu' +import { useIsMobile } from '@repo/ui/hooks/use-mobile' export const middleware: Route.MiddlewareFunction[] = [authMiddleware] @@ -16,6 +22,7 @@ export async function loader({ context }: Route.ActionArgs) { } export default function Component({ loaderData }: Route.ComponentProps) { + const isMobile = useIsMobile() const { user } = loaderData return ( @@ -25,7 +32,25 @@ export default function Component({ loaderData }: Route.ComponentProps) { px-4 py-2 lg:py-4 sticky top-0 z-5" >
- +
+ + + + + + + + Meus cursos + + + Certificados + + + Histórico de compras + + + +
@@ -42,3 +67,14 @@ export default function Component({ loaderData }: Route.ComponentProps) {
) } + +function NavMenuLink({ children, ...props }) { + return ( + + {children} + + ) +} diff --git a/apps/saladeaula.digital/app/routes/settings.tsx b/apps/saladeaula.digital/app/routes/settings.tsx index ceb4af2..117bfb9 100644 --- a/apps/saladeaula.digital/app/routes/settings.tsx +++ b/apps/saladeaula.digital/app/routes/settings.tsx @@ -2,9 +2,10 @@ import type { Route } from './+types' import { Link } from 'react-router' -import { userContext } from '@/context' import { request as req } from '@/lib/request' -import type { User } from '@/middleware/auth' + +import type { User } from '@repo/auth/auth' +import { userContext } from '@repo/auth/context' import { Breadcrumb, BreadcrumbItem, diff --git a/package-lock.json b/package-lock.json index 28da202..50f1db3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -663,6 +663,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -4678,6 +4687,12 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "license": "MIT" + }, "node_modules/@types/lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", @@ -4769,6 +4784,28 @@ "resolved": "apps/admin.saladeaula.digital", "link": true }, + "node_modules/ahooks": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.9.6.tgz", + "integrity": "sha512-Mr7f05swd5SmKlR9SZo5U6M0LsL4ErweLzpdgXjA1JPmnZ78Vr6wzx0jUtvoxrcqGKYnX0Yjc02iEASVxHFPjQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@types/js-cookie": "^3.0.6", + "dayjs": "^1.9.1", + "intersection-observer": "^0.12.0", + "js-cookie": "^3.0.5", + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "tslib": "^2.4.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -5104,6 +5141,12 @@ "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", "license": "MIT" }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -5469,6 +5512,13 @@ "resolved": "apps/insights.saladeaula.digital", "link": true }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==", + "deprecated": "The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.", + "license": "Apache-2.0" + }, "node_modules/is-arrayish": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", @@ -5548,6 +5598,15 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6297,6 +6356,12 @@ "react": "^19.2.0" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, "node_modules/react-hook-form": { "version": "7.66.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.0.tgz", @@ -6502,6 +6567,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -6599,6 +6670,18 @@ "integrity": "sha512-0/A0Z8310GlZ0/Kx54FFeoJ0KDCz9Hwxu3sVvMCQvw37GClwcd6mao5kRio+uivqVJyMTjTWfzc0wsIHZf1l6w==", "license": "MIT" }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -8083,6 +8166,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/postcss": "^4.1.16", "@tailwindcss/vite": "^4.1.16", + "ahooks": "^3.9.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/packages/auth/src/session.ts b/packages/auth/src/session.ts index 91561c1..735f2cc 100644 --- a/packages/auth/src/session.ts +++ b/packages/auth/src/session.ts @@ -12,5 +12,6 @@ export function createSessionStorage(env) { maxAge: 86400 * 7 // 7 days } }) + return sessionStorage } diff --git a/packages/ui/package.json b/packages/ui/package.json index 165bc30..adf07fc 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -30,6 +30,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tailwindcss/postcss": "^4.1.16", "@tailwindcss/vite": "^4.1.16", + "ahooks": "^3.9.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/packages/ui/src/components/dark-mode.tsx b/packages/ui/src/components/dark-mode.tsx index b11c5b7..808f938 100644 --- a/packages/ui/src/components/dark-mode.tsx +++ b/packages/ui/src/components/dark-mode.tsx @@ -44,11 +44,11 @@ export function ModeToggle() { ) } -export function ThemedImage() { +export function ThemedImage({ ...props }) { return ( - <> +
- +
) } diff --git a/packages/ui/src/components/search-form.tsx b/packages/ui/src/components/search-form.tsx index 03a1580..a0f0059 100644 --- a/packages/ui/src/components/search-form.tsx +++ b/packages/ui/src/components/search-form.tsx @@ -1,3 +1,4 @@ +import { useKeyPress } from 'ahooks' import { debounce } from 'lodash' import { SearchIcon, XIcon } from 'lucide-react' import { useRef } from 'react' @@ -8,8 +9,6 @@ import { InputGroupButton, InputGroupInput } from '@repo/ui/components/ui/input-group' - -import { useKeyPress } from '@repo/ui/hooks/use-keypress' import { cn } from '@repo/ui/lib/utils' export function SearchForm({ @@ -26,9 +25,18 @@ export function SearchForm({ } & React.HTMLAttributes) { const inputRef = useRef(null) - useKeyPress('/', () => { - inputRef.current?.focus() - }) + useKeyPress( + ['forwardslash'], + () => { + inputRef.current?.focus() + }, + { + exactMatch: true, + events: ['keyup'], + useCapture: true, + target: () => window + } + ) const debouncedOnChange = debounce((value: string) => { onChange?.(value) diff --git a/packages/ui/src/components/ui/checkbox.tsx b/packages/ui/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..cb0b07b --- /dev/null +++ b/packages/ui/src/components/ui/checkbox.tsx @@ -0,0 +1,32 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { CheckIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/packages/ui/src/components/ui/sidebar.tsx b/packages/ui/src/components/ui/sidebar.tsx index 30638ac..a4ee7ee 100644 --- a/packages/ui/src/components/ui/sidebar.tsx +++ b/packages/ui/src/components/ui/sidebar.tsx @@ -1,39 +1,39 @@ -"use client" +'use client' -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" -import { PanelLeftIcon } from "lucide-react" +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' +import { PanelLeftIcon } from 'lucide-react' +import * as React from 'react' -import { useIsMobile } from "@/hooks/use-mobile" -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Separator } from "@/components/ui/separator" +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Separator } from '@/components/ui/separator' import { Sheet, SheetContent, SheetDescription, SheetHeader, - SheetTitle, -} from "@/components/ui/sheet" -import { Skeleton } from "@/components/ui/skeleton" + SheetTitle +} from '@/components/ui/sheet' +import { Skeleton } from '@/components/ui/skeleton' import { Tooltip, TooltipContent, TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip" + TooltipTrigger +} from '@/components/ui/tooltip' +import { useIsMobile } from '@/hooks/use-mobile' +import { cn } from '@/lib/utils' -const SIDEBAR_COOKIE_NAME = "sidebar_state" +const SIDEBAR_COOKIE_NAME = 'sidebar_state' const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 -const SIDEBAR_WIDTH = "16rem" -const SIDEBAR_WIDTH_MOBILE = "18rem" -const SIDEBAR_WIDTH_ICON = "3rem" -const SIDEBAR_KEYBOARD_SHORTCUT = "b" +const SIDEBAR_WIDTH = '16rem' +const SIDEBAR_WIDTH_MOBILE = '18rem' +const SIDEBAR_WIDTH_ICON = '3rem' +const SIDEBAR_KEYBOARD_SHORTCUT = 'b' type SidebarContextProps = { - state: "expanded" | "collapsed" + state: 'expanded' | 'collapsed' open: boolean setOpen: (open: boolean) => void openMobile: boolean @@ -47,7 +47,7 @@ const SidebarContext = React.createContext(null) function useSidebar() { const context = React.useContext(SidebarContext) if (!context) { - throw new Error("useSidebar must be used within a SidebarProvider.") + throw new Error('useSidebar must be used within a SidebarProvider.') } return context @@ -61,7 +61,7 @@ function SidebarProvider({ style, children, ...props -}: React.ComponentProps<"div"> & { +}: React.ComponentProps<'div'> & { defaultOpen?: boolean open?: boolean onOpenChange?: (open: boolean) => void @@ -75,7 +75,7 @@ function SidebarProvider({ const open = openProp ?? _open const setOpen = React.useCallback( (value: boolean | ((value: boolean) => boolean)) => { - const openState = typeof value === "function" ? value(open) : value + const openState = typeof value === 'function' ? value(open) : value if (setOpenProp) { setOpenProp(openState) } else { @@ -105,13 +105,13 @@ function SidebarProvider({ } } - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) }, [toggleSidebar]) // We add a state so that we can do data-state="expanded" or "collapsed". // This makes it easier to style the sidebar with Tailwind classes. - const state = open ? "expanded" : "collapsed" + const state = open ? 'expanded' : 'collapsed' const contextValue = React.useMemo( () => ({ @@ -121,7 +121,7 @@ function SidebarProvider({ isMobile, openMobile, setOpenMobile, - toggleSidebar, + toggleSidebar }), [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] ) @@ -133,13 +133,13 @@ function SidebarProvider({ data-slot="sidebar-wrapper" style={ { - "--sidebar-width": SIDEBAR_WIDTH, - "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, - ...style, + '--sidebar-width': SIDEBAR_WIDTH, + '--sidebar-width-icon': SIDEBAR_WIDTH_ICON, + ...style } as React.CSSProperties } className={cn( - "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", + 'group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full', className )} {...props} @@ -152,25 +152,25 @@ function SidebarProvider({ } function Sidebar({ - side = "left", - variant = "sidebar", - collapsible = "offcanvas", + side = 'left', + variant = 'sidebar', + collapsible = 'offcanvas', className, children, ...props -}: React.ComponentProps<"div"> & { - side?: "left" | "right" - variant?: "sidebar" | "floating" | "inset" - collapsible?: "offcanvas" | "icon" | "none" +}: React.ComponentProps<'div'> & { + side?: 'left' | 'right' + variant?: 'sidebar' | 'floating' | 'inset' + collapsible?: 'offcanvas' | 'icon' | 'none' }) { const { isMobile, state, openMobile, setOpenMobile } = useSidebar() - if (collapsible === "none") { + if (collapsible === 'none') { return (