mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-23 23:02:17 +10:00
refactor: ponytail audit
This commit is contained in:
@@ -15,7 +15,6 @@ import {
|
||||
import { useHotkey } from "@tanstack/react-hotkeys";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { m } from "motion/react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useControls, useTransformComponent } from "react-zoom-pan-pinch";
|
||||
import { toast } from "sonner";
|
||||
import { useCopyToClipboard } from "usehooks-ts";
|
||||
@@ -72,15 +71,8 @@ export function BuilderDock({ pageLayout, onTogglePageLayout }: BuilderDockProps
|
||||
redo();
|
||||
});
|
||||
|
||||
const publicUrl = useMemo(() => {
|
||||
if (!session?.user.username || !resumeSlug) return "";
|
||||
return `${window.location.origin}/${session.user.username}/${resumeSlug}`;
|
||||
}, [session?.user.username, resumeSlug]);
|
||||
|
||||
const onCopyUrl = useCallback(async () => {
|
||||
await copyToClipboard(publicUrl);
|
||||
toast.success(t`A link to your resume has been copied to clipboard.`);
|
||||
}, [publicUrl, copyToClipboard]);
|
||||
const publicUrl =
|
||||
session?.user.username && resumeSlug ? `${window.location.origin}/${session.user.username}/${resumeSlug}` : "";
|
||||
|
||||
return (
|
||||
<div className="fixed inset-x-0 bottom-20 flex items-center justify-center md:bottom-4">
|
||||
@@ -111,7 +103,14 @@ export function BuilderDock({ pageLayout, onTogglePageLayout }: BuilderDockProps
|
||||
}}
|
||||
/>
|
||||
<div className="mx-1 h-8 w-px bg-border" />
|
||||
<DockIcon icon={LinkSimpleIcon} title={t`Copy URL`} onClick={() => onCopyUrl()} />
|
||||
<DockIcon
|
||||
icon={LinkSimpleIcon}
|
||||
title={t`Copy URL`}
|
||||
onClick={async () => {
|
||||
await copyToClipboard(publicUrl);
|
||||
toast.success(t`A link to your resume has been copied to clipboard.`);
|
||||
}}
|
||||
/>
|
||||
</m.div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,29 +19,9 @@ import {
|
||||
import { useResumeStore } from "@/features/resume/builder/draft";
|
||||
import { useConfirm } from "@/hooks/use-confirm";
|
||||
import { getResumeErrorMessage } from "@/libs/error-message";
|
||||
import { formatRelativeTime } from "@/libs/locale";
|
||||
import { orpc } from "@/libs/orpc/client";
|
||||
|
||||
const RELATIVE_TIME_DIVISIONS: { amount: number; unit: Intl.RelativeTimeFormatUnit }[] = [
|
||||
{ amount: 31_536_000_000, unit: "year" },
|
||||
{ amount: 2_592_000_000, unit: "month" },
|
||||
{ amount: 604_800_000, unit: "week" },
|
||||
{ amount: 86_400_000, unit: "day" },
|
||||
{ amount: 3_600_000, unit: "hour" },
|
||||
{ amount: 60_000, unit: "minute" },
|
||||
];
|
||||
|
||||
function formatRelativeTime(value: Date | string, formatter: Intl.RelativeTimeFormat) {
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
const diffMs = date.getTime() - Date.now();
|
||||
const absMs = Math.abs(diffMs);
|
||||
|
||||
// No division matches only when the gap is under a minute (the smallest division), so fall back to seconds.
|
||||
const division = RELATIVE_TIME_DIVISIONS.find((candidate) => absMs >= candidate.amount);
|
||||
if (!division) return formatter.format(0, "second");
|
||||
|
||||
return formatter.format(Math.round(diffMs / division.amount), division.unit);
|
||||
}
|
||||
|
||||
type BuilderVersionHistoryProps = {
|
||||
resumeId: string;
|
||||
};
|
||||
|
||||
@@ -326,7 +326,6 @@ describe("CustomStylesSectionBuilder", () => {
|
||||
|
||||
fireEvent.blur(fontSizeInput);
|
||||
expect(fontSizeInput).toHaveValue(12);
|
||||
expect(updateResumeData).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("commits normalized legacy values when the input loses focus", () => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Trans } from "@lingui/react/macro";
|
||||
import { ArrowRightIcon, InfoIcon, LightningIcon, SparkleIcon } from "@phosphor-icons/react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { useMemo } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { match } from "ts-pattern";
|
||||
import { Alert, AlertDescription } from "@reactive-resume/ui/components/alert";
|
||||
@@ -93,13 +92,8 @@ export function ResumeAnalysisSectionBuilder() {
|
||||
// so the server render has no date and there's no hydration mismatch to defer around.
|
||||
const updatedAtLabel = updatedAt ? new Date(updatedAt).toLocaleString() : null;
|
||||
const analyzeLabel = isPending ? t`Analyzing…` : t`Analyze Resume`;
|
||||
|
||||
const scoreTone = useMemo(() => {
|
||||
if (score == null) return "bg-muted";
|
||||
if (score >= 80) return "bg-emerald-600";
|
||||
if (score >= 60) return "bg-amber-600";
|
||||
return "bg-rose-600";
|
||||
}, [score]);
|
||||
const scoreTone =
|
||||
score == null ? "bg-muted" : score >= 80 ? "bg-emerald-600" : score >= 60 ? "bg-amber-600" : "bg-rose-600";
|
||||
|
||||
const onAnalyze = () => {
|
||||
if (!resume) return;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Trans } from "@lingui/react/macro";
|
||||
import { ORPCError } from "@orpc/client";
|
||||
import { ClipboardIcon, LockSimpleIcon, LockSimpleOpenIcon } from "@phosphor-icons/react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useCopyToClipboard } from "usehooks-ts";
|
||||
import { Button } from "@reactive-resume/ui/components/button";
|
||||
@@ -29,10 +29,7 @@ export function SharingSectionBuilder() {
|
||||
const { mutateAsync: setPassword } = useMutation(orpc.resume.setPassword.mutationOptions());
|
||||
const { mutateAsync: removePassword } = useMutation(orpc.resume.removePassword.mutationOptions());
|
||||
|
||||
const publicUrl = useMemo(() => {
|
||||
if (!session) return "";
|
||||
return `${window.location.origin}/${session.user.username}/${resume.slug}`;
|
||||
}, [session, resume]);
|
||||
const publicUrl = session ? `${window.location.origin}/${session.user.username}/${resume.slug}` : "";
|
||||
|
||||
const onCopyUrl = useCallback(async () => {
|
||||
await copyToClipboard(publicUrl);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Layout, usePanelRef } from "react-resizable-panels";
|
||||
import Cookies from "js-cookie";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { useMediaQuery, useWindowSize } from "usehooks-ts";
|
||||
import { create } from "zustand/react";
|
||||
|
||||
@@ -135,17 +135,14 @@ export function useBuilderSidebar(): UseBuilderSidebarReturn {
|
||||
[expandSize],
|
||||
);
|
||||
|
||||
// ponytail: memoized but callers destructure; selector removed (state rebuilt every render, zero benefit)
|
||||
return useMemo(() => {
|
||||
return {
|
||||
maxSidebarSize,
|
||||
minSidebarSize,
|
||||
collapsedSidebarSize,
|
||||
groupResizeBehavior,
|
||||
isCollapsed,
|
||||
toggleSidebar,
|
||||
};
|
||||
}, [maxSidebarSize, minSidebarSize, collapsedSidebarSize, groupResizeBehavior, isCollapsed, toggleSidebar]);
|
||||
return {
|
||||
maxSidebarSize,
|
||||
minSidebarSize,
|
||||
collapsedSidebarSize,
|
||||
groupResizeBehavior,
|
||||
isCollapsed,
|
||||
toggleSidebar,
|
||||
};
|
||||
}
|
||||
|
||||
export const setBuilderLayout = (data: BuilderLayout) => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { getBuilderLayout } from "./-store/sidebar";
|
||||
|
||||
export const Route = createFileRoute("/builder/$resumeId")({
|
||||
component: RouteComponent,
|
||||
beforeLoad: async ({ context }) => {
|
||||
beforeLoad: ({ context }) => {
|
||||
if (!context.session) throw redirect({ to: "/auth/login", replace: true });
|
||||
return { session: context.session };
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user