fix issue with missing DialogTitle/DialogDescription, fix issue with hot reloads

This commit is contained in:
Amruth Pillai
2025-01-19 22:01:37 +01:00
parent 18cf814779
commit 460a40711e
27 changed files with 136 additions and 277 deletions

View File

@ -1,13 +1,23 @@
import { cn } from "@reactive-resume/utils";
import { forwardRef, useEffect } from "react";
import { useDebounceValue } from "usehooks-ts";
type BrandIconProps = {
slug: string;
};
export const BrandIcon = ({ slug }: BrandIconProps) => {
if (slug === "linkedin") {
export const BrandIcon = forwardRef<HTMLImageElement, BrandIconProps>(({ slug }, ref) => {
const [debouncedSlug, setValue] = useDebounceValue(slug, 600);
useEffect(() => {
setValue(slug);
}, [slug]);
if (!slug) return null;
if (debouncedSlug === "linkedin") {
return (
<img
ref={ref}
alt="LinkedIn"
className="size-5"
src={`${window.location.origin}/support-logos/linkedin.svg`}
@ -15,5 +25,14 @@ export const BrandIcon = ({ slug }: BrandIconProps) => {
);
}
return <i className={cn("si si--color text-[1.25rem]", `si-${slug}`)} />;
};
return (
<img
ref={ref}
alt={debouncedSlug}
className="size-5"
src={`https://cdn.simpleicons.org/${debouncedSlug}`}
/>
);
});
BrandIcon.displayName = "BrandIcon";

View File

@ -1,5 +1,15 @@
import { useBreakpoint } from "@reactive-resume/hooks";
import { Panel, PanelGroup, PanelResizeHandle, Sheet, SheetContent } from "@reactive-resume/ui";
import {
Panel,
PanelGroup,
PanelResizeHandle,
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
VisuallyHidden,
} from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { Outlet } from "react-router";
@ -78,6 +88,13 @@ export const BuilderLayout = () => {
return (
<div className="relative">
<Sheet open={sheet.left.open} onOpenChange={sheet.left.setOpen}>
<VisuallyHidden>
<SheetHeader>
<SheetTitle />
<SheetDescription />
</SheetHeader>
</VisuallyHidden>
<SheetContent
side="left"
showClose={false}
@ -97,6 +114,13 @@ export const BuilderLayout = () => {
className="top-16 p-0 sm:max-w-xl"
onOpenAutoFocus={onOpenAutoFocus}
>
<VisuallyHidden>
<SheetHeader>
<SheetTitle />
<SheetDescription />
</SheetHeader>
</VisuallyHidden>
<RightSidebar />
</SheetContent>
</Sheet>

View File

@ -6,7 +6,7 @@ import { useResumeStore } from "@/client/stores/resume";
import { CustomFieldsSection } from "./custom/section";
import { PictureSection } from "./picture/section";
import { getSectionIcon } from "./shared/section-icon";
import { SectionIcon } from "./shared/section-icon";
import { URLInput } from "./shared/url-input";
export const BasicsSection = () => {
@ -17,7 +17,7 @@ export const BasicsSection = () => {
<section id="basics" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("basics")}
<SectionIcon id="basics" size={18} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Basics`}</h2>
</div>
</header>

View File

@ -25,7 +25,7 @@ import get from "lodash.get";
import { useDialog } from "@/client/stores/dialog";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "./section-icon";
import { SectionIcon } from "./section-icon";
import { SectionListItem } from "./section-list-item";
import { SectionOptions } from "./section-options";
@ -98,8 +98,7 @@ export const SectionBase = <T extends SectionItem>({ id, title, description }: P
>
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon(id)}
<SectionIcon id={id} size={18} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{section.name}</h2>
</div>

View File

@ -23,7 +23,7 @@ import get from "lodash.get";
import { useResumeStore } from "@/client/stores/resume";
export const getSectionIcon = (id: SectionKey, props: IconProps = {}) => {
const getSectionIcon = (id: SectionKey, props: IconProps = {}) => {
switch (id) {
// Left Sidebar
case "basics": {
@ -75,13 +75,14 @@ export const getSectionIcon = (id: SectionKey, props: IconProps = {}) => {
}
};
type SectionIconProps = ButtonProps & {
type SectionIconProps = Omit<ButtonProps, "size"> & {
id: SectionKey;
name?: string;
size?: number;
icon?: React.ReactNode;
};
export const SectionIcon = ({ id, name, icon, ...props }: SectionIconProps) => {
export const SectionIcon = ({ id, name, icon, size = 14, ...props }: SectionIconProps) => {
const section = useResumeStore((state) =>
get(state.resume.data.sections, id, defaultSection),
) as SectionWithItem;
@ -89,7 +90,7 @@ export const SectionIcon = ({ id, name, icon, ...props }: SectionIconProps) => {
return (
<Tooltip side="right" content={name ?? section.name}>
<Button size="icon" variant="ghost" className="size-8 rounded-full" {...props}>
{icon ?? getSectionIcon(id, { size: 14 })}
{icon ?? getSectionIcon(id, { size })}
</Button>
</Tooltip>
);

View File

@ -5,7 +5,7 @@ import { cn } from "@reactive-resume/utils";
import { AiActions } from "@/client/components/ai-actions";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "./shared/section-icon";
import { SectionIcon } from "./shared/section-icon";
import { SectionOptions } from "./shared/section-options";
export const SummarySection = () => {
@ -19,7 +19,7 @@ export const SummarySection = () => {
<section id="summary" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("summary")}
<SectionIcon id="summary" size={18} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{section.name}</h2>
</div>

View File

@ -7,7 +7,7 @@ import CodeEditor from "react-simple-code-editor";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
export const CssSection = () => {
const { isDarkMode } = useTheme();
@ -24,7 +24,7 @@ export const CssSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("css")}
<SectionIcon id="css" size={18} name={t`Custom CSS`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Custom CSS`}</h2>
</div>
</header>

View File

@ -7,7 +7,7 @@ import { saveAs } from "file-saver";
import { usePrintResume } from "@/client/services/resume/print";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
const onJsonExport = () => {
const { resume } = useResumeStore.getState();
@ -36,7 +36,7 @@ export const ExportSection = () => {
<section id="export" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("export")}
<SectionIcon id="export" size={18} name={t`Export`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Export`}</h2>
</div>
</header>

View File

@ -10,7 +10,7 @@ import {
} from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
const DonateCard = () => (
<Card className="space-y-4 bg-info text-info-foreground">
@ -113,7 +113,7 @@ export const InformationSection = () => {
<section id="information" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("information")}
<SectionIcon id="information" size={18} name={t`Information`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Information`}</h2>
</div>
</header>

View File

@ -27,7 +27,7 @@ import { useState } from "react";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
type ColumnProps = {
id: string;
@ -194,7 +194,7 @@ export const LayoutSection = () => {
<section id="layout" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("layout")}
<SectionIcon id="layout" size={18} name={t`Layout`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Layout`}</h2>
</div>

View File

@ -3,7 +3,7 @@ import { RichInput } from "@reactive-resume/ui";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
export const NotesSection = () => {
const setValue = useResumeStore((state) => state.setValue);
@ -13,7 +13,7 @@ export const NotesSection = () => {
<section id="notes" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("notes")}
<SectionIcon id="notes" size={18} name={t`Notes`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Notes`}</h2>
</div>
</header>

View File

@ -12,7 +12,7 @@ import {
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
export const PageSection = () => {
const setValue = useResumeStore((state) => state.setValue);
@ -22,7 +22,7 @@ export const PageSection = () => {
<section id="page" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("page")}
<SectionIcon id="page" size={18} name={t`Page`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Page`}</h2>
</div>
</header>

View File

@ -7,7 +7,7 @@ import { useToast } from "@/client/hooks/use-toast";
import { useUser } from "@/client/services/user";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
export const SharingSection = () => {
const { user } = useUser();
@ -35,7 +35,7 @@ export const SharingSection = () => {
<section id="sharing" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("sharing")}
<SectionIcon id="sharing" size={18} name={t`Sharing`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Sharing`}</h2>
</div>
</header>

View File

@ -7,7 +7,7 @@ import { AnimatePresence, motion } from "framer-motion";
import { useResumeStatistics } from "@/client/services/resume";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
export const StatisticsSection = () => {
const id = useResumeStore((state) => state.resume.id);
@ -19,7 +19,7 @@ export const StatisticsSection = () => {
<section id="statistics" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("statistics")}
<SectionIcon id="statistics" size={18} name={t`Statistics`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Statistics`}</h2>
</div>
</header>

View File

@ -5,7 +5,7 @@ import { motion } from "framer-motion";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
export const TemplateSection = () => {
const setValue = useResumeStore((state) => state.setValue);
@ -15,7 +15,7 @@ export const TemplateSection = () => {
<section id="template" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("template")}
<SectionIcon id="template" size={18} name={t`Template`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Template`}</h2>
</div>
</header>

View File

@ -6,7 +6,7 @@ import { HexColorPicker } from "react-colorful";
import { colors } from "@/client/constants/colors";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
export const ThemeSection = () => {
const setValue = useResumeStore((state) => state.setValue);
@ -16,7 +16,7 @@ export const ThemeSection = () => {
<section id="theme" className="grid gap-y-6">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("theme")}
<SectionIcon id="theme" size={18} name={t`Theme`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Theme`}</h2>
</div>
</header>

View File

@ -9,7 +9,7 @@ import webfontloader from "webfontloader";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
import { SectionIcon } from "../shared/section-icon";
const fontSuggestions = [
"Open Sans",
@ -62,7 +62,7 @@ export const TypographySection = () => {
<section id="typography" className="grid gap-y-8">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("typography")}
<SectionIcon id="typography" size={18} name={t`Typography`} />
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Typography`}</h2>
</div>
</header>

View File

@ -16,7 +16,7 @@ import {
import type { ButtonProps } from "@reactive-resume/ui";
import { Button, Tooltip } from "@reactive-resume/ui";
export type MetadataKey =
type MetadataKey =
| "template"
| "layout"
| "typography"
@ -30,7 +30,7 @@ export type MetadataKey =
| "notes"
| "information";
export const getSectionIcon = (id: MetadataKey, props: IconProps = {}) => {
const getSectionIcon = (id: MetadataKey, props: IconProps = {}) => {
switch (id) {
// Left Sidebar
case "notes": {
@ -76,16 +76,17 @@ export const getSectionIcon = (id: MetadataKey, props: IconProps = {}) => {
}
};
type SectionIconProps = ButtonProps & {
type SectionIconProps = Omit<ButtonProps, "size"> & {
id: MetadataKey;
name: string;
size?: number;
icon?: React.ReactNode;
};
export const SectionIcon = ({ id, name, icon, ...props }: SectionIconProps) => (
export const SectionIcon = ({ id, name, icon, size = 14, ...props }: SectionIconProps) => (
<Tooltip side="left" content={name}>
<Button size="icon" variant="ghost" className="size-8 rounded-full" {...props}>
{icon ?? getSectionIcon(id, { size: 14 })}
{icon ?? getSectionIcon(id, { size })}
</Button>
</Tooltip>
);

View File

@ -1,113 +0,0 @@
/* eslint-disable lingui/no-unlocalized-strings */
import { t } from "@lingui/macro";
import { Helmet } from "react-helmet-async";
export const PrivacyPolicyPage = () => (
<main className="relative isolate bg-background">
<Helmet prioritizeSeoTags>
<title>
{t`Privacy Policy`} - {t`Reactive Resume`}
</title>
<meta
name="description"
content="A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume."
/>
</Helmet>
<section
id="privacy-policy"
className="container prose prose-zinc relative max-w-4xl py-32 dark:prose-invert"
>
<h1 className="mb-4">{t`Privacy Policy`}</h1>
<h6 className="text-sm">Last updated on 3rd May 2024</h6>
<hr className="my-6" />
<ol>
<li>
<h2 className="mb-2">Introduction</h2>
<p>
This privacy policy outlines how we collect, use, and protect the personal information
you provide when using our web application. By accessing or using Reactive Resume, you
agree to the collection and use of information in accordance with this policy.
</p>
</li>
<li>
<h2 className="mb-2">Information Collection and Use</h2>
<p>
For a better experience while using our Service, we may require you to provide us with
certain personally identifiable information, including but not limited to your name and
email address. The information that we collect will be used to contact or identify you
primarily for the following purposes:
</p>
<ul>
<li>
<strong>Account Creation:</strong> to allow you to create and manage your account.
</li>
<li>
<strong>Functionality:</strong> to enable the various features of the application that
you choose to utilize, such as building and saving resumes.
</li>
</ul>
</li>
<li>
<h2 className="mb-2">How We Collect Information</h2>
<p>
All personal data is provided directly by you. We collect information through our web
application when you voluntarily provide it to us as part of using our service.
</p>
</li>
<li>
<h2 className="mb-2">Data Security</h2>
<p>
Reactive Resume is committed to ensuring the security of your data. Our application and
database are hosted on a secure server from DigitalOcean, which has both SOC 2 and SOC 3
compliance, ensuring that your data is protected with industry-standard security
measures.
</p>
</li>
<li>
<h2 className="mb-2">Data Retention</h2>
<p>
We retain your personal data as long as your account is active or as needed to provide
you services. If you do not use your account for 6 months, your personal information is
automatically deleted from our servers. You may also delete your data at any time via
the user dashboard.
</p>
</li>
<li>
<h2 className="mb-2">Third-Party Disclosure</h2>
<p>
We do not share your personal information with third parties, ensuring your data is used
exclusively for the purposes stated in this privacy policy.
</p>
</li>
<li>
<h2 className="mb-2">Changes to This Privacy Policy</h2>
<p>
We may update our Privacy Policy from time to time. We will notify you of any changes by
posting the new Privacy Policy on this page. You are advised to review this Privacy
Policy periodically for any changes.
</p>
</li>
<li>
<h2 className="mb-2">Contact Us</h2>
<p>
If you have any questions or suggestions about our Privacy Policy, do not hesitate to
contact us at <code>hello[at]amruthpillai[dot]com</code>.
</p>
</li>
</ol>
</section>
</main>
);

View File

@ -77,7 +77,7 @@ export const PublicResumePage = () => {
<div
style={{ width: `${pageSizeMap[format].width}mm` }}
className="mx-auto mb-6 mt-16 overflow-hidden rounded shadow-xl print:m-0 print:shadow-none"
className="overflow-hidden rounded shadow-xl sm:mx-auto sm:mb-6 sm:mt-16 print:m-0 print:shadow-none"
>
<iframe
ref={frameRef}
@ -87,7 +87,7 @@ export const PublicResumePage = () => {
/>
</div>
<div className="flex justify-center py-10 opacity-50 print:hidden">
<div className="hidden justify-center py-10 opacity-50 sm:flex print:hidden">
<Link to="/">
<Button size="sm" variant="ghost" className="space-x-1.5 text-xs font-normal">
<span>{t`Built with`}</span>
@ -97,7 +97,7 @@ export const PublicResumePage = () => {
</Link>
</div>
<div className="fixed bottom-5 right-5 print:hidden">
<div className="fixed bottom-5 right-5 hidden sm:block print:hidden">
<div className="flex items-center gap-x-4">
<Button variant="outline" className="gap-x-2 rounded-full" onClick={onDownloadPdf}>
{loading ? <CircleNotch size={16} className="animate-spin" /> : <FilePdf size={16} />}

View File

@ -14,7 +14,6 @@ import { DashboardLayout } from "../pages/dashboard/layout";
import { ResumesPage } from "../pages/dashboard/resumes/page";
import { SettingsPage } from "../pages/dashboard/settings/page";
import { HomeLayout } from "../pages/home/layout";
import { PrivacyPolicyPage } from "../pages/home/meta/privacy-policy/page";
import { HomePage } from "../pages/home/page";
import { publicLoader, PublicResumePage } from "../pages/public/page";
import { Providers } from "../providers";
@ -27,11 +26,6 @@ export const routes = createRoutesFromElements(
<Route element={<Providers />} hydrateFallbackElement={<div>Loading...</div>}>
<Route element={<HomeLayout />}>
<Route path="/" element={<HomePage />} />
<Route path="meta">
<Route path="privacy-policy" element={<PrivacyPolicyPage />} />
<Route index element={<Navigate replace to="/" />} />
</Route>
</Route>
<Route path="auth">