Migrate from Biome to Oxlint/Oxfmt (#2822)

* Migrate from Biome to Oxlint/Oxfmt

* pin version of autofix

* set version of autofix

* pin version of autofix

* [autofix.ci] apply automated fixes

* better comments, test formatter

* [autofix.ci] apply automated fixes

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Amruth Pillai
2026-03-18 14:59:05 +01:00
committed by GitHub
parent 040755bec9
commit 99c602e3c7
338 changed files with 19496 additions and 5374 deletions
+4 -2
View File
@@ -4,13 +4,15 @@ import { DownloadSimpleIcon } from "@phosphor-icons/react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { createFileRoute, notFound, redirect } from "@tanstack/react-router";
import { useCallback, useEffect } from "react";
import type { ResumeData } from "@/schema/resume/data";
import { LoadingScreen } from "@/components/layout/loading-screen";
import { ResumePreview } from "@/components/resume/preview";
import { useResumeStore } from "@/components/resume/store/resume";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { orpc, type RouterOutput } from "@/integrations/orpc/client";
import type { ResumeData } from "@/schema/resume/data";
import { downloadFromUrl } from "@/utils/file";
import { cn } from "@/utils/style";
@@ -68,7 +70,7 @@ function RouteComponent() {
const handleDownload = useCallback(async () => {
if (!resume) return;
const { url } = await printResumeAsPDF({ id: resume.id });
downloadFromUrl(url, `${resume.name}.pdf`);
await downloadFromUrl(url, `${resume.name}.pdf`);
}, [resume, printResumeAsPDF]);
if (!isReady) return <LoadingScreen />;
+6 -3
View File
@@ -1,12 +1,16 @@
import "@fontsource-variable/ibm-plex-sans";
import "@phosphor-icons/web/regular/style.css";
import type { QueryClient } from "@tanstack/react-query";
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { IconContext } from "@phosphor-icons/react";
import type { QueryClient } from "@tanstack/react-query";
import { createRootRouteWithContext, HeadContent, Scripts } from "@tanstack/react-router";
import { MotionConfig } from "motion/react";
import type { AuthSession } from "@/integrations/auth/types";
import type { FeatureFlags } from "@/integrations/orpc/services/flags";
import { CommandPalette } from "@/components/command-palette";
import { BreakpointIndicator } from "@/components/layout/breakpoint-indicator";
import { ThemeProvider } from "@/components/theme/provider";
@@ -16,11 +20,10 @@ import { DialogManager } from "@/dialogs/manager";
import { ConfirmDialogProvider } from "@/hooks/use-confirm";
import { PromptDialogProvider } from "@/hooks/use-prompt";
import { getSession } from "@/integrations/auth/functions";
import type { AuthSession } from "@/integrations/auth/types";
import { client, type orpc } from "@/integrations/orpc/client";
import type { FeatureFlags } from "@/integrations/orpc/services/flags";
import { getLocale, isRTL, type Locale, loadLocale } from "@/utils/locale";
import { getTheme, type Theme } from "@/utils/theme";
import appCss from "../styles/globals.css?url";
type RouterContext = {
+6 -5
View File
@@ -10,6 +10,7 @@ import {
WrenchIcon,
} from "@phosphor-icons/react";
import { motion } from "motion/react";
import { Button } from "@/components/ui/button";
import { cn } from "@/utils/style";
@@ -115,7 +116,7 @@ const FeatureCard = ({ icon: Icon, title, description, delay }: FeatureCardProps
<Icon size={24} weight="light" />
</motion.div>
<h3 className="font-semibold tracking-tight">{title}</h3>
<p className="text-muted-foreground leading-relaxed">{description}</p>
<p className="leading-relaxed text-muted-foreground">{description}</p>
</motion.div>
);
@@ -135,7 +136,7 @@ export const DonationBanner = () => (
<div className="absolute -inset-e-32 bottom-1/4 size-64 rounded-full bg-rose-500/5 blur-3xl" />
</div>
<div className="container relative px-8">
<div className="relative container px-8">
{/* Header */}
<motion.div
className="flex flex-col items-center text-center"
@@ -151,7 +152,7 @@ export const DonationBanner = () => (
</div>
<motion.h2
className="mb-6 font-semibold text-2xl tracking-tight md:text-4xl xl:text-5xl"
className="mb-6 text-2xl font-semibold tracking-tight md:text-4xl xl:text-5xl"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
@@ -161,7 +162,7 @@ export const DonationBanner = () => (
</motion.h2>
<motion.p
className="max-w-3xl text-base text-muted-foreground leading-relaxed"
className="max-w-3xl text-base leading-relaxed text-muted-foreground"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
@@ -233,7 +234,7 @@ export const DonationBanner = () => (
{/* Footer note */}
<motion.p
className="mt-8 text-center text-muted-foreground leading-relaxed"
className="mt-8 text-center leading-relaxed text-muted-foreground"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
+3 -2
View File
@@ -1,6 +1,7 @@
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { motion } from "motion/react";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/utils/style";
@@ -69,7 +70,7 @@ export function FAQ() {
>
<motion.h2
className={cn(
"flex-1 font-semibold text-2xl tracking-tight md:text-4xl xl:text-5xl",
"flex-1 text-2xl font-semibold tracking-tight md:text-4xl xl:text-5xl",
"flex shrink-0 flex-wrap items-center gap-x-1.5 lg:flex-col lg:items-start",
)}
initial={{ opacity: 0, x: -20 }}
@@ -117,7 +118,7 @@ function FAQItemComponent({ item, index }: FAQItemComponentProps) {
>
<AccordionItem value={item.question} className="group border-t">
<AccordionTrigger className="py-5">{item.question}</AccordionTrigger>
<AccordionContent className="pb-5 text-muted-foreground leading-relaxed">{item.answer}</AccordionContent>
<AccordionContent className="pb-5 leading-relaxed text-muted-foreground">{item.answer}</AccordionContent>
</AccordionItem>
</motion.div>
);
+6 -5
View File
@@ -20,6 +20,7 @@ import {
TranslateIcon,
} from "@phosphor-icons/react";
import { motion } from "motion/react";
import { cn } from "@/utils/style";
type Feature = {
@@ -160,8 +161,8 @@ function FeatureCard({ icon: Icon, title, description, index }: FeatureCardProps
{/* Content */}
<div className="relative flex flex-col gap-y-1.5">
<h3 className="font-semibold text-base tracking-tight transition-colors group-hover:text-primary">{title}</h3>
<p className="text-muted-foreground text-sm leading-relaxed">{description}</p>
<h3 className="text-base font-semibold tracking-tight transition-colors group-hover:text-primary">{title}</h3>
<p className="text-sm leading-relaxed text-muted-foreground">{description}</p>
</div>
</motion.div>
);
@@ -178,11 +179,11 @@ export function Features() {
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<h2 className="font-semibold text-2xl tracking-tight md:text-4xl xl:text-5xl">
<h2 className="text-2xl font-semibold tracking-tight md:text-4xl xl:text-5xl">
<Trans>Features</Trans>
</h2>
<p className="max-w-2xl text-muted-foreground leading-relaxed">
<p className="max-w-2xl leading-relaxed text-muted-foreground">
<Trans>
Everything you need to create, customize, and share professional resumes. Built with privacy in mind,
powered by open source, and completely free forever.
@@ -191,7 +192,7 @@ export function Features() {
</motion.div>
{/* Features Grid */}
<div className="grid grid-cols-1 xs:grid-cols-2 border-t xl:grid-cols-4">
<div className="xs:grid-cols-2 grid grid-cols-1 border-t xl:grid-cols-4">
{getFeatures().map((feature, index) => (
<FeatureCard key={feature.id} {...feature} index={index} />
))}
+6 -4
View File
@@ -1,9 +1,11 @@
import type { Icon } from "@phosphor-icons/react";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import type { Icon } from "@phosphor-icons/react";
import { GithubLogoIcon, LinkedinLogoIcon, XLogoIcon } from "@phosphor-icons/react";
import { motion } from "motion/react";
import { useState } from "react";
import { BrandIcon } from "@/components/ui/brand-icon";
import { Button } from "@/components/ui/button";
import { Copyright } from "@/components/ui/copyright";
@@ -60,8 +62,8 @@ export function Footer() {
<BrandIcon variant="logo" className="size-10" />
<div className="space-y-2">
<h2 className="font-bold text-lg tracking-tight">Reactive Resume</h2>
<p className="max-w-xs text-muted-foreground text-sm leading-relaxed">
<h2 className="text-lg font-bold tracking-tight">Reactive Resume</h2>
<p className="max-w-xs text-sm leading-relaxed text-muted-foreground">
<Trans>
A free and open-source resume builder that simplifies the process of creating, updating, and sharing
your resume.
@@ -110,7 +112,7 @@ export function Footer() {
function FooterLinkGroup({ title, links }: FooterLinkGroupProps) {
return (
<div className="space-y-4">
<h2 className="font-medium text-muted-foreground text-sm tracking-tight">{title}</h2>
<h2 className="text-sm font-medium tracking-tight text-muted-foreground">{title}</h2>
<ul className="space-y-3">
{links.map((link) => (
+3 -1
View File
@@ -3,11 +3,13 @@ import { ArrowRightIcon, TranslateIcon } from "@phosphor-icons/react";
import { Link } from "@tanstack/react-router";
import { motion, useMotionValue, useSpring } from "motion/react";
import { useEffect, useRef } from "react";
import { GithubStarsButton } from "@/components/input/github-stars-button";
import { LocaleCombobox } from "@/components/locale/combobox";
import { ThemeToggleButton } from "@/components/theme/toggle-button";
import { BrandIcon } from "@/components/ui/brand-icon";
import { Button } from "@/components/ui/button";
import { ProductHuntBanner } from "./product-hunt-banner";
export function Header() {
@@ -44,7 +46,7 @@ export function Header() {
return (
<motion.header
style={{ y: springY }}
className="fixed inset-x-0 top-0 z-50 border-transparent border-b bg-background/80 backdrop-blur-lg transition-colors"
className="fixed inset-x-0 top-0 z-50 border-b border-transparent bg-background/80 backdrop-blur-lg transition-colors"
initial={{ y: -100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.5, ease: "easeOut" }}
+7 -6
View File
@@ -3,6 +3,7 @@ import { Trans } from "@lingui/react/macro";
import { ArrowRightIcon, BookIcon, SparkleIcon } from "@phosphor-icons/react";
import { Link } from "@tanstack/react-router";
import { motion } from "motion/react";
import { CometCard } from "@/components/animation/comet-card";
import { Spotlight } from "@/components/animation/spotlight";
import { Badge } from "@/components/ui/badge";
@@ -21,7 +22,7 @@ export function Hero() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 1.5, ease: "easeOut" }}
>
<CometCard glareOpacity={0} className="relative -mb-12 3xl:max-w-7xl max-w-4xl px-8 md:-mb-24 md:px-12 lg:px-0">
<CometCard glareOpacity={0} className="3xl:max-w-7xl relative -mb-12 max-w-4xl px-8 md:-mb-24 md:px-12 lg:px-0">
<video
loop
muted
@@ -36,12 +37,12 @@ export function Hero() {
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 bg-linear-to-b from-transparent via-40% via-transparent to-background"
className="pointer-events-none absolute inset-0 bg-linear-to-b from-transparent via-transparent via-40% to-background"
/>
</CometCard>
</motion.div>
<div className="relative z-10 flex max-w-2xl flex-col items-center gap-y-6 px-4 xs:px-0 text-center">
<div className="xs:px-0 relative z-10 flex max-w-2xl flex-col items-center gap-y-6 px-4 text-center">
{/* Badge */}
<motion.a
initial={{ opacity: 0, y: 20 }}
@@ -64,8 +65,8 @@ export function Hero() {
transition={{ duration: 0.6, delay: 1 }}
>
<Trans>
<p className="font-medium text-muted-foreground tracking-tight md:text-lg">Finally,</p>
<h1 className="mt-1 font-bold text-4xl tracking-tight md:text-5xl lg:text-6xl">
<p className="font-medium tracking-tight text-muted-foreground md:text-lg">Finally,</p>
<h1 className="mt-1 text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl">
A free and open-source resume builder
</h1>
</Trans>
@@ -73,7 +74,7 @@ export function Hero() {
{/* Description */}
<motion.p
className="max-w-xl text-base text-muted-foreground leading-relaxed md:text-lg"
className="max-w-xl text-base leading-relaxed text-muted-foreground md:text-lg"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 1.2 }}
+3 -2
View File
@@ -1,5 +1,6 @@
import { Trans } from "@lingui/react/macro";
import { motion } from "motion/react";
import { TextMaskEffect } from "@/components/animation/text-mask";
export function Prefooter() {
@@ -21,11 +22,11 @@ export function Prefooter() {
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<h2 className="font-bold text-2xl tracking-tight md:text-4xl">
<h2 className="text-2xl font-bold tracking-tight md:text-4xl">
<Trans>By the community, for the community.</Trans>
</h2>
<p className="text-muted-foreground leading-relaxed">
<p className="leading-relaxed text-muted-foreground">
<Trans>
Reactive Resume continues to grow thanks to its vibrant community. This project owes its progress to
numerous individuals who've dedicated their time and skills to make it better. We celebrate the coders
@@ -29,7 +29,7 @@ export function ProductHuntBanner() {
<a
target="_blank"
rel="noopener"
className="flex h-8 items-center justify-center bg-secondary text-center font-medium text-[0.85rem] text-secondary-foreground tracking-tight underline-offset-2 hover:underline"
className="flex h-8 items-center justify-center bg-secondary text-center text-[0.85rem] font-medium tracking-tight text-secondary-foreground underline-offset-2 hover:underline"
href="https://www.producthunt.com/products/reactive-resume/launches/reactive-resume-v5-2?embed=true&amp;utm_source=badge-featured&amp;utm_medium=badge&amp;utm_campaign=badge-reactive-resume-v5-2"
>
<Trans>Reactive Resume is launching on Product Hunt today, head over to show some love!</Trans>
+5 -3
View File
@@ -1,8 +1,10 @@
import { t } from "@lingui/core/macro";
import type { Icon } from "@phosphor-icons/react";
import { t } from "@lingui/core/macro";
import { FileTextIcon, UsersIcon } from "@phosphor-icons/react";
import { useQueries } from "@tanstack/react-query";
import { motion } from "motion/react";
import { CountUp } from "@/components/animation/count-up";
import { orpc } from "@/integrations/orpc/client";
@@ -73,11 +75,11 @@ function StatisticCard({ statistic, index }: StatisticCardProps) {
separator=","
duration={0.8}
to={statistic.value}
className="font-bold text-5xl tracking-tight md:text-6xl"
className="text-5xl font-bold tracking-tight md:text-6xl"
/>
{/* Label */}
<p className="relative font-medium text-base text-muted-foreground tracking-tight">{statistic.label}</p>
<p className="relative text-base font-medium tracking-tight text-muted-foreground">{statistic.label}</p>
</motion.div>
);
}
+4 -2
View File
@@ -1,7 +1,9 @@
import { Trans } from "@lingui/react/macro";
import { motion } from "motion/react";
import { useMemo } from "react";
import type { TemplateMetadata } from "@/dialogs/resume/template/data";
import { templates } from "@/dialogs/resume/template/data";
type TemplateItemProps = {
@@ -88,11 +90,11 @@ export function Templates() {
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<h2 className="font-semibold text-2xl tracking-tight md:text-4xl xl:text-5xl">
<h2 className="text-2xl font-semibold tracking-tight md:text-4xl xl:text-5xl">
<Trans>Templates</Trans>
</h2>
<p className="max-w-2xl text-muted-foreground leading-relaxed">
<p className="max-w-2xl leading-relaxed text-muted-foreground">
<Trans>
Explore our diverse selection of templates, each designed to fit different styles, professions, and
personalities. Reactive Resume currently offers 12 templates, with more on the way.
+3 -3
View File
@@ -41,7 +41,7 @@ function TestimonialCard({ testimonial }: TestimonialCardProps) {
transition={{ type: "spring", stiffness: 300, damping: 20 }}
>
<div className="relative flex h-full flex-col rounded-xl border bg-card p-5 shadow-sm transition-shadow duration-300 group-hover:shadow-xl">
<p className="flex-1 text-muted-foreground leading-relaxed">"{testimonial}"</p>
<p className="flex-1 leading-relaxed text-muted-foreground">"{testimonial}"</p>
</div>
</motion.div>
);
@@ -91,11 +91,11 @@ export function Testimonials() {
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<h2 className="font-semibold text-2xl tracking-tight md:text-4xl xl:text-5xl">
<h2 className="text-2xl font-semibold tracking-tight md:text-4xl xl:text-5xl">
<Trans>Testimonials</Trans>
</h2>
<p className="max-w-4xl text-balance text-muted-foreground leading-relaxed">
<p className="max-w-4xl leading-relaxed text-balance text-muted-foreground">
<Trans>
A lot of people have written to me over the years to share their experiences with Reactive Resume and how it
has helped them, and I never get tired of reading them. If you have a story to share, let me know by sending
+2 -1
View File
@@ -1,4 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
import { DonationBanner } from "./-sections/donate";
import { FAQ } from "./-sections/faq";
import { Features } from "./-sections/features";
@@ -19,7 +20,7 @@ function RouteComponent() {
<Hero />
<div className="container mx-auto px-4 sm:px-6 lg:px-12">
<div className="border-border border-x [&>section:first-child]:border-t-0 [&>section]:border-border [&>section]:border-t">
<div className="border-x border-border [&>section]:border-t [&>section]:border-border [&>section:first-child]:border-t-0">
<Statistics />
<Features />
<Templates />
+1
View File
@@ -1,5 +1,6 @@
import { Trans } from "@lingui/react/macro";
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { Header } from "./-sections/header";
export const Route = createFileRoute("/_home")({
+1
View File
@@ -1,4 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
import { auth } from "@/integrations/auth/config";
async function handler({ request }: { request: Request }) {
+1
View File
@@ -1,5 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
import { sql } from "drizzle-orm";
import { db } from "@/integrations/drizzle/client";
import { printerService } from "@/integrations/orpc/services/printer";
import { getStorageService } from "@/integrations/orpc/services/storage";
+1
View File
@@ -5,6 +5,7 @@ import { onError } from "@orpc/server";
import { BatchHandlerPlugin, RequestHeadersPlugin, StrictGetMethodPlugin } from "@orpc/server/plugins";
import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
import { createFileRoute } from "@tanstack/react-router";
import router from "@/integrations/orpc/router";
import { resumeDataSchema } from "@/schema/resume/data";
import { env } from "@/utils/env";
+1
View File
@@ -2,6 +2,7 @@ import { onError } from "@orpc/server";
import { RPCHandler } from "@orpc/server/fetch";
import { BatchHandlerPlugin, RequestHeadersPlugin, StrictGetMethodPlugin } from "@orpc/server/plugins";
import { createFileRoute } from "@tanstack/react-router";
import router from "@/integrations/orpc/router";
import { getLocale } from "@/utils/locale";
import { logger } from "@/utils/logger";
+4 -3
View File
@@ -4,6 +4,7 @@ import { GithubLogoIcon, GoogleLogoIcon, VaultIcon } from "@phosphor-icons/react
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "@tanstack/react-router";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { authClient } from "@/integrations/auth/client";
import { orpc } from "@/integrations/orpc/client";
@@ -27,7 +28,7 @@ export function SocialAuth() {
}
toast.dismiss(toastId);
router.invalidate();
await router.invalidate();
};
const handleOAuthLogin = async () => {
@@ -44,14 +45,14 @@ export function SocialAuth() {
}
toast.dismiss(toastId);
router.invalidate();
await router.invalidate();
};
return (
<>
<div className="flex items-center gap-x-2">
<hr className="flex-1" />
<span className="font-medium text-xs tracking-wide">
<span className="text-xs font-medium tracking-wide">
<Trans context="Choose to authenticate with a social provider (Google, GitHub, etc.) instead of email and password">
or continue with
</Trans>
+3 -2
View File
@@ -7,6 +7,7 @@ import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
@@ -57,7 +58,7 @@ function RouteComponent() {
return (
<>
<div className="space-y-1 text-center">
<h1 className="font-bold text-2xl tracking-tight">
<h1 className="text-2xl font-bold tracking-tight">
<Trans>Forgot your password?</Trans>
</h1>
@@ -109,7 +110,7 @@ function PostForgotPasswordScreen() {
return (
<>
<div className="space-y-1 text-center">
<h1 className="font-bold text-2xl tracking-tight">
<h1 className="text-2xl font-bold tracking-tight">
<Trans>You've got mail!</Trans>
</h1>
<p className="text-muted-foreground">
+6 -4
View File
@@ -7,10 +7,12 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { useToggle } from "usehooks-ts";
import z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { authClient } from "@/integrations/auth/client";
import { SocialAuth } from "./-components/social-auth";
export const Route = createFileRoute("/auth/login")({
@@ -66,14 +68,14 @@ function RouteComponent() {
// Credential check passed, but the account still requires a 2FA verification step.
if (requiresTwoFactor) {
toast.dismiss(toastId);
navigate({ to: "/auth/verify-2fa", replace: true });
void navigate({ to: "/auth/verify-2fa", replace: true });
return;
}
// Refresh route context so protected routes can read the newly established session.
await router.invalidate();
toast.dismiss(toastId);
navigate({ to: "/dashboard", replace: true });
await router.invalidate();
void navigate({ to: "/dashboard", replace: true });
} catch {
toast.error(t`Failed to sign in. Please try again.`, { id: toastId });
}
@@ -82,7 +84,7 @@ function RouteComponent() {
return (
<>
<div className="space-y-1 text-center">
<h1 className="font-bold text-2xl tracking-tight">
<h1 className="text-2xl font-bold tracking-tight">
<Trans>Sign in to your account</Trans>
</h1>
+4 -2
View File
@@ -8,11 +8,13 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { useToggle } from "usehooks-ts";
import z from "zod";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { authClient } from "@/integrations/auth/client";
import { SocialAuth } from "./-components/social-auth";
export const Route = createFileRoute("/auth/register")({
@@ -82,7 +84,7 @@ function RouteComponent() {
return (
<>
<div className="space-y-1 text-center">
<h1 className="font-bold text-2xl tracking-tight">
<h1 className="text-2xl font-bold tracking-tight">
<Trans>Create a new account</Trans>
</h1>
@@ -219,7 +221,7 @@ function PostSignupScreen() {
return (
<>
<div className="space-y-1 text-center">
<h1 className="font-bold text-2xl tracking-tight">
<h1 className="text-2xl font-bold tracking-tight">
<Trans>You've got mail!</Trans>
</h1>
<p className="text-muted-foreground">
+4 -2
View File
@@ -8,6 +8,7 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { useToggle } from "usehooks-ts";
import z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
@@ -59,13 +60,14 @@ function RouteComponent() {
toast.success(t`Your password has been reset successfully. You can now sign in with your new password.`, {
id: toastId,
});
navigate({ to: "/auth/login" });
void navigate({ to: "/auth/login" });
};
return (
<>
<div className="space-y-1 text-center">
<h1 className="font-bold text-2xl tracking-tight">
<h1 className="text-2xl font-bold tracking-tight">
<Trans>Reset your password</Trans>
</h1>
+4 -3
View File
@@ -11,6 +11,7 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { useToggle } from "usehooks-ts";
import z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
@@ -67,7 +68,7 @@ function RouteComponent() {
{
onSuccess: () => {
toast.dismiss(toastId);
navigate({ to: redirect, replace: true });
void navigate({ to: redirect, replace: true });
},
onError: (error) => {
if (error instanceof ORPCError && error.code === "INVALID_PASSWORD") {
@@ -84,11 +85,11 @@ function RouteComponent() {
return (
<>
<div className="space-y-4 text-center">
<h1 className="font-bold text-2xl tracking-tight">
<h1 className="text-2xl font-bold tracking-tight">
<Trans>The resume you are trying to access is password protected</Trans>
</h1>
<div className="text-muted-foreground leading-relaxed">
<div className="leading-relaxed text-muted-foreground">
<Trans>Please enter the password shared with you by the owner of the resume to continue.</Trans>
</div>
</div>
+2 -1
View File
@@ -1,4 +1,5 @@
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { BrandIcon } from "@/components/ui/brand-icon";
export const Route = createFileRoute("/auth")({
@@ -7,7 +8,7 @@ export const Route = createFileRoute("/auth")({
function RouteComponent() {
return (
<div className="mx-auto flex h-svh w-dvw max-w-sm flex-col justify-center space-y-6 px-4 xs:px-0">
<div className="xs:px-0 mx-auto flex h-svh w-dvw max-w-sm flex-col justify-center space-y-6 px-4">
<BrandIcon className="mb-4 size-20 self-center" />
<Outlet />
+4 -3
View File
@@ -7,6 +7,7 @@ import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form";
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/ui/input-otp";
@@ -48,14 +49,14 @@ function RouteComponent() {
}
toast.dismiss(toastId);
router.invalidate();
navigate({ to: "/dashboard", replace: true });
await router.invalidate();
void navigate({ to: "/dashboard", replace: true });
};
return (
<>
<div className="space-y-1 text-center">
<h1 className="font-bold text-2xl tracking-tight">
<h1 className="text-2xl font-bold tracking-tight">
<Trans>Verify with a Backup Code</Trans>
</h1>
<div className="text-muted-foreground">
+4 -3
View File
@@ -7,6 +7,7 @@ import { REGEXP_ONLY_DIGITS } from "input-otp";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form";
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "@/components/ui/input-otp";
@@ -48,15 +49,15 @@ function RouteComponent() {
return;
}
router.invalidate();
toast.dismiss(toastId);
navigate({ to: "/dashboard", replace: true });
await router.invalidate();
void navigate({ to: "/dashboard", replace: true });
};
return (
<>
<div className="space-y-1 text-center">
<h1 className="font-bold text-2xl tracking-tight">
<h1 className="text-2xl font-bold tracking-tight">
<Trans>Two-Factor Authentication</Trans>
</h1>
<div className="text-muted-foreground">
@@ -19,6 +19,7 @@ import { useHotkeys } from "react-hotkeys-hook";
import { useControls } from "react-zoom-pan-pinch";
import { toast } from "sonner";
import { useCopyToClipboard } from "usehooks-ts";
import { AIChat } from "@/components/ai/chat";
import { useTemporalStore } from "@/components/resume/store/resume";
import { Button } from "@/components/ui/button";
@@ -82,7 +83,7 @@ export function BuilderDock() {
try {
const { url } = await printResumeAsPDF({ id: resume.id });
downloadFromUrl(url, filename);
await downloadFromUrl(url, filename);
} catch {
toast.error(t`There was a problem while generating the PDF, please try again in some time.`);
} finally {
@@ -97,7 +98,7 @@ export function BuilderDock() {
animate={{ opacity: 0.5, y: 0 }}
whileHover={{ opacity: 1 }}
transition={{ duration: 0.2 }}
className="flex items-center rounded-r-full rounded-l-full bg-popover px-2 shadow-xl"
className="flex items-center rounded-l-full rounded-r-full bg-popover px-2 shadow-xl"
>
<DockIcon
disabled={!canUndo}
@@ -13,6 +13,7 @@ import {
import { useMutation } from "@tanstack/react-query";
import { Link, useNavigate } from "@tanstack/react-router";
import { toast } from "sonner";
import { useResumeStore } from "@/components/resume/store/resume";
import { Button } from "@/components/ui/button";
import {
@@ -25,6 +26,7 @@ import {
import { useDialogStore } from "@/dialogs/store";
import { useConfirm } from "@/hooks/use-confirm";
import { orpc } from "@/integrations/orpc/client";
import { useBuilderSidebar } from "../-store/sidebar";
export function BuilderHeader() {
@@ -117,7 +119,7 @@ function BuilderHeaderDropdown() {
{
onSuccess: () => {
toast.success(t`Your resume has been deleted successfully.`, { id: toastId });
navigate({ to: "/dashboard/resumes", search: { sort: "lastUpdatedAt", tags: [] } });
void navigate({ to: "/dashboard/resumes", search: { sort: "lastUpdatedAt", tags: [] } });
},
onError: (error) => {
toast.error(error.message, { id: toastId });
@@ -1,5 +1,6 @@
import { Fragment, useCallback, useRef } from "react";
import { match } from "ts-pattern";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
@@ -7,6 +8,7 @@ import { Separator } from "@/components/ui/separator";
import { UserDropdownMenu } from "@/components/user/dropdown-menu";
import { getSectionIcon, getSectionTitle, type LeftSidebarSection, leftSidebarSections } from "@/utils/resume/section";
import { getInitials } from "@/utils/string";
import { BuilderSidebarEdge } from "../../-components/edge";
import { useBuilderSidebar } from "../../-store/sidebar";
import { AwardsSectionBuilder } from "./sections/awards";
@@ -89,7 +91,7 @@ function SidebarEdge({ scrollAreaRef }: SidebarEdgeProps) {
return (
<BuilderSidebarEdge side="left">
<div className="flex min-h-0 w-full flex-1 flex-col items-center gap-y-2 overflow-hidden">
<div className="no-scrollbar min-h-0 w-full flex-1 overflow-y-auto overflow-x-hidden">
<div className="no-scrollbar min-h-0 w-full flex-1 overflow-x-hidden overflow-y-auto">
<div className="flex min-h-full flex-col items-center justify-center gap-y-2">
{leftSidebarSections.map((section) => (
<Button
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { awardItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,12 +1,15 @@
import type z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Trans } from "@lingui/react/macro";
import { useForm } from "react-hook-form";
import type z from "zod";
import { URLInput } from "@/components/input/url-input";
import { useResumeStore } from "@/components/resume/store/resume";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { basicsSchema } from "@/schema/resume/data";
import { SectionBase } from "../shared/section-base";
import { CustomFieldsSection } from "./custom-fields";
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { certificationItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,15 +1,18 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { DotsSixVerticalIcon, LinkIcon, ListPlusIcon, XIcon } from "@phosphor-icons/react";
import { Reorder, useDragControls } from "motion/react";
import { Controller, useFieldArray, useFormContext, useWatch } from "react-hook-form";
import type z from "zod";
import type { basicsSchema } from "@/schema/resume/data";
import { IconPicker } from "@/components/input/icon-picker";
import { Button } from "@/components/ui/button";
import { FormControl, FormField, FormItem } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import type { basicsSchema } from "@/schema/resume/data";
import { generateId } from "@/utils/string";
type FormValues = z.infer<typeof basicsSchema>;
@@ -34,17 +37,17 @@ export function CustomFieldsSection({ onSubmit }: Props) {
const currentFieldsMap = Object.fromEntries(customFields.map((f) => [f.id, f]));
const reordered = newFields.map((field) => currentFieldsMap[field.id] ?? field);
form.setValue("customFields", reordered);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}
function handleRemove(index: number) {
customFieldsArray.remove(index);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}
function handleAdd() {
customFieldsArray.append({ id: generateId(), icon: "acorn", text: "", link: "" });
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}
return (
@@ -63,7 +66,7 @@ export function CustomFieldsSection({ onSubmit }: Props) {
className="rounded-r-none! border-e-0!"
onChange={(icon) => {
field.onChange(icon);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -84,7 +87,7 @@ export function CustomFieldsSection({ onSubmit }: Props) {
className="rounded-l-none!"
onChange={(e) => {
field.onChange(e.target.value);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -104,7 +107,7 @@ export function CustomFieldsSection({ onSubmit }: Props) {
<PopoverContent align="center">
<div className="flex flex-col gap-y-1.5">
<Label htmlFor={`customFields.${index}.link`} className="text-muted-foreground text-xs">
<Label htmlFor={`customFields.${index}.link`} className="text-xs text-muted-foreground">
<Trans>Enter the URL to link to</Trans>
</Label>
@@ -119,7 +122,7 @@ export function CustomFieldsSection({ onSubmit }: Props) {
placeholder="Must start with https://"
onChange={(e) => {
field.onChange(e.target.value);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
)}
@@ -10,6 +10,13 @@ import {
} from "@phosphor-icons/react";
import { AnimatePresence, Reorder } from "motion/react";
import { match } from "ts-pattern";
import type {
CustomSection,
CustomSectionItem as CustomSectionItemType,
CustomSectionType,
} from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { Badge } from "@/components/ui/badge";
import {
@@ -27,14 +34,10 @@ import {
} from "@/components/ui/dropdown-menu";
import { useDialogStore } from "@/dialogs/store";
import { useConfirm } from "@/hooks/use-confirm";
import type {
CustomSection,
CustomSectionItem as CustomSectionItemType,
CustomSectionType,
} from "@/schema/resume/data";
import { getSectionTitle } from "@/utils/resume/section";
import { stripHtml } from "@/utils/string";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -144,8 +147,8 @@ function CustomSectionContainer({ section }: { section: CustomSection }) {
<Badge variant="secondary" className="mb-1.5 rounded-sm">
{getSectionTitle(section.type)}
</Badge>
<span className="line-clamp-1 text-wrap font-medium text-base">{section.title}</span>
<span className="text-muted-foreground text-xs">
<span className="line-clamp-1 text-base font-medium text-wrap">{section.title}</span>
<span className="text-xs text-muted-foreground">
<Plural value={section.items.length} one="# item" other="# items" />
</span>
</button>
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { educationItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,10 +1,14 @@
import type z from "zod";
import { plural } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { experienceItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { interestItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { languageItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,3 +1,5 @@
import type z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
@@ -6,7 +8,7 @@ import { useMutation } from "@tanstack/react-query";
import { useRef } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import type z from "zod";
import { ColorPicker } from "@/components/input/color-picker";
import { useResumeStore } from "@/components/resume/store/resume";
import { Button } from "@/components/ui/button";
@@ -16,6 +18,7 @@ import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@/components/ui/input-group";
import { orpc } from "@/integrations/orpc/client";
import { pictureSchema } from "@/schema/resume/data";
import { SectionBase } from "../shared/section-base";
export function PictureSectionBuilder() {
@@ -65,7 +68,7 @@ function PictureSectionForm() {
if (pictureOrigin === appOrigin) deleteFile({ filename });
form.setValue("url", "", { shouldDirty: true });
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
};
const onUploadPicture = async (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -77,7 +80,7 @@ function PictureSectionForm() {
uploadFile(file, {
onSuccess: ({ url }) => {
form.setValue("url", url, { shouldDirty: true });
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
toast.dismiss(toastId);
if (fileInputRef.current) fileInputRef.current.value = "";
},
@@ -101,7 +104,7 @@ function PictureSectionForm() {
<img
alt=""
src={picture.url}
className="fade-in relative z-10 size-full animate-in rounded-md object-cover transition-opacity group-hover/picture:opacity-20"
className="relative z-10 size-full animate-in rounded-md object-cover transition-opacity fade-in group-hover/picture:opacity-20"
/>
)}
@@ -126,7 +129,7 @@ function PictureSectionForm() {
variant="ghost"
onClick={() => {
form.setValue("hidden", !picture.hidden, { shouldDirty: true });
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
>
{picture.hidden ? <EyeSlashIcon /> : <EyeIcon />}
@@ -137,7 +140,7 @@ function PictureSectionForm() {
/>
</div>
<div className="grid @md:grid-cols-2 grid-cols-1 gap-4">
<div className="grid grid-cols-1 gap-4 @md:grid-cols-2">
<FormField
control={form.control}
name="size"
@@ -235,7 +238,7 @@ function PictureSectionForm() {
title={t`Square`}
onClick={() => {
field.onChange(1);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
>
<div className="aspect-square min-h-3 min-w-3 border border-primary" />
@@ -246,7 +249,7 @@ function PictureSectionForm() {
title={t`Landscape`}
onClick={() => {
field.onChange(1.5);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
>
<div className="aspect-1.5/1 min-h-3 min-w-3 border border-primary" />
@@ -257,7 +260,7 @@ function PictureSectionForm() {
title={t`Portrait`}
onClick={() => {
field.onChange(0.5);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
>
<div className="aspect-1/1.5 min-h-3 min-w-3 border border-primary" />
@@ -303,7 +306,7 @@ function PictureSectionForm() {
title="0pt"
onClick={() => {
field.onChange(0);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
>
<div className="size-3 rounded-none border border-primary" />
@@ -314,7 +317,7 @@ function PictureSectionForm() {
title="10pt"
onClick={() => {
field.onChange(10);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
>
<div className="size-3 rounded-[10%] border border-primary" />
@@ -325,7 +328,7 @@ function PictureSectionForm() {
title="100pt"
onClick={() => {
field.onChange(100);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
>
<div className="size-3 rounded-full border border-primary" />
@@ -348,7 +351,7 @@ function PictureSectionForm() {
defaultValue={field.value}
onChange={(color) => {
field.onChange(color);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -402,7 +405,7 @@ function PictureSectionForm() {
defaultValue={field.value}
onChange={(color) => {
field.onChange(color);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { profileItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { projectItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { publicationItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { referenceItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { skillItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,5 +1,6 @@
import { RichInput } from "@/components/input/rich-input";
import { useResumeStore } from "@/components/resume/store/resume";
import { SectionBase } from "../shared/section-base";
export function SummarySectionBuilder() {
@@ -1,9 +1,13 @@
import type z from "zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, Reorder } from "motion/react";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import type { volunteerItemSchema } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
import { SectionAddItemButton, SectionItem } from "../shared/section-item";
@@ -1,10 +1,13 @@
import { CaretDownIcon } from "@phosphor-icons/react";
import type { SectionType } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import type { SectionType } from "@/schema/resume/data";
import { getSectionIcon, getSectionTitle, type LeftSidebarSection } from "@/utils/resume/section";
import { cn } from "@/utils/style";
import { useSectionStore } from "../../../-store/section";
import { SectionDropdownMenu } from "./section-menu";
@@ -45,7 +48,7 @@ export function SectionBase({ type, className, ...props }: Props) {
<div className="flex flex-1 items-center gap-x-4">
{getSectionIcon(type)}
<h2 className="line-clamp-1 font-bold text-2xl tracking-tight">
<h2 className="line-clamp-1 text-2xl font-bold tracking-tight">
{("title" in section && section.title) || getSectionTitle(type)}
</h2>
</div>
@@ -15,6 +15,14 @@ import {
} from "@phosphor-icons/react";
import { Reorder, useDragControls } from "motion/react";
import { useMemo } from "react";
import type {
CustomSectionItem,
CustomSectionType,
SectionItem as SectionItemType,
SectionType,
} from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { Button, type ButtonProps } from "@/components/ui/button";
import {
@@ -30,12 +38,6 @@ import {
} from "@/components/ui/dropdown-menu";
import { useDialogStore } from "@/dialogs/store";
import { useConfirm } from "@/hooks/use-confirm";
import type {
CustomSectionItem,
CustomSectionType,
SectionItem as SectionItemType,
SectionType,
} from "@/schema/resume/data";
import {
addItemToSection,
createCustomSectionWithItem,
@@ -242,10 +244,10 @@ export function SectionItem<T extends CustomSectionItem | SectionItemType>({
initial={{ opacity: 1, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className="group relative flex h-18 select-none border-b"
className="group relative flex h-18 border-b select-none"
>
<div
className="flex cursor-ns-resize touch-none items-center px-1.5 opacity-40 transition-[background-color,opacity] hover:bg-secondary/40 group-hover:opacity-100"
className="flex cursor-ns-resize touch-none items-center px-1.5 opacity-40 transition-[background-color,opacity] group-hover:opacity-100 hover:bg-secondary/40"
onPointerDown={(e) => {
e.preventDefault();
controls.start(e);
@@ -262,11 +264,11 @@ export function SectionItem<T extends CustomSectionItem | SectionItemType>({
)}
>
<div className="line-clamp-1 font-medium">{title}</div>
{subtitle && <div className="line-clamp-1 text-muted-foreground text-xs">{subtitle}</div>}
{subtitle && <div className="line-clamp-1 text-xs text-muted-foreground">{subtitle}</div>}
</button>
<DropdownMenu>
<DropdownMenuTrigger className="flex cursor-context-menu items-center px-1.5 opacity-40 transition-[background-color,opacity] hover:bg-secondary/40 focus:outline-none focus-visible:ring-1 group-hover:opacity-100">
<DropdownMenuTrigger className="flex cursor-context-menu items-center px-1.5 opacity-40 transition-[background-color,opacity] group-hover:opacity-100 hover:bg-secondary/40 focus:outline-none focus-visible:ring-1">
<DotsThreeVerticalIcon />
</DropdownMenuTrigger>
@@ -9,6 +9,9 @@ import {
PencilSimpleLineIcon,
PlusIcon,
} from "@phosphor-icons/react";
import type { SectionType } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { Button } from "@/components/ui/button";
import {
@@ -27,7 +30,6 @@ import {
import { useDialogStore } from "@/dialogs/store";
import { useConfirm } from "@/hooks/use-confirm";
import { usePrompt } from "@/hooks/use-prompt";
import type { SectionType } from "@/schema/resume/data";
type Props = {
type: "summary" | SectionType;
@@ -1,5 +1,6 @@
import { Fragment, useCallback, useRef } from "react";
import { match } from "ts-pattern";
import { Button } from "@/components/ui/button";
import { Copyright } from "@/components/ui/copyright";
import { ScrollArea } from "@/components/ui/scroll-area";
@@ -10,6 +11,7 @@ import {
type RightSidebarSection,
rightSidebarSections,
} from "@/utils/resume/section";
import { BuilderSidebarEdge } from "../../-components/edge";
import { useBuilderSidebar } from "../../-store/sidebar";
import { CSSSectionBuilder } from "./sections/css";
@@ -86,7 +88,7 @@ function SidebarEdge({ scrollAreaRef }: SidebarEdgeProps) {
return (
<BuilderSidebarEdge side="right">
<div className="no-scrollbar min-h-0 w-full flex-1 overflow-y-auto overflow-x-hidden">
<div className="no-scrollbar min-h-0 w-full flex-1 overflow-x-hidden overflow-y-auto">
<div className="flex min-h-full flex-col items-center justify-center gap-y-2">
{rightSidebarSections.map((section) => (
<Button
@@ -1,5 +1,6 @@
import Editor, { useMonaco } from "@monaco-editor/react";
import { useEffect } from "react";
import type { Theme } from "@/utils/theme";
const CSS_SELECTORS = [
@@ -1,14 +1,17 @@
import type z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Trans } from "@lingui/react/macro";
import { lazy, Suspense } from "react";
import { useForm } from "react-hook-form";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import { useTheme } from "@/components/theme/provider";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import { metadataSchema } from "@/schema/resume/data";
import { SectionBase } from "../shared/section-base";
const CSSMonacoEditor = lazy(() => import("./css-editor"));
@@ -58,7 +61,7 @@ function CSSSectionForm() {
checked={field.value}
onCheckedChange={(checked) => {
field.onChange(checked);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -84,7 +87,7 @@ function CSSSectionForm() {
defaultValue={field.value}
onChange={(value) => {
field.onChange(value ?? "");
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
</Suspense>
@@ -1,8 +1,10 @@
import type z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Trans } from "@lingui/react/macro";
import { AnimatePresence, motion } from "motion/react";
import { useForm } from "react-hook-form";
import type z from "zod";
import { ColorPicker } from "@/components/input/color-picker";
import { IconPicker } from "@/components/input/icon-picker";
import { LevelTypeCombobox } from "@/components/level/combobox";
@@ -13,6 +15,7 @@ import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { colorDesignSchema, levelDesignSchema } from "@/schema/resume/data";
import { cn } from "@/utils/style";
import { SectionBase } from "../shared/section-base";
export function DesignSectionBuilder() {
@@ -56,7 +59,7 @@ function ColorSectionForm() {
active={color === field.value}
onSelect={(color) => {
field.onChange(color);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
))}
@@ -77,7 +80,7 @@ function ColorSectionForm() {
value={field.value}
onChange={(color) => {
field.onChange(color);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
<FormControl render={<Input {...field} />} />
@@ -100,7 +103,7 @@ function ColorSectionForm() {
defaultValue={field.value}
onChange={(color) => {
field.onChange(color);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
<FormControl render={<Input {...field} />} />
@@ -123,7 +126,7 @@ function ColorSectionForm() {
defaultValue={field.value}
onChange={(color) => {
field.onChange(color);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
<FormControl render={<Input {...field} />} />
@@ -218,7 +221,7 @@ function LevelSectionForm() {
return (
<Form {...form}>
<form onChange={form.handleSubmit(onSubmit)} className="space-y-4">
<h4 className="font-semibold text-lg leading-none tracking-tight">
<h4 className="text-lg leading-none font-semibold tracking-tight">
<Trans>Level</Trans>
</h4>
@@ -250,7 +253,7 @@ function LevelSectionForm() {
value={field.value}
onChange={(value) => {
field.onChange(value);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -274,7 +277,7 @@ function LevelSectionForm() {
onValueChange={(value) => {
if (!value) return;
field.onChange(value);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -4,10 +4,12 @@ import { CircleNotchIcon, FileJsIcon, FilePdfIcon } from "@phosphor-icons/react"
import { useMutation } from "@tanstack/react-query";
import { useCallback } from "react";
import { toast } from "sonner";
import { useResumeStore } from "@/components/resume/store/resume";
import { Button } from "@/components/ui/button";
import { orpc } from "@/integrations/orpc/client";
import { downloadFromUrl, downloadWithAnchor, generateFilename } from "@/utils/file";
import { SectionBase } from "../shared/section-base";
export function ExportSectionBuilder() {
@@ -33,7 +35,7 @@ export function ExportSectionBuilder() {
try {
const { url } = await printResumeAsPDF({ id: resume.id });
downloadFromUrl(url, filename);
await downloadFromUrl(url, filename);
} catch {
toast.error(t`There was a problem while generating the PDF, please try again in some time.`);
} finally {
@@ -46,12 +48,12 @@ export function ExportSectionBuilder() {
<Button
variant="outline"
onClick={onDownloadJSON}
className="h-auto gap-x-4 whitespace-normal p-4! text-start font-normal active:scale-98"
className="h-auto gap-x-4 p-4! text-start font-normal whitespace-normal active:scale-98"
>
<FileJsIcon className="size-6 shrink-0" />
<div className="flex flex-1 flex-col gap-y-1">
<h6 className="font-medium">JSON</h6>
<p className="text-muted-foreground text-xs leading-normal">
<p className="text-xs leading-normal text-muted-foreground">
<Trans>
Download a copy of your resume in JSON format. Use this file for backup or to import your resume into
other applications, including AI assistants.
@@ -64,7 +66,7 @@ export function ExportSectionBuilder() {
variant="outline"
disabled={isPrinting}
onClick={onDownloadPDF}
className="h-auto gap-x-4 whitespace-normal p-4! text-start font-normal active:scale-98"
className="h-auto gap-x-4 p-4! text-start font-normal whitespace-normal active:scale-98"
>
{isPrinting ? (
<CircleNotchIcon className="size-6 shrink-0 animate-spin" />
@@ -74,7 +76,7 @@ export function ExportSectionBuilder() {
<div className="flex flex-1 flex-col gap-y-1">
<h6 className="font-medium">PDF</h6>
<p className="text-muted-foreground text-xs leading-normal">
<p className="text-xs leading-normal text-muted-foreground">
<Trans>
Download a copy of your resume in PDF format. Use this file for printing or to easily share your resume
with recruiters.
@@ -1,6 +1,8 @@
import { Trans } from "@lingui/react/macro";
import { HandHeartIcon } from "@phosphor-icons/react";
import { Button } from "@/components/ui/button";
import { SectionBase } from "../shared/section-base";
export function InformationSectionBuilder() {
@@ -28,7 +30,7 @@ export function InformationSectionBuilder() {
size="sm"
variant="default"
nativeButton={false}
className="mt-2 whitespace-normal px-4! text-xs"
className="mt-2 px-4! text-xs whitespace-normal"
render={
<a href="http://opencollective.com/reactive-resume" target="_blank" rel="noopener">
<HandHeartIcon />
@@ -1,12 +1,15 @@
import type z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Trans } from "@lingui/react/macro";
import { useForm } from "react-hook-form";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@/components/ui/input-group";
import { Slider } from "@/components/ui/slider";
import { metadataSchema } from "@/schema/resume/data";
import { SectionBase } from "../../shared/section-base";
import { LayoutPages } from "./pages";
@@ -16,11 +16,13 @@ import { Trans } from "@lingui/react/macro";
import { DotsSixVerticalIcon, PlusIcon, TrashIcon } from "@phosphor-icons/react";
import { type CSSProperties, forwardRef, type HTMLAttributes, useCallback, useState } from "react";
import { match } from "ts-pattern";
import type { SectionType } from "@/schema/resume/data";
import { useResumeStore } from "@/components/resume/store/resume";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { templates } from "@/dialogs/resume/template/data";
import type { SectionType } from "@/schema/resume/data";
import { getSectionTitle } from "@/utils/resume/section";
import { cn } from "@/utils/style";
@@ -260,14 +262,14 @@ function PageContainer({
<div className="space-y-3 rounded-md border border-dashed bg-background/40">
<div className="flex items-center justify-between bg-secondary/50 px-4 py-3">
<div className="flex w-full items-center gap-4">
<span className="font-medium text-xs">
<span className="text-xs font-medium">
<Trans>Page {pageIndex + 1}</Trans>
</span>
<label className="flex cursor-pointer items-center gap-2">
<Switch checked={page.fullWidth} onCheckedChange={(checked) => onToggleFullWidth(pageIndex, checked)} />
<span className="font-medium text-muted-foreground text-xs">
<span className="text-xs font-medium text-muted-foreground">
<Trans>Full Width</Trans>
</span>
</label>
@@ -283,7 +285,7 @@ function PageContainer({
<div
className={cn(
"grid w-full @md:grid-cols-2 gap-x-4 gap-y-2 p-4 pt-0 font-medium",
"grid w-full gap-x-4 gap-y-2 p-4 pt-0 font-medium @md:grid-cols-2",
sidebarPosition === "none" && "@md:grid-cols-1",
)}
>
@@ -332,7 +334,7 @@ function LayoutColumn({
return (
<SortableContext id={droppableId} items={items} strategy={verticalListSortingStrategy}>
<div className={cn("space-y-1.5", disabled && "opacity-50", className)}>
{!hideLabel && <div className="@md:row-start-1 ps-4 font-medium text-xs">{getColumnLabel(columnId)}</div>}
{!hideLabel && <div className="ps-4 text-xs font-medium @md:row-start-1">{getColumnLabel(columnId)}</div>}
<div
ref={setNodeRef}
@@ -346,7 +348,7 @@ function LayoutColumn({
))}
{items.length === 0 && (
<div className="rounded-md border border-dashed p-4 font-medium text-muted-foreground text-xs">
<div className="rounded-md border border-dashed p-4 text-xs font-medium text-muted-foreground">
<Trans>Drag and drop sections here to move them between columns</Trans>
</div>
)}
@@ -396,7 +398,7 @@ const LayoutItemContent = forwardRef<HTMLDivElement, LayoutItemContentProps>(
data-overlay={isOverlay ? "true" : undefined}
data-dragging={isDragging ? "true" : undefined}
className={cn(
"group/item flex cursor-grab touch-none select-none items-center gap-x-2 rounded-md border border-border bg-background px-2 py-1.5 font-medium text-sm transition-all duration-200 ease-out",
"group/item flex cursor-grab touch-none items-center gap-x-2 rounded-md border border-border bg-background px-2 py-1.5 text-sm font-medium transition-all duration-200 ease-out select-none",
"hover:bg-secondary/40 active:cursor-grabbing active:border-primary/60 active:bg-secondary/40",
"data-[overlay=true]:cursor-grabbing data-[overlay=true]:border-primary/60 data-[overlay=true]:bg-background",
"data-[dragging=true]:cursor-grabbing data-[dragging=true]:border-primary/60 data-[dragging=true]:bg-background",
@@ -1,6 +1,8 @@
import { Trans } from "@lingui/react/macro";
import { RichInput } from "@/components/input/rich-input";
import { useResumeStore } from "@/components/resume/store/resume";
import { SectionBase } from "../shared/section-base";
export function NotesSectionBuilder() {
@@ -1,8 +1,10 @@
import type z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { useForm } from "react-hook-form";
import type z from "zod";
import { getLocaleOptions } from "@/components/locale/combobox";
import { useResumeStore } from "@/components/resume/store/resume";
import { Combobox } from "@/components/ui/combobox";
@@ -10,6 +12,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@/components/ui/input-group";
import { Switch } from "@/components/ui/switch";
import { pageSchema } from "@/schema/resume/data";
import { SectionBase } from "../shared/section-base";
export function PageSectionBuilder() {
@@ -42,7 +45,7 @@ function PageSectionForm() {
return (
<Form {...form}>
<form onChange={form.handleSubmit(onSubmit)} className="grid @md:grid-cols-2 grid-cols-1 gap-4">
<form onChange={form.handleSubmit(onSubmit)} className="grid grid-cols-1 gap-4 @md:grid-cols-2">
<FormField
control={form.control}
name="locale"
@@ -58,7 +61,7 @@ function PageSectionForm() {
value={field.value}
onValueChange={(locale) => {
field.onChange(locale);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -87,7 +90,7 @@ function PageSectionForm() {
value={field.value}
onValueChange={(value) => {
field.onChange(value);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -242,7 +245,7 @@ function PageSectionForm() {
checked={field.value}
onCheckedChange={(checked) => {
field.onChange(checked);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -7,6 +7,7 @@ import { useParams } from "@tanstack/react-router";
import { useCallback, useMemo } from "react";
import { toast } from "sonner";
import { useCopyToClipboard } from "usehooks-ts";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -15,6 +16,7 @@ import { useConfirm } from "@/hooks/use-confirm";
import { usePrompt } from "@/hooks/use-prompt";
import { authClient } from "@/integrations/auth/client";
import { orpc } from "@/integrations/orpc/client";
import { SectionBase } from "../shared/section-base";
export function SharingSectionBuilder() {
@@ -114,7 +116,7 @@ export function SharingSectionBuilder() {
<Trans>Allow Public Access</Trans>
</p>
<span className="text-muted-foreground text-xs">
<span className="text-xs text-muted-foreground">
<Trans>Anyone with the link can view and download the resume.</Trans>
</span>
</Label>
@@ -3,9 +3,11 @@ import { Trans } from "@lingui/react/macro";
import { InfoIcon } from "@phosphor-icons/react";
import { useQuery } from "@tanstack/react-query";
import { useParams } from "@tanstack/react-router";
import { Accordion, AccordionContent, AccordionItem } from "@/components/ui/accordion";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { orpc } from "@/integrations/orpc/client";
import { SectionBase } from "../shared/section-base";
export function StatisticsSectionBuilder() {
@@ -37,7 +39,7 @@ export function StatisticsSectionBuilder() {
</AccordionItem>
<AccordionItem value="isPublic">
<AccordionContent className="grid @md:grid-cols-2 grid-cols-1 gap-4 pb-0">
<AccordionContent className="grid grid-cols-1 gap-4 pb-0 @md:grid-cols-2">
<StatisticsItem
label={t`Views`}
value={statistics.views}
@@ -67,9 +69,9 @@ type StatisticsItemProps = {
function StatisticsItem({ label, value, timestamp }: StatisticsItemProps) {
return (
<div>
<h4 className="mb-1 font-bold font-mono text-4xl">{value}</h4>
<p className="font-medium text-muted-foreground leading-none">{label}</p>
{timestamp && <span className="text-muted-foreground text-xs">{timestamp}</span>}
<h4 className="mb-1 font-mono text-4xl font-bold">{value}</h4>
<p className="leading-none font-medium text-muted-foreground">{label}</p>
{timestamp && <span className="text-xs text-muted-foreground">{timestamp}</span>}
</div>
);
}
@@ -1,10 +1,12 @@
import { useLingui } from "@lingui/react";
import { SwapIcon } from "@phosphor-icons/react";
import { useResumeStore } from "@/components/resume/store/resume";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { templates } from "@/dialogs/resume/template/data";
import { useDialogStore } from "@/dialogs/store";
import { SectionBase } from "../shared/section-base";
export function TemplateSectionBuilder() {
@@ -27,7 +29,7 @@ function TemplateSectionForm() {
};
return (
<div className="flex @md:flex-row flex-col items-stretch gap-x-4 gap-y-2">
<div className="flex flex-col items-stretch gap-x-4 gap-y-2 @md:flex-row">
<Button
variant="ghost"
onClick={onOpenTemplateGallery}
@@ -44,8 +46,8 @@ function TemplateSectionForm() {
<div className="flex flex-1 flex-col space-y-4 @md:pt-1 @md:pb-3">
<div className="space-y-1">
<h3 className="font-semibold text-2xl capitalize tracking-tight">{metadata.name}</h3>
<p className="text-muted-foreground text-sm">{i18n.t(metadata.description)}</p>
<h3 className="text-2xl font-semibold tracking-tight capitalize">{metadata.name}</h3>
<p className="text-sm text-muted-foreground">{i18n.t(metadata.description)}</p>
</div>
<div className="flex flex-wrap gap-2.5">
@@ -1,13 +1,16 @@
import type z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { Trans } from "@lingui/react/macro";
import { useForm } from "react-hook-form";
import type z from "zod";
import { useResumeStore } from "@/components/resume/store/resume";
import { FontFamilyCombobox, FontWeightCombobox, getNextWeights } from "@/components/typography/combobox";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@/components/ui/input-group";
import { Separator } from "@/components/ui/separator";
import { typographySchema } from "@/schema/resume/data";
import { SectionBase } from "../shared/section-base";
export function TypographySectionBuilder() {
@@ -44,10 +47,10 @@ function TypographySectionForm() {
return (
<Form {...form}>
<form onChange={form.handleSubmit(onSubmit)} className="grid @md:grid-cols-2 grid-cols-1 gap-4">
<form onChange={form.handleSubmit(onSubmit)} className="grid grid-cols-1 gap-4 @md:grid-cols-2">
<div className="col-span-full flex items-center gap-x-2">
<Separator className="basis-[16px]" />
<div className="shrink-0 font-medium text-base leading-none">
<div className="shrink-0 text-base leading-none font-medium">
<Trans context="Body Text (paragraphs, lists, etc.)">Body</Trans>
</div>
<Separator className="flex-1" />
@@ -70,10 +73,9 @@ function TypographySectionForm() {
if (value === null) return;
field.onChange(value);
const nextWeights = getNextWeights(value);
if (nextWeights !== null) {
form.setValue("body.fontWeights", nextWeights, { shouldDirty: true });
}
form.handleSubmit(onSubmit)();
if (!nextWeights) return;
form.setValue("body.fontWeights", nextWeights, { shouldDirty: true });
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -98,7 +100,7 @@ function TypographySectionForm() {
fontFamily={bodyFontFamily}
onValueChange={(value) => {
field.onChange(value);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -176,7 +178,7 @@ function TypographySectionForm() {
<div className="col-span-full flex items-center gap-x-2">
<Separator className="basis-[16px]" />
<div className="shrink-0 font-medium text-base leading-none">
<div className="shrink-0 text-base leading-none font-medium">
<Trans context="Headings or Titles (H1, H2, H3, H4, H5, H6)">Heading</Trans>
</div>
<Separator className="flex-1" />
@@ -199,10 +201,9 @@ function TypographySectionForm() {
if (value === null) return;
field.onChange(value);
const nextWeights = getNextWeights(value);
if (nextWeights !== null) {
form.setValue("heading.fontWeights", nextWeights, { shouldDirty: true });
}
form.handleSubmit(onSubmit)();
if (!nextWeights) return;
form.setValue("heading.fontWeights", nextWeights, { shouldDirty: true });
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -227,7 +228,7 @@ function TypographySectionForm() {
fontFamily={headingFontFamily}
onValueChange={(value) => {
field.onChange(value);
form.handleSubmit(onSubmit)();
void form.handleSubmit(onSubmit)();
}}
/>
}
@@ -1,8 +1,10 @@
import { CaretDownIcon } from "@phosphor-icons/react";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import { getSectionIcon, getSectionTitle, type RightSidebarSection } from "@/utils/resume/section";
import { cn } from "@/utils/style";
import { useSectionStore } from "../../../-store/section";
type Props = React.ComponentProps<typeof AccordionContent> & {
@@ -33,7 +35,7 @@ export function SectionBase({ type, className, ...props }: Props) {
<div className="flex flex-1 items-center gap-x-4">
{getSectionIcon(type)}
<h2 className="line-clamp-1 font-bold text-2xl tracking-tight">{getSectionTitle(type)}</h2>
<h2 className="line-clamp-1 text-2xl font-bold tracking-tight">{getSectionTitle(type)}</h2>
</div>
</div>
@@ -1,6 +1,7 @@
import { createJSONStorage, persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
import { create } from "zustand/react";
import { leftSidebarSections, rightSidebarSections, type SidebarSection } from "@/utils/resume/section";
type SectionCollapseState = {
@@ -1,7 +1,9 @@
import { useCallback, useMemo } from "react";
import type { usePanelRef } from "react-resizable-panels";
import { useCallback, useMemo } from "react";
import { useWindowSize } from "usehooks-ts";
import { create } from "zustand/react";
import { useIsMobile } from "@/hooks/use-mobile";
type PanelImperativeHandle = ReturnType<typeof usePanelRef>;
+3 -1
View File
@@ -4,7 +4,9 @@ import { createFileRoute } from "@tanstack/react-router";
import { useHotkeys } from "react-hotkeys-hook";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { toast } from "sonner";
import { ResumePreview } from "@/components/resume/preview";
import { BuilderDock } from "./-components/dock";
export const Route = createFileRoute("/builder/$resumeId/")({
@@ -29,7 +31,7 @@ function RouteComponent() {
<TransformComponent wrapperClass="h-full! w-full!">
<ResumePreview
showPageNumbers
className="flex items-start space-x-10 space-y-10"
className="flex items-start space-y-10 space-x-10"
pageClassName="shadow-xl rounded-md overflow-hidden"
/>
</TransformComponent>
+5 -2
View File
@@ -1,18 +1,21 @@
import type React from "react";
import { useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { getCookie, setCookie } from "@tanstack/react-start/server";
import type React from "react";
import { useEffect } from "react";
import { type Layout, usePanelRef } from "react-resizable-panels";
import { useDebounceCallback } from "usehooks-ts";
import z from "zod";
import { LoadingScreen } from "@/components/layout/loading-screen";
import { useCSSVariables } from "@/components/resume/hooks/use-css-variables";
import { useResumeStore } from "@/components/resume/store/resume";
import { ResizableGroup, ResizablePanel, ResizableSeparator } from "@/components/ui/resizable";
import { useIsMobile } from "@/hooks/use-mobile";
import { orpc } from "@/integrations/orpc/client";
import { BuilderHeader } from "./-components/header";
import { BuilderSidebarLeft } from "./-sidebar/left";
import { BuilderSidebarRight } from "./-sidebar/right";
@@ -76,7 +79,7 @@ function BuilderLayout({ initialLayout, ...props }: BuilderLayoutProps) {
}));
const onLayoutChange = useDebounceCallback((layout: Layout) => {
setBuilderLayoutServerFn({ data: layout });
void setBuilderLayoutServerFn({ data: layout });
}, 200);
useEffect(() => {
+2 -1
View File
@@ -1,4 +1,5 @@
import type { Icon as IconType } from "@phosphor-icons/react";
import { SidebarTrigger } from "@/components/ui/sidebar";
import { cn } from "@/utils/style";
@@ -13,7 +14,7 @@ export function DashboardHeader({ title, icon: IconComponent, className }: Props
<div className={cn("relative flex items-center justify-center gap-x-2.5 md:justify-start", className)}>
<SidebarTrigger className="absolute inset-s-0 md:hidden" />
<IconComponent weight="light" className="size-5" />
<h1 className="font-medium text-xl tracking-tight">{title}</h1>
<h1 className="text-xl font-medium tracking-tight">{title}</h1>
</div>
);
}
+4 -2
View File
@@ -1,4 +1,5 @@
import type { MessageDescriptor } from "@lingui/core";
import { msg } from "@lingui/core/macro";
import { useLingui } from "@lingui/react";
import { Trans } from "@lingui/react/macro";
@@ -13,6 +14,7 @@ import {
} from "@phosphor-icons/react";
import { Link } from "@tanstack/react-router";
import { AnimatePresence, motion } from "motion/react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { BrandIcon } from "@/components/ui/brand-icon";
import { Copyright } from "@/components/ui/copyright";
@@ -169,7 +171,7 @@ export function DashboardSidebar() {
<div className="transition-[margin,opacity] duration-200 ease-in-out group-data-[collapsible=icon]:-ms-8 group-data-[collapsible=icon]:opacity-0">
<p className="font-medium">{session.user.name}</p>
<p className="text-muted-foreground text-xs">{session.user.email}</p>
<p className="text-xs text-muted-foreground">{session.user.email}</p>
</div>
</SidebarMenuButton>
)}
@@ -185,7 +187,7 @@ export function DashboardSidebar() {
animate={{ y: 0, height: "auto", opacity: 1 }}
exit={{ y: 50, height: 0, opacity: 0 }}
>
<Copyright className="wrap-break-word shrink-0 whitespace-normal p-2" />
<Copyright className="shrink-0 p-2 wrap-break-word whitespace-normal" />
</motion.div>
)}
</AnimatePresence>
@@ -1,6 +1,8 @@
import { t } from "@lingui/core/macro";
import { PlusIcon } from "@phosphor-icons/react";
import { useDialogStore } from "@/dialogs/store";
import { BaseCard } from "./base-card";
export function CreateResumeCard() {
@@ -1,6 +1,8 @@
import { t } from "@lingui/core/macro";
import { DownloadSimpleIcon } from "@phosphor-icons/react";
import { useDialogStore } from "@/dialogs/store";
import { BaseCard } from "./base-card";
export function ImportResumeCard() {
@@ -6,8 +6,10 @@ import { Link } from "@tanstack/react-router";
import { AnimatePresence, motion } from "motion/react";
import { useMemo } from "react";
import { match, P } from "ts-pattern";
import { orpc, type RouterOutput } from "@/integrations/orpc/client";
import { cn } from "@/utils/style";
import { ResumeContextMenu } from "../menus/context-menu";
import { BaseCard } from "./base-card";
@@ -1,5 +1,7 @@
import { AnimatePresence, motion } from "motion/react";
import type { RouterOutput } from "@/integrations/orpc/client";
import { CreateResumeCard } from "./cards/create-card";
import { ImportResumeCard } from "./cards/import-card";
import { ResumeCard } from "./cards/resume-card";
@@ -12,7 +14,7 @@ type Props = {
export function GridView({ resumes }: Props) {
return (
<div className="grid 3xl:grid-cols-6 grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
<div className="3xl:grid-cols-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
<motion.div initial={{ opacity: 0, x: -50 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -50 }}>
<CreateResumeCard />
</motion.div>
@@ -4,9 +4,12 @@ import { DotsThreeIcon, DownloadSimpleIcon, PlusIcon } from "@phosphor-icons/rea
import { Link } from "@tanstack/react-router";
import { AnimatePresence, motion } from "motion/react";
import { useMemo } from "react";
import type { RouterOutput } from "@/integrations/orpc/client";
import { Button } from "@/components/ui/button";
import { useDialogStore } from "@/dialogs/store";
import type { RouterOutput } from "@/integrations/orpc/client";
import { ResumeDropdownMenu } from "./menus/dropdown-menu";
type Resume = RouterOutput["resume"]["list"][number];
@@ -11,6 +11,7 @@ import {
import { useMutation } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router";
import { toast } from "sonner";
import {
ContextMenu,
ContextMenuContent,
@@ -11,6 +11,7 @@ import {
import { useMutation } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router";
import { toast } from "sonner";
import {
DropdownMenu,
DropdownMenuContent,
+7 -5
View File
@@ -9,11 +9,13 @@ import { getCookie, setCookie } from "@tanstack/react-start/server";
import { zodValidator } from "@tanstack/zod-adapter";
import { useMemo } from "react";
import z from "zod";
import { Combobox } from "@/components/ui/combobox";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { orpc } from "@/integrations/orpc/client";
import { cn } from "@/utils/style";
import { DashboardHeader } from "../-components/header";
import { GridView } from "./-components/grid-view";
import { ListView } from "./-components/list-view";
@@ -60,9 +62,9 @@ function RouteComponent() {
];
}, [i18n]);
const onViewChange = (value: string) => {
setViewServerFn({ data: value as "grid" | "list" });
router.invalidate();
const onViewChange = async (value: string) => {
await setViewServerFn({ data: value as "grid" | "list" });
void router.invalidate();
};
return (
@@ -78,7 +80,7 @@ function RouteComponent() {
placeholder={t`Sort by`}
onValueChange={(value) => {
if (!value) return;
navigate({ search: { tags, sort: value as SortOption } });
void navigate({ search: { tags, sort: value as SortOption } });
}}
/>
@@ -89,7 +91,7 @@ function RouteComponent() {
placeholder={t`Filter by`}
className={cn({ hidden: tagOptions.length === 0 })}
onValueChange={(value) => {
navigate({ search: { tags: value ?? [], sort } });
void navigate({ search: { tags: value ?? [], sort } });
}}
/>
+5 -4
View File
@@ -1,5 +1,7 @@
import { createFileRoute, Outlet, redirect, useRouter } from "@tanstack/react-router";
import { SidebarProvider } from "@/components/ui/sidebar";
import { getDashboardSidebarServerFn, setDashboardSidebarServerFn } from "./-components/functions";
import { DashboardSidebar } from "./-components/sidebar";
@@ -19,10 +21,9 @@ function RouteComponent() {
const router = useRouter();
const { sidebarState } = Route.useLoaderData();
const handleSidebarOpenChange = (open: boolean) => {
setDashboardSidebarServerFn({ data: open }).then(() => {
router.invalidate();
});
const handleSidebarOpenChange = async (open: boolean) => {
await setDashboardSidebarServerFn({ data: open });
void router.invalidate();
};
return (
+3 -1
View File
@@ -7,6 +7,7 @@ import { motion } from "motion/react";
import { useMemo } from "react";
import { toast } from "sonner";
import { useIsClient } from "usehooks-ts";
import { Button } from "@/components/ui/button";
import { Combobox, type ComboboxOption } from "@/components/ui/combobox";
import { Input } from "@/components/ui/input";
@@ -17,6 +18,7 @@ import { Switch } from "@/components/ui/switch";
import { type AIProvider, useAIStore } from "@/integrations/ai/store";
import { orpc } from "@/integrations/orpc/client";
import { cn } from "@/utils/style";
import { DashboardHeader } from "../-components/header";
export const Route = createFileRoute("/dashboard/settings/ai")({
@@ -232,7 +234,7 @@ function RouteComponent() {
<Trans>Your data is stored locally</Trans>
</h3>
<p className="text-muted-foreground leading-relaxed">
<p className="leading-relaxed text-muted-foreground">
<Trans>
Everything entered here is stored locally on your browser. Your data is only sent to the server when
making a request to the AI provider, and is never stored or logged on our servers.
+5 -3
View File
@@ -5,11 +5,13 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router";
import { AnimatePresence, motion } from "motion/react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { useDialogStore } from "@/dialogs/store";
import { useConfirm } from "@/hooks/use-confirm";
import { authClient } from "@/integrations/auth/client";
import { DashboardHeader } from "../-components/header";
export const Route = createFileRoute("/dashboard/settings/api-keys")({
@@ -52,7 +54,7 @@ function RouteComponent() {
}
toast.success(t`The API key has been deleted successfully.`, { id: toastId });
queryClient.invalidateQueries({ queryKey: ["auth", "api-keys"] });
void queryClient.invalidateQueries({ queryKey: ["auth", "api-keys"] });
};
return (
@@ -77,7 +79,7 @@ function RouteComponent() {
<Trans>How do I use the API?</Trans>
</h3>
<p className="text-muted-foreground leading-relaxed">
<p className="leading-relaxed text-muted-foreground">
<Trans>
Explore the API documentation to learn how to integrate Reactive Resume with your applications. Find
detailed endpoints, request examples, and authentication methods.
@@ -123,7 +125,7 @@ function RouteComponent() {
<div className="flex-1 space-y-1">
<p className="font-mono text-xs">{key.start}...</p>
<div className="text-muted-foreground text-xs">
<div className="text-xs text-muted-foreground">
<Trans>Expires on {key.expiresAt?.toLocaleDateString()}</Trans>
</div>
</div>
@@ -1,12 +1,15 @@
import type { ReactNode } from "react";
import { t } from "@lingui/core/macro";
import { GithubLogoIcon, GoogleLogoIcon, PasswordIcon, VaultIcon } from "@phosphor-icons/react";
import { useQuery } from "@tanstack/react-query";
import type { ReactNode } from "react";
import { useCallback } from "react";
import { toast } from "sonner";
import { match } from "ts-pattern";
import { authClient } from "@/integrations/auth/client";
import type { AuthProvider } from "@/integrations/auth/types";
import { authClient } from "@/integrations/auth/client";
import { orpc } from "@/integrations/orpc/client";
/**
@@ -4,8 +4,10 @@ import { Link, useNavigate } from "@tanstack/react-router";
import { motion } from "motion/react";
import { useCallback, useMemo } from "react";
import { match } from "ts-pattern";
import { Button } from "@/components/ui/button";
import { useDialogStore } from "@/dialogs/store";
import { useAuthAccounts } from "./hooks";
export function PasswordSection() {
@@ -19,7 +21,7 @@ export function PasswordSection() {
if (hasPassword) {
openDialog("auth.change-password", undefined);
} else {
navigate({ to: "/auth/forgot-password" });
void navigate({ to: "/auth/forgot-password" });
}
}, [hasPassword, navigate, openDialog]);
@@ -30,7 +32,7 @@ export function PasswordSection() {
transition={{ duration: 0.3, delay: 0.1 }}
className="flex items-center justify-between gap-x-4"
>
<h2 className="flex items-center gap-x-3 font-medium text-base">
<h2 className="flex items-center gap-x-3 text-base font-medium">
<PasswordIcon />
<Trans>Password</Trans>
</h2>
@@ -3,9 +3,12 @@ import { LinkBreakIcon, LinkIcon } from "@phosphor-icons/react";
import { motion } from "motion/react";
import { useCallback, useMemo } from "react";
import { match } from "ts-pattern";
import type { AuthProvider } from "@/integrations/auth/types";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import type { AuthProvider } from "@/integrations/auth/types";
import { getProviderIcon, getProviderName, useAuthAccounts, useAuthProviderActions } from "./hooks";
type SocialProviderSectionProps = {
@@ -24,13 +27,13 @@ export function SocialProviderSection({ provider, name, animationDelay = 0 }: So
const account = useMemo(() => getAccountByProviderId(provider), [getAccountByProviderId, provider]);
const isConnected = useMemo(() => hasAccount(provider), [hasAccount, provider]);
const handleLink = useCallback(() => {
link(provider);
const handleLink = useCallback(async () => {
await link(provider);
}, [link, provider]);
const handleUnlink = useCallback(() => {
const handleUnlink = useCallback(async () => {
if (!account?.accountId) return;
unlink(provider, account.accountId);
await unlink(provider, account.accountId);
}, [account, unlink, provider]);
return (
@@ -42,7 +45,7 @@ export function SocialProviderSection({ provider, name, animationDelay = 0 }: So
<Separator />
<div className="mt-4 flex items-center justify-between gap-x-4">
<h2 className="flex items-center gap-x-3 font-medium text-base">
<h2 className="flex items-center gap-x-3 text-base font-medium">
{providerIcon}
{providerName}
</h2>
@@ -3,10 +3,12 @@ import { KeyIcon, LockOpenIcon, ToggleLeftIcon, ToggleRightIcon } from "@phospho
import { motion } from "motion/react";
import { useCallback, useMemo } from "react";
import { match } from "ts-pattern";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { useDialogStore } from "@/dialogs/store";
import { authClient } from "@/integrations/auth/client";
import { useAuthAccounts } from "./hooks";
export function TwoFactorSection() {
@@ -36,7 +38,7 @@ export function TwoFactorSection() {
<Separator />
<div className="mt-4 flex items-center justify-between gap-x-4">
<h2 className="flex items-center gap-x-3 font-medium text-base">
<h2 className="flex items-center gap-x-3 text-base font-medium">
{hasTwoFactor ? <LockOpenIcon /> : <KeyIcon />}
<Trans>Two-Factor Authentication</Trans>
</h2>
@@ -2,7 +2,9 @@ import { t } from "@lingui/core/macro";
import { ShieldCheckIcon } from "@phosphor-icons/react";
import { createFileRoute } from "@tanstack/react-router";
import { motion } from "motion/react";
import { Separator } from "@/components/ui/separator";
import { DashboardHeader } from "../../-components/header";
import { useEnabledProviders } from "./-components/hooks";
import { PasswordSection } from "./-components/password";
@@ -6,12 +6,14 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { motion } from "motion/react";
import { useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { useConfirm } from "@/hooks/use-confirm";
import { authClient } from "@/integrations/auth/client";
import { orpc } from "@/integrations/orpc/client";
import { DashboardHeader } from "../-components/header";
export const Route = createFileRoute("/dashboard/settings/danger-zone")({
@@ -43,7 +45,7 @@ function RouteComponent() {
onSuccess: async () => {
toast.success(t`Your account has been deleted successfully.`, { id: toastId });
await authClient.signOut();
navigate({ to: "/" });
void navigate({ to: "/" });
},
onError: (error) => {
toast.error(error.message, { id: toastId });
@@ -3,11 +3,13 @@ import { Trans } from "@lingui/react/macro";
import { ArrowRightIcon, GearSixIcon } from "@phosphor-icons/react";
import { createFileRoute } from "@tanstack/react-router";
import { motion } from "motion/react";
import { LocaleCombobox } from "@/components/locale/combobox";
import { ThemeCombobox } from "@/components/theme/combobox";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { DashboardHeader } from "../-components/header";
export const Route = createFileRoute("/dashboard/settings/preferences")({
@@ -43,7 +45,7 @@ function RouteComponent() {
size="sm"
variant="link"
nativeButton={false}
className="h-5 justify-start text-muted-foreground text-xs active:scale-100"
className="h-5 justify-start text-xs text-muted-foreground active:scale-100"
render={
<a href="https://crowdin.com/project/reactive-resume" target="_blank" rel="noopener">
<Trans>Help translate the app to your language</Trans>
+8 -6
View File
@@ -9,11 +9,13 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { match } from "ts-pattern";
import z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { authClient } from "@/integrations/auth/client";
import { DashboardHeader } from "../-components/header";
export const Route = createFileRoute("/dashboard/settings/profile")({
@@ -70,7 +72,7 @@ function RouteComponent() {
toast.success(t`Your profile has been updated successfully.`);
form.reset({ name: data.name, username: data.username, email: session.user.email });
router.invalidate();
void router.invalidate();
if (data.email !== session.user.email) {
const { error } = await authClient.changeEmail({
@@ -87,7 +89,7 @@ function RouteComponent() {
t`A confirmation link has been sent to your current email address. Please check your inbox to confirm the change.`,
);
form.reset({ name: data.name, username: data.username, email: session.user.email });
router.invalidate();
void router.invalidate();
}
};
@@ -108,7 +110,7 @@ function RouteComponent() {
t`A new verification link has been sent to your email address. Please check your inbox to verify your account.`,
{ id: toastId },
);
router.invalidate();
void router.invalidate();
};
return (
@@ -188,19 +190,19 @@ function RouteComponent() {
<FormMessage />
{match(session.user.emailVerified)
.with(true, () => (
<p className="flex items-center gap-x-1.5 text-green-700 text-xs">
<p className="flex items-center gap-x-1.5 text-xs text-green-700">
<CheckIcon />
<Trans>Verified</Trans>
</p>
))
.with(false, () => (
<p className="flex items-center gap-x-1.5 text-amber-600 text-xs">
<p className="flex items-center gap-x-1.5 text-xs text-amber-600">
<WarningIcon className="size-3.5" />
<Trans>Unverified</Trans>
<span>|</span>
<Button
variant="link"
className="h-auto gap-x-1.5 p-0! text-inherit text-xs"
className="h-auto gap-x-1.5 p-0! text-xs text-inherit"
onClick={handleResendVerificationEmail}
>
<Trans>Resend verification email</Trans>
+1
View File
@@ -1,4 +1,5 @@
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import z from "zod";
// ── Shared prompt helpers ────────────────────────────────────────
+2
View File
@@ -1,5 +1,7 @@
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { client } from "@/integrations/orpc/client";
import schemaJSON from "@/schema/schema.json";
+2
View File
@@ -1,6 +1,8 @@
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import z from "zod";
import { client } from "@/integrations/orpc/client";
import { jsonPatchOperationSchema } from "@/utils/resume/patch";
+2
View File
@@ -1,7 +1,9 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
import { createFileRoute } from "@tanstack/react-router";
import { logger } from "@/utils/logger";
import { registerPrompts } from "./-helpers/prompts";
import { registerResources } from "./-helpers/resources";
import { registerTools } from "./-helpers/tools";
+1
View File
@@ -2,6 +2,7 @@ import { createFileRoute, redirect } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { useEffect } from "react";
import { z } from "zod";
import { LoadingScreen } from "@/components/layout/loading-screen";
import { ResumePreview } from "@/components/resume/preview";
import { useResumeStore } from "@/components/resume/store/resume";
+1
View File
@@ -1,5 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
import z from "zod";
import { resumeDataSchema } from "@/schema/resume/data";
function handler() {
+2 -1
View File
@@ -1,6 +1,7 @@
import { createFileRoute } from "@tanstack/react-router";
import { createHash } from "node:crypto";
import { basename, extname, normalize } from "node:path";
import { createFileRoute } from "@tanstack/react-router";
import { getStorageService, inferContentType } from "@/integrations/orpc/services/storage";
import { env } from "@/utils/env";