mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-26 01:44:53 +10:00
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:
@@ -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 />;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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} />
|
||||
))}
|
||||
|
||||
@@ -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,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" }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-reactive-resume-v5-2"
|
||||
>
|
||||
<Trans>Reactive Resume is launching on Product Hunt today, head over to show some love!</Trans>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,4 +1,5 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { auth } from "@/integrations/auth/config";
|
||||
|
||||
async function handler({ request }: { request: Request }) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 } });
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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,4 +1,5 @@
|
||||
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
|
||||
import z from "zod";
|
||||
|
||||
// ── Shared prompt helpers ────────────────────────────────────────
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import z from "zod";
|
||||
|
||||
import { resumeDataSchema } from "@/schema/resume/data";
|
||||
|
||||
function handler() {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user