update to react-router v7

This commit is contained in:
Amruth Pillai
2025-01-12 19:41:18 +01:00
parent db6e7a7480
commit 7fb0226ddc
45 changed files with 112 additions and 106 deletions

View File

@ -40,12 +40,5 @@
<!-- Phosphor Icons --> <!-- Phosphor Icons -->
<script src="https://unpkg.com/@phosphor-icons/web"></script> <script src="https://unpkg.com/@phosphor-icons/web"></script>
<!-- Simple Icons -->
<link
type="text/css"
rel="stylesheet"
href="https://unpkg.com/simple-icons-font@v14/font/simple-icons.min.css"
/>
</body> </body>
</html> </html>

View File

@ -1,5 +1,3 @@
import { cn } from "@reactive-resume/utils";
type BrandIconProps = { type BrandIconProps = {
slug: string; slug: string;
}; };
@ -8,12 +6,12 @@ export const BrandIcon = ({ slug }: BrandIconProps) => {
if (slug === "linkedin") { if (slug === "linkedin") {
return ( return (
<img <img
alt="LinkedIn" alt="linkedin"
className="size-4" className="size-4"
src={`${window.location.origin}/support-logos/linkedin.svg`} src={`${window.location.origin}/support-logos/linkedin.svg`}
/> />
); );
} }
return <i className={cn("si si--color text-[1rem]", `si-${slug}`)} />; return <img alt={slug} className="size-4" src={`https://cdn.simpleicons.org/${slug}`} />;
}; };

View File

@ -1,6 +1,6 @@
import { StrictMode } from "react"; import { StrictMode } from "react";
import * as ReactDOM from "react-dom/client"; import * as ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom"; import { RouterProvider } from "react-router";
import { router } from "./router"; import { router } from "./router";

View File

@ -1,5 +1,5 @@
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router";
import webfontloader from "webfontloader"; import webfontloader from "webfontloader";
import { useArtboardStore } from "../store/artboard"; import { useArtboardStore } from "../store/artboard";

View File

@ -1,5 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router";
import { useArtboardStore } from "../store/artboard"; import { useArtboardStore } from "../store/artboard";

View File

@ -1,4 +1,4 @@
import { createBrowserRouter, createRoutesFromChildren, Route } from "react-router-dom"; import { createBrowserRouter, createRoutesFromChildren, Route } from "react-router";
import { ArtboardPage } from "../pages/artboard"; import { ArtboardPage } from "../pages/artboard";
import { BuilderLayout } from "../pages/builder"; import { BuilderLayout } from "../pages/builder";
@ -6,7 +6,7 @@ import { PreviewLayout } from "../pages/preview";
import { Providers } from "../providers"; import { Providers } from "../providers";
export const routes = createRoutesFromChildren( export const routes = createRoutesFromChildren(
<Route element={<Providers />}> <Route element={<Providers />} hydrateFallbackElement={<div>Loading...</div>}>
<Route path="artboard" element={<ArtboardPage />}> <Route path="artboard" element={<ArtboardPage />}>
<Route path="builder" element={<BuilderLayout />} /> <Route path="builder" element={<BuilderLayout />} />
<Route path="preview" element={<PreviewLayout />} /> <Route path="preview" element={<PreviewLayout />} />

View File

@ -43,12 +43,5 @@
<!-- Phosphor Icons --> <!-- Phosphor Icons -->
<script src="https://unpkg.com/@phosphor-icons/web"></script> <script src="https://unpkg.com/@phosphor-icons/web"></script>
<!-- Simple Icons -->
<link
type="text/css"
rel="stylesheet"
href="https://unpkg.com/simple-icons-font@v14/font/simple-icons.min.css"
/>
</body> </body>
</html> </html>

View File

@ -7,7 +7,7 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
KeyboardShortcut, KeyboardShortcut,
} from "@reactive-resume/ui"; } from "@reactive-resume/ui";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router";
import { useLogout } from "../services/auth"; import { useLogout } from "../services/auth";
@ -26,7 +26,7 @@ export const UserOptions = ({ children }: Props) => {
<DropdownMenuContent side="top" align="start" className="w-48"> <DropdownMenuContent side="top" align="start" className="w-48">
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
navigate("/dashboard/settings"); void navigate("/dashboard/settings");
}} }}
> >
{t`Settings`} {t`Settings`}

View File

@ -2,7 +2,7 @@ import { t } from "@lingui/macro";
import { deepSearchAndParseDates, ErrorMessage } from "@reactive-resume/utils"; import { deepSearchAndParseDates, ErrorMessage } from "@reactive-resume/utils";
import _axios from "axios"; import _axios from "axios";
import createAuthRefreshInterceptor from "axios-auth-refresh"; import createAuthRefreshInterceptor from "axios-auth-refresh";
import { redirect } from "react-router-dom"; import { redirect } from "react-router";
import { refreshToken } from "@/client/services/auth"; import { refreshToken } from "@/client/services/auth";

View File

@ -1,6 +1,6 @@
import { StrictMode } from "react"; import { StrictMode } from "react";
import * as ReactDOM from "react-dom/client"; import * as ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom"; import { RouterProvider } from "react-router";
import { router } from "./router"; import { router } from "./router";

View File

@ -16,7 +16,7 @@ import {
import { useRef } from "react"; import { useRef } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router";
import { z } from "zod"; import { z } from "zod";
import { useBackupOtp } from "@/client/services/auth"; import { useBackupOtp } from "@/client/services/auth";
@ -39,7 +39,7 @@ export const BackupOtpPage = () => {
try { try {
await backupOtp(data); await backupOtp(data);
navigate("/dashboard"); void navigate("/dashboard");
} catch { } catch {
form.reset(); form.reset();
} }
@ -92,7 +92,7 @@ export const BackupOtpPage = () => {
variant="link" variant="link"
className="px-5" className="px-5"
onClick={() => { onClick={() => {
navigate(-1); void navigate(-1);
}} }}
> >
<ArrowLeft size={14} className="mr-2" /> <ArrowLeft size={14} className="mr-2" />

View File

@ -17,7 +17,7 @@ import {
import { useState } from "react"; import { useState } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router";
import { z } from "zod"; import { z } from "zod";
import { useForgotPassword } from "@/client/services/auth"; import { useForgotPassword } from "@/client/services/auth";
@ -93,7 +93,7 @@ export const ForgotPasswordPage = () => {
variant="link" variant="link"
className="px-5" className="px-5"
onClick={() => { onClick={() => {
navigate(-1); void navigate(-1);
}} }}
> >
<ArrowLeft size={14} className="mr-2" /> <ArrowLeft size={14} className="mr-2" />

View File

@ -1,7 +1,7 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { cn } from "@reactive-resume/utils"; import { cn } from "@reactive-resume/utils";
import { useMemo } from "react"; import { useMemo } from "react";
import { Link, matchRoutes, Outlet, useLocation } from "react-router-dom"; import { Link, matchRoutes, Outlet, useLocation } from "react-router";
import { LocaleSwitch } from "@/client/components/locale-switch"; import { LocaleSwitch } from "@/client/components/locale-switch";
import { Logo } from "@/client/components/logo"; import { Logo } from "@/client/components/logo";

View File

@ -20,7 +20,7 @@ import { cn } from "@reactive-resume/utils";
import { useRef } from "react"; import { useRef } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { Link } from "react-router-dom"; import { Link } from "react-router";
import { z } from "zod"; import { z } from "zod";
import { useLogin } from "@/client/services/auth"; import { useLogin } from "@/client/services/auth";

View File

@ -20,7 +20,7 @@ import { cn } from "@reactive-resume/utils";
import { useRef } from "react"; import { useRef } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router";
import { z } from "zod"; import { z } from "zod";
import { useRegister } from "@/client/services/auth"; import { useRegister } from "@/client/services/auth";
@ -51,7 +51,7 @@ export const RegisterPage = () => {
try { try {
await register(data); await register(data);
navigate("/auth/verify-email"); void navigate("/auth/verify-email");
} catch { } catch {
form.reset(); form.reset();
} }

View File

@ -16,7 +16,7 @@ import {
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router";
import { z } from "zod"; import { z } from "zod";
import { useResetPassword } from "@/client/services/auth"; import { useResetPassword } from "@/client/services/auth";
@ -42,7 +42,7 @@ export const ResetPasswordPage = () => {
try { try {
await resetPassword(data); await resetPassword(data);
navigate("/auth/login"); void navigate("/auth/login");
} catch { } catch {
form.reset(); form.reset();
} }
@ -50,7 +50,7 @@ export const ResetPasswordPage = () => {
// Redirect the user to the forgot password page if the token is not present. // Redirect the user to the forgot password page if the token is not present.
useEffect(() => { useEffect(() => {
if (!token) navigate("/auth/forgot-password"); if (!token) void navigate("/auth/forgot-password");
}, [token, navigate]); }, [token, navigate]);
return ( return (

View File

@ -3,7 +3,7 @@ import { ArrowRight, Info, SealCheck } from "@phosphor-icons/react";
import { Alert, AlertDescription, AlertTitle, Button } from "@reactive-resume/ui"; import { Alert, AlertDescription, AlertTitle, Button } from "@reactive-resume/ui";
import { useEffect } from "react"; import { useEffect } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { Link, useNavigate, useSearchParams } from "react-router-dom"; import { Link, useNavigate, useSearchParams } from "react-router";
import { useToast } from "@/client/hooks/use-toast"; import { useToast } from "@/client/hooks/use-toast";
import { queryClient } from "@/client/libs/query-client"; import { queryClient } from "@/client/libs/query-client";
@ -28,7 +28,7 @@ export const VerifyEmailPage = () => {
title: t`Your email address has been verified successfully.`, title: t`Your email address has been verified successfully.`,
}); });
navigate("/dashboard/resumes", { replace: true }); void navigate("/dashboard/resumes", { replace: true });
}; };
if (!token) return; if (!token) return;

View File

@ -16,7 +16,7 @@ import {
import { useRef } from "react"; import { useRef } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router";
import { z } from "zod"; import { z } from "zod";
import { useVerifyOtp } from "@/client/services/auth"; import { useVerifyOtp } from "@/client/services/auth";
@ -39,7 +39,7 @@ export const VerifyOtpPage = () => {
try { try {
await verifyOtp(data); await verifyOtp(data);
navigate("/dashboard"); void navigate("/dashboard");
} catch { } catch {
form.reset(); form.reset();
} }

View File

@ -2,7 +2,7 @@ import { t } from "@lingui/macro";
import { HouseSimple, Lock, SidebarSimple } from "@phosphor-icons/react"; import { HouseSimple, Lock, SidebarSimple } from "@phosphor-icons/react";
import { Button, Tooltip } from "@reactive-resume/ui"; import { Button, Tooltip } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils"; import { cn } from "@reactive-resume/utils";
import { Link } from "react-router-dom"; import { Link } from "react-router";
import { useBuilderStore } from "@/client/stores/builder"; import { useBuilderStore } from "@/client/stores/builder";
import { useResumeStore } from "@/client/stores/resume"; import { useResumeStore } from "@/client/stores/resume";

View File

@ -1,7 +1,7 @@
import { useBreakpoint } from "@reactive-resume/hooks"; import { useBreakpoint } from "@reactive-resume/hooks";
import { Panel, PanelGroup, PanelResizeHandle, Sheet, SheetContent } from "@reactive-resume/ui"; import { Panel, PanelGroup, PanelResizeHandle, Sheet, SheetContent } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils"; import { cn } from "@reactive-resume/utils";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router";
import { useBuilderStore } from "@/client/stores/builder"; import { useBuilderStore } from "@/client/stores/builder";

View File

@ -2,7 +2,7 @@ import { t } from "@lingui/macro";
import { ResumeDto } from "@reactive-resume/dto"; import { ResumeDto } from "@reactive-resume/dto";
import { useCallback, useEffect } from "react"; import { useCallback, useEffect } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { LoaderFunction, redirect } from "react-router-dom"; import { LoaderFunction, redirect } from "react-router";
import { queryClient } from "@/client/libs/query-client"; import { queryClient } from "@/client/libs/query-client";
import { findResumeById } from "@/client/services/resume"; import { findResumeById } from "@/client/services/resume";

View File

@ -17,7 +17,7 @@ import {
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Button, ScrollArea, Separator } from "@reactive-resume/ui"; import { Button, ScrollArea, Separator } from "@reactive-resume/ui";
import { Fragment, useRef } from "react"; import { Fragment, useRef } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router";
import { Icon } from "@/client/components/icon"; import { Icon } from "@/client/components/icon";
import { UserAvatar } from "@/client/components/user-avatar"; import { UserAvatar } from "@/client/components/user-avatar";

View File

@ -3,7 +3,7 @@ import { FadersHorizontal, ReadCvLogo } from "@phosphor-icons/react";
import { Button, KeyboardShortcut, Separator } from "@reactive-resume/ui"; import { Button, KeyboardShortcut, Separator } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils"; import { cn } from "@reactive-resume/utils";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router";
import useKeyboardShortcut from "use-keyboard-shortcut"; import useKeyboardShortcut from "use-keyboard-shortcut";
import { Copyright } from "@/client/components/copyright"; import { Copyright } from "@/client/components/copyright";
@ -71,12 +71,12 @@ export const Sidebar = ({ setOpen }: SidebarProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
useKeyboardShortcut(["shift", "r"], () => { useKeyboardShortcut(["shift", "r"], () => {
navigate("/dashboard/resumes"); void navigate("/dashboard/resumes");
setOpen?.(false); setOpen?.(false);
}); });
useKeyboardShortcut(["shift", "s"], () => { useKeyboardShortcut(["shift", "s"], () => {
navigate("/dashboard/settings"); void navigate("/dashboard/settings");
setOpen?.(false); setOpen?.(false);
}); });

View File

@ -2,7 +2,7 @@ import { SidebarSimple } from "@phosphor-icons/react";
import { Button, Sheet, SheetClose, SheetContent, SheetTrigger } from "@reactive-resume/ui"; import { Button, Sheet, SheetClose, SheetContent, SheetTrigger } from "@reactive-resume/ui";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { useState } from "react"; import { useState } from "react";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router";
import { Sidebar } from "./_components/sidebar"; import { Sidebar } from "./_components/sidebar";

View File

@ -43,7 +43,7 @@ import { useCreateResume, useDeleteResume, useUpdateResume } from "@/client/serv
import { useImportResume } from "@/client/services/resume/import"; import { useImportResume } from "@/client/services/resume/import";
import { useDialog } from "@/client/stores/dialog"; import { useDialog } from "@/client/stores/dialog";
const formSchema = createResumeSchema.extend({ id: idSchema.optional() }); const formSchema = createResumeSchema.extend({ id: idSchema.optional(), slug: z.string() });
type FormValues = z.infer<typeof formSchema>; type FormValues = z.infer<typeof formSchema>;

View File

@ -18,7 +18,7 @@ import {
import { cn } from "@reactive-resume/utils"; import { cn } from "@reactive-resume/utils";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, motion } from "framer-motion";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router";
import { useDialog } from "@/client/stores/dialog"; import { useDialog } from "@/client/stores/dialog";
@ -37,7 +37,7 @@ export const ResumeCard = ({ resume }: Props) => {
const lastUpdated = dayjs().to(resume.updatedAt); const lastUpdated = dayjs().to(resume.updatedAt);
const onOpen = () => { const onOpen = () => {
navigate(`/builder/${resume.id}`); void navigate(`/builder/${resume.id}`);
}; };
const onUpdate = () => { const onUpdate = () => {

View File

@ -22,7 +22,7 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@reactive-resume/ui"; } from "@reactive-resume/ui";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router";
import { useDialog } from "@/client/stores/dialog"; import { useDialog } from "@/client/stores/dialog";
@ -40,7 +40,7 @@ export const ResumeListItem = ({ resume }: Props) => {
const lastUpdated = dayjs().to(resume.updatedAt); const lastUpdated = dayjs().to(resume.updatedAt);
const onOpen = () => { const onOpen = () => {
navigate(`/builder/${resume.id}`); void navigate(`/builder/${resume.id}`);
}; };
const onUpdate = () => { const onUpdate = () => {

View File

@ -11,7 +11,7 @@ import {
Input, Input,
} from "@reactive-resume/ui"; } from "@reactive-resume/ui";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router";
import { useCounter } from "usehooks-ts"; import { useCounter } from "usehooks-ts";
import { z } from "zod"; import { z } from "zod";
@ -52,7 +52,7 @@ export const DangerZoneSettings = () => {
title: t`Your account and all your data has been deleted successfully. Goodbye!`, title: t`Your account and all your data has been deleted successfully. Goodbye!`,
}); });
navigate("/"); void navigate("/");
} }
}; };

View File

@ -1,6 +1,6 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { Separator } from "@reactive-resume/ui"; import { Separator } from "@reactive-resume/ui";
import { Link } from "react-router-dom"; import { Link } from "react-router";
import { Copyright } from "@/client/components/copyright"; import { Copyright } from "@/client/components/copyright";
import { LocaleSwitch } from "@/client/components/locale-switch"; import { LocaleSwitch } from "@/client/components/locale-switch";

View File

@ -1,5 +1,5 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Link } from "react-router-dom"; import { Link } from "react-router";
import { Logo } from "@/client/components/logo"; import { Logo } from "@/client/components/logo";

View File

@ -1,5 +1,5 @@
import { ScrollArea } from "@reactive-resume/ui"; import { ScrollArea } from "@reactive-resume/ui";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router";
import { Footer } from "./components/footer"; import { Footer } from "./components/footer";
import { Header } from "./components/header"; import { Header } from "./components/header";

View File

@ -1,7 +1,7 @@
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { Book, SignOut } from "@phosphor-icons/react"; import { Book, SignOut } from "@phosphor-icons/react";
import { Button } from "@reactive-resume/ui"; import { Button } from "@reactive-resume/ui";
import { Link } from "react-router-dom"; import { Link } from "react-router";
import { useLogout } from "@/client/services/auth"; import { useLogout } from "@/client/services/auth";
import { useAuthStore } from "@/client/stores/auth"; import { useAuthStore } from "@/client/stores/auth";

View File

@ -5,7 +5,7 @@ import { Button } from "@reactive-resume/ui";
import { pageSizeMap } from "@reactive-resume/utils"; import { pageSizeMap } from "@reactive-resume/utils";
import { useCallback, useEffect, useRef } from "react"; import { useCallback, useEffect, useRef } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { Link, LoaderFunction, redirect, useLoaderData } from "react-router-dom"; import { Link, LoaderFunction, redirect, useLoaderData } from "react-router";
import { Icon } from "@/client/components/icon"; import { Icon } from "@/client/components/icon";
import { ThemeSwitch } from "@/client/components/theme-switch"; import { ThemeSwitch } from "@/client/components/theme-switch";
@ -22,8 +22,8 @@ export const PublicResumePage = () => {
const { printResume, loading } = usePrintResume(); const { printResume, loading } = usePrintResume();
const { id, title, data: resume } = useLoaderData() as ResumeDto; const { id, title, data: resume } = useLoaderData();
const format = resume.metadata.page.format; const format = resume.metadata.page.format as keyof typeof pageSizeMap;
const updateResumeInFrame = useCallback(() => { const updateResumeInFrame = useCallback(() => {
if (!frameRef.current?.contentWindow) return; if (!frameRef.current?.contentWindow) return;

View File

@ -1,7 +1,7 @@
import { TooltipProvider } from "@reactive-resume/ui"; import { TooltipProvider } from "@reactive-resume/ui";
import { QueryClientProvider } from "@tanstack/react-query"; import { QueryClientProvider } from "@tanstack/react-query";
import { HelmetProvider } from "react-helmet-async"; import { HelmetProvider } from "react-helmet-async";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router";
import { helmetContext } from "../constants/helmet"; import { helmetContext } from "../constants/helmet";
import { queryClient } from "../libs/query-client"; import { queryClient } from "../libs/query-client";

View File

@ -1,4 +1,4 @@
import { Navigate, Outlet, useLocation } from "react-router-dom"; import { Navigate, Outlet, useLocation } from "react-router";
import { useUser } from "@/client/services/user"; import { useUser } from "@/client/services/user";

View File

@ -1,4 +1,4 @@
import { Navigate, Outlet, useSearchParams } from "react-router-dom"; import { Navigate, Outlet, useSearchParams } from "react-router";
import { useAuthStore } from "@/client/stores/auth"; import { useAuthStore } from "@/client/stores/auth";

View File

@ -1,4 +1,4 @@
import { createBrowserRouter, createRoutesFromElements, Navigate, Route } from "react-router-dom"; import { createBrowserRouter, createRoutesFromElements, Navigate, Route } from "react-router";
import { BackupOtpPage } from "../pages/auth/backup-otp/page"; import { BackupOtpPage } from "../pages/auth/backup-otp/page";
import { ForgotPasswordPage } from "../pages/auth/forgot-password/page"; import { ForgotPasswordPage } from "../pages/auth/forgot-password/page";
@ -23,7 +23,8 @@ import { GuestGuard } from "./guards/guest";
import { authLoader } from "./loaders/auth"; import { authLoader } from "./loaders/auth";
export const routes = createRoutesFromElements( export const routes = createRoutesFromElements(
<Route element={<Providers />}> // eslint-disable-next-line lingui/no-unlocalized-strings
<Route element={<Providers />} hydrateFallbackElement={<div>Loading...</div>}>
<Route element={<HomeLayout />}> <Route element={<HomeLayout />}>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />

View File

@ -1,5 +1,5 @@
import { authResponseSchema, UserDto } from "@reactive-resume/dto"; import { authResponseSchema, UserDto } from "@reactive-resume/dto";
import { LoaderFunction, redirect } from "react-router-dom"; import { LoaderFunction, redirect } from "react-router";
import { USER_KEY } from "@/client/constants/query-keys"; import { USER_KEY } from "@/client/constants/query-keys";
import { queryClient } from "@/client/libs/query-client"; import { queryClient } from "@/client/libs/query-client";

View File

@ -1,7 +1,7 @@
import { AuthResponseDto, LoginDto } from "@reactive-resume/dto"; import { AuthResponseDto, LoginDto } from "@reactive-resume/dto";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router";
import { axios } from "@/client/libs/axios"; import { axios } from "@/client/libs/axios";
import { queryClient } from "@/client/libs/query-client"; import { queryClient } from "@/client/libs/query-client";
@ -28,7 +28,7 @@ export const useLogin = () => {
mutationFn: login, mutationFn: login,
onSuccess: (data) => { onSuccess: (data) => {
if (data.status === "2fa_required") { if (data.status === "2fa_required") {
navigate("/auth/verify-otp"); void navigate("/auth/verify-otp");
return; return;
} }

View File

@ -14,6 +14,7 @@
"@sindresorhus/slugify": "^2.2.1", "@sindresorhus/slugify": "^2.2.1",
"nestjs-zod": "^3.0.0", "nestjs-zod": "^3.0.0",
"@swc/helpers": "~0.5.11", "@swc/helpers": "~0.5.11",
"zod": "^3.24.1" "zod": "^3.24.1",
"@paralleldrive/cuid2": "^2.2.2"
} }
} }

View File

@ -1,3 +1,4 @@
import { createId } from "@paralleldrive/cuid2";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { createZodDto } from "nestjs-zod/dto"; import { createZodDto } from "nestjs-zod/dto";
import { z } from "zod"; import { z } from "zod";
@ -7,7 +8,11 @@ export const createResumeSchema = z.object({
slug: z slug: z
.string() .string()
.min(1) .min(1)
.transform((value) => slugify(value)) .transform((value) => {
const slug = slugify(value);
if (!slug) return createId();
return slug;
})
.optional(), .optional(),
visibility: z.enum(["public", "private"]).default("private"), visibility: z.enum(["public", "private"]).default("private"),
}); });

View File

@ -1,3 +1,4 @@
import { createId } from "@paralleldrive/cuid2";
import { resumeDataSchema } from "@reactive-resume/schema"; import { resumeDataSchema } from "@reactive-resume/schema";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { createZodDto } from "nestjs-zod/dto"; import { createZodDto } from "nestjs-zod/dto";
@ -8,7 +9,11 @@ export const importResumeSchema = z.object({
slug: z slug: z
.string() .string()
.min(1) .min(1)
.transform((value) => slugify(value)) .transform((value) => {
const slug = slugify(value);
if (slug === "") return createId();
return slug;
})
.optional(), .optional(),
visibility: z.enum(["public", "private"]).default("private").optional(), visibility: z.enum(["public", "private"]).default("private").optional(),
data: resumeDataSchema, data: resumeDataSchema,

View File

@ -7,4 +7,4 @@ export const pageSizeMap = {
width: 216, width: 216,
height: 279, height: 279,
}, },
}; } as const;

View File

@ -224,7 +224,7 @@
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-parallax-tilt": "^1.7.272", "react-parallax-tilt": "^1.7.272",
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"react-router-dom": "^6.28.1", "react-router": "^7.1.1",
"react-zoom-pan-pinch": "^3.6.1", "react-zoom-pan-pinch": "^3.6.1",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",

66
pnpm-lock.yaml generated
View File

@ -323,9 +323,9 @@ importers:
react-resizable-panels: react-resizable-panels:
specifier: ^2.1.7 specifier: ^2.1.7
version: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-router-dom: react-router:
specifier: ^6.28.1 specifier: ^7.1.1
version: 6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-zoom-pan-pinch: react-zoom-pan-pinch:
specifier: ^3.6.1 specifier: ^3.6.1
version: 3.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 3.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -3579,10 +3579,6 @@ packages:
'@remirror/core-constants@3.0.0': '@remirror/core-constants@3.0.0':
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
'@remix-run/router@1.21.0':
resolution: {integrity: sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==}
engines: {node: '>=14.0.0'}
'@rollup/pluginutils@5.1.4': '@rollup/pluginutils@5.1.4':
resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
@ -4235,6 +4231,9 @@ packages:
peerDependencies: peerDependencies:
'@types/express': '*' '@types/express': '*'
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
'@types/cookies@0.9.0': '@types/cookies@0.9.0':
resolution: {integrity: sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==} resolution: {integrity: sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==}
@ -5601,6 +5600,10 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
cookie@1.0.2:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
cookies@0.9.1: cookies@0.9.1:
resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -9680,18 +9683,15 @@ packages:
react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-router-dom@6.28.1: react-router@7.1.1:
resolution: {integrity: sha512-YraE27C/RdjcZwl5UCqF/ffXnZDxpJdk9Q6jw38SZHjXs7NNdpViq2l2c7fO7+4uWaEfcwfGCv3RSg4e1By/fQ==} resolution: {integrity: sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==}
engines: {node: '>=14.0.0'} engines: {node: '>=20.0.0'}
peerDependencies: peerDependencies:
react: '>=16.8' react: '>=18'
react-dom: '>=16.8' react-dom: '>=18'
peerDependenciesMeta:
react-router@6.28.1: react-dom:
resolution: {integrity: sha512-2omQTA3rkMljmrvvo6WtewGdVh45SpL9hGiCI9uUrwGGfNFDIvGK4gYJsKlJoNVi6AQZcopSCballL+QGOm7fA==} optional: true
engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
react-style-singleton@2.2.1: react-style-singleton@2.2.1:
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
@ -10073,6 +10073,9 @@ packages:
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
set-function-length@1.2.2: set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -10768,6 +10771,9 @@ packages:
resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
engines: {node: '>=0.6.x'} engines: {node: '>=0.6.x'}
turbo-stream@2.4.0:
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
type-check@0.4.0: type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -15223,8 +15229,6 @@ snapshots:
'@remirror/core-constants@3.0.0': {} '@remirror/core-constants@3.0.0': {}
'@remix-run/router@1.21.0': {}
'@rollup/pluginutils@5.1.4(rollup@4.30.1)': '@rollup/pluginutils@5.1.4(rollup@4.30.1)':
dependencies: dependencies:
'@types/estree': 1.0.6 '@types/estree': 1.0.6
@ -15927,6 +15931,8 @@ snapshots:
dependencies: dependencies:
'@types/express': 4.17.21 '@types/express': 4.17.21
'@types/cookie@0.6.0': {}
'@types/cookies@0.9.0': '@types/cookies@0.9.0':
dependencies: dependencies:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
@ -17597,6 +17603,8 @@ snapshots:
cookie@0.7.2: {} cookie@0.7.2: {}
cookie@1.0.2: {}
cookies@0.9.1: cookies@0.9.1:
dependencies: dependencies:
depd: 2.0.0 depd: 2.0.0
@ -22694,17 +22702,15 @@ snapshots:
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
react-router-dom@6.28.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): react-router@7.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
'@remix-run/router': 1.21.0 '@types/cookie': 0.6.0
cookie: 1.0.2
react: 18.3.1 react: 18.3.1
set-cookie-parser: 2.7.1
turbo-stream: 2.4.0
optionalDependencies:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
react-router: 6.28.1(react@18.3.1)
react-router@6.28.1(react@18.3.1):
dependencies:
'@remix-run/router': 1.21.0
react: 18.3.1
react-style-singleton@2.2.1(@types/react@18.3.18)(react@18.3.1): react-style-singleton@2.2.1(@types/react@18.3.18)(react@18.3.1):
dependencies: dependencies:
@ -23153,6 +23159,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
set-cookie-parser@2.7.1: {}
set-function-length@1.2.2: set-function-length@1.2.2:
dependencies: dependencies:
define-data-property: 1.1.4 define-data-property: 1.1.4
@ -23973,6 +23981,8 @@ snapshots:
tsscmp@1.0.6: {} tsscmp@1.0.6: {}
turbo-stream@2.4.0: {}
type-check@0.4.0: type-check@0.4.0:
dependencies: dependencies:
prelude-ls: 1.2.1 prelude-ls: 1.2.1