返回 presentation-ai
useFontUpload.ts
1 // font-step/hooks/useFontUpload.ts
2 import { useState } from "react";
3 import { useWatch, type Control } from "react-hook-form";
4 import { toast } from "sonner";
5
6 import { useUploadThing } from "@/hooks/globals/useUploadthing";
7 import { loadCustomFont } from "@/lib/presentation/loadCustomFont";
8 import { type ThemeFormValues } from "../../types";
9 import { type LocalFont } from "./types";
10
11 interface UseFontUploadOptions {
12 setValue: (
13 name: `fonts.${keyof ThemeFormValues["fonts"]}`,
14 value: string | undefined,
15 options?: { shouldDirty?: boolean },
16 ) => void;
17 control: Control<ThemeFormValues>;
18 }
19
20 export function useFontUpload({ setValue, control }: UseFontUploadOptions) {
21 const [isUploadingHeading, setIsUploadingHeading] = useState(false);
22 const [isUploadingBody, setIsUploadingBody] = useState(false);
23
24 const { startUpload } = useUploadThing("fontUploader", {
25 onClientUploadComplete: () => {},
26 onUploadError: (error: Error) => {
27 console.error("Font upload error:", error.message);
28 },
29 });
30
31 const handleFontUpload = async (target: "heading" | "body") => {
32 const input = document.createElement("input");
33 input.type = "file";
34 input.accept = ".ttf,.otf,.woff,.woff2";
35
36 input.onchange = async (e) => {
37 const file = (e.target as HTMLInputElement).files?.[0];
38 if (!file) return;
39
40 if (file.size > 2 * 1024 * 1024) {
41 toast.error("Font file must be smaller than 2MB");
42 return;
43 }
44
45 if (target === "heading") setIsUploadingHeading(true);
46 else setIsUploadingBody(true);
47
48 try {
49 const result = await startUpload([file]);
50 if (result?.[0]) {
51 const { serverData, ufsUrl } = result[0];
52 const options = { shouldDirty: true };
53
54 if (target === "heading") {
55 setValue("fonts.heading", serverData.familyName, options);
56 setValue("fonts.headingUrl", ufsUrl, options);
57 } else {
58 setValue("fonts.body", serverData.familyName, options);
59 setValue("fonts.bodyUrl", ufsUrl, options);
60 }
61
62 // Load the font immediately using FontFace API
63 try {
64 await loadCustomFont(serverData.familyName, ufsUrl, 400);
65 toast.success("Font uploaded and loaded successfully");
66 } catch (fontLoadError) {
67 console.error("Font uploaded but failed to load:", fontLoadError);
68 toast.success("Font uploaded successfully");
69 }
70 }
71 } catch (error) {
72 console.error("Font upload failed:", error);
73 toast.error("Upload failed");
74 } finally {
75 if (target === "heading") setIsUploadingHeading(false);
76 else setIsUploadingBody(false);
77 }
78 };
79
80 input.click();
81 };
82
83 // Use useWatch to get current font values for local custom fonts
84 const headingUrl = useWatch({ control, name: "fonts.headingUrl" });
85 const headingFamily = useWatch({ control, name: "fonts.heading" });
86 const bodyUrl = useWatch({ control, name: "fonts.bodyUrl" });
87 const bodyFamily = useWatch({ control, name: "fonts.body" });
88
89 const getLocalCustomFonts = (target: "heading" | "body"): LocalFont[] => {
90 const fontUrl = target === "heading" ? headingUrl : bodyUrl;
91 const fontFamily = target === "heading" ? headingFamily : bodyFamily;
92
93 if (!fontUrl || !fontFamily) return [];
94
95 return [
96 {
97 name: fontFamily,
98 category: "custom",
99 sane: fontFamily.toLowerCase().replace(/\s+/g, "_"),
100 cased: fontFamily.toLowerCase(),
101 variants: ["0,400"],
102 isLocal: true,
103 },
104 ];
105 };
106
107 return {
108 isUploadingHeading,
109 isUploadingBody,
110 handleFontUpload,
111 getLocalCustomFonts,
112 };
113 }
114
114 lines TYPESCRIPT