@liiift-studio/sanity-font-manager 2.3.18 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +437 -437
  2. package/dist/index.js +103 -48
  3. package/dist/index.mjs +103 -48
  4. package/package.json +85 -85
  5. package/src/components/BatchUploadFonts.jsx +640 -639
  6. package/src/components/FontScriptUploaderComponent.jsx +463 -463
  7. package/src/components/GenerateCollectionsPairsComponent.jsx +259 -259
  8. package/src/components/KeyValueInput.jsx +95 -95
  9. package/src/components/KeyValueReferenceInput.jsx +254 -254
  10. package/src/components/NestedObjectArraySelector.jsx +146 -146
  11. package/src/components/PriceInput.jsx +26 -26
  12. package/src/components/PrimaryCollectionGeneratorTypeface.jsx +116 -116
  13. package/src/components/RegenerateSubfamiliesComponent.jsx +185 -185
  14. package/src/components/SetOTF.jsx +87 -87
  15. package/src/components/SingleUploaderTool.jsx +673 -673
  16. package/src/components/StatusDisplay.jsx +26 -26
  17. package/src/components/StyleCountInput.jsx +16 -16
  18. package/src/components/UpdateScriptsComponent.jsx +76 -76
  19. package/src/components/UploadButton.jsx +43 -43
  20. package/src/components/UploadScriptsComponent.jsx +537 -537
  21. package/src/components/VariableInstanceReferencesInput.jsx +190 -190
  22. package/src/hooks/useNestedObjects.js +92 -92
  23. package/src/hooks/useSanityClient.js +9 -9
  24. package/src/index.js +70 -70
  25. package/src/schema/openTypeField.js +1945 -1945
  26. package/src/schema/styleCountField.js +12 -12
  27. package/src/schema/stylesField.js +268 -268
  28. package/src/schema/stylisticSetField.js +301 -301
  29. package/src/utils/generateCssFile.js +205 -205
  30. package/src/utils/generateFontData.js +145 -145
  31. package/src/utils/generateFontFile.js +38 -38
  32. package/src/utils/generateKeywords.js +185 -185
  33. package/src/utils/generateSubset.js +45 -45
  34. package/src/utils/getEmptyFontKit.js +99 -99
  35. package/src/utils/parseVariableFontInstances.js +211 -211
  36. package/src/utils/processFontFiles.js +487 -477
  37. package/src/utils/regenerateFontData.js +146 -146
  38. package/src/utils/sanitizeForSanityId.js +65 -65
  39. package/src/utils/updateFontPrices.js +94 -94
  40. package/src/utils/updateTypefaceDocument.js +149 -160
  41. package/src/utils/uploadFontFiles.js +115 -26
  42. package/src/utils/utils.js +24 -24
@@ -1,146 +1,146 @@
1
- // Renames font document IDs across a typeface when a typeface slug changes
2
-
3
- import * as fontkit from 'fontkit';
4
- import {
5
- readFontFile,
6
- extractFontMetadata,
7
- } from './processFontFiles';
8
-
9
- /**
10
- * Re-downloads TTF/OTF files for all fonts in a typeface and re-extracts their metadata,
11
- * patching each font document's title, slug, weightName, and subfamily.
12
- *
13
- * @param {Object} params
14
- * @param {Object} params.client - Sanity client
15
- * @param {string} params.typefaceName
16
- * @param {Object} params.slug - Typeface slug object
17
- * @param {string[]} params.weightKeywordList
18
- * @param {string[]} params.italicKeywordList
19
- * @param {boolean} params.preserveShortenedNames
20
- * @param {Function} params.setStatus
21
- * @param {Function} params.setError
22
- * @returns {Promise<Object>}
23
- */
24
- export const renameFontDocuments = async ({
25
- client,
26
- typefaceName,
27
- slug,
28
- weightKeywordList,
29
- italicKeywordList,
30
- preserveShortenedNames = false,
31
- setStatus,
32
- setError,
33
- }) => {
34
- try {
35
- if (!typefaceName) {
36
- setStatus('Typeface needs a title');
37
- setError(true);
38
- console.error('Typeface needs title');
39
- return { success: false, message: 'Typeface needs title' };
40
- }
41
-
42
- setStatus('Fetching font documents...');
43
- const slugCurrent = slug?.current || typefaceName;
44
-
45
- const query = await client.fetch(
46
- `*[_type == "typeface" && slug.current == $slugCurrent][0]{
47
- "fonts": styles.fonts[]->{ _id, title, subfamily, fileInput, style, weight, _key }
48
- }`,
49
- { slugCurrent }
50
- );
51
- const fontDocuments = query?.fonts || [];
52
-
53
- if (fontDocuments.length === 0) {
54
- setStatus('No font documents found for this typeface');
55
- setError(true);
56
- return { success: false, message: 'No font documents found for this typeface' };
57
- }
58
-
59
- setStatus(`Found ${fontDocuments.length} font documents to process...`);
60
-
61
- let updatedCount = 0;
62
- let skippedCount = 0;
63
- let errorCount = 0;
64
-
65
- for (let i = 0; i < fontDocuments.length; i++) {
66
- const fontDoc = fontDocuments[i];
67
- setStatus(`Processing font ${i + 1}/${fontDocuments.length}: ${fontDoc.title}`);
68
-
69
- if (!fontDoc.fileInput?.ttf?.asset?._ref && !fontDoc.fileInput?.otf?.asset?._ref) {
70
- console.log(`Skipping ${fontDoc.title} — no TTF or OTF file found`);
71
- skippedCount++;
72
- continue;
73
- }
74
-
75
- try {
76
- let file;
77
- let ttfAsset;
78
- let otfAsset;
79
-
80
- if (fontDoc.fileInput?.ttf?.asset?._ref) {
81
- ttfAsset = await client.getDocument(fontDoc.fileInput.ttf.asset._ref);
82
- }
83
-
84
- if (!ttfAsset?.url) {
85
- if (fontDoc.fileInput?.otf?.asset?._ref) {
86
- otfAsset = await client.getDocument(fontDoc.fileInput.otf.asset._ref);
87
- }
88
- if (!otfAsset?.url) {
89
- console.log(`Skipping ${fontDoc.title} — no TTF or OTF URL`);
90
- skippedCount++;
91
- continue;
92
- }
93
- setStatus(`Fetching OTF file for ${fontDoc.title}...`);
94
- const res = await fetch(otfAsset.url);
95
- file = await res.blob();
96
- } else {
97
- setStatus(`Fetching TTF file for ${fontDoc.title}...`);
98
- const res = await fetch(ttfAsset.url);
99
- file = await res.blob();
100
- }
101
-
102
- const fontBuffer = await readFontFile(file);
103
- const font = fontkit.create(fontBuffer);
104
-
105
- const { weightName, subfamilyName, fontTitle } = extractFontMetadata(
106
- font,
107
- typefaceName,
108
- weightKeywordList,
109
- italicKeywordList,
110
- preserveShortenedNames,
111
- );
112
-
113
- const slugValue = fontTitle.toLowerCase().replace(/\s+/g, '-');
114
-
115
- setStatus(`Updating font ${i + 1}/${fontDocuments.length}: ${fontTitle}`);
116
- await client.patch(fontDoc._id)
117
- .set({
118
- title: fontTitle,
119
- slug: { _type: 'slug', current: slugValue },
120
- weightName: weightName,
121
- subfamily: subfamilyName,
122
- })
123
- .commit();
124
-
125
- updatedCount++;
126
- } catch (err) {
127
- console.error(`Error processing ${fontDoc.title}:`, err);
128
- errorCount++;
129
- }
130
- }
131
-
132
- const resultMessage = `Renamed ${updatedCount} of ${fontDocuments.length} font documents (${skippedCount} skipped, ${errorCount} errors)`;
133
- setStatus(resultMessage);
134
-
135
- return {
136
- success: true,
137
- message: resultMessage,
138
- stats: { total: fontDocuments.length, updated: updatedCount, skipped: skippedCount, errors: errorCount },
139
- };
140
- } catch (err) {
141
- console.error('Error renaming font documents:', err);
142
- setError(true);
143
- setStatus(`Error: ${err.message}`);
144
- return { success: false, message: `Error: ${err.message}` };
145
- }
146
- };
1
+ // Renames font document IDs across a typeface when a typeface slug changes
2
+
3
+ import * as fontkit from 'fontkit';
4
+ import {
5
+ readFontFile,
6
+ extractFontMetadata,
7
+ } from './processFontFiles';
8
+
9
+ /**
10
+ * Re-downloads TTF/OTF files for all fonts in a typeface and re-extracts their metadata,
11
+ * patching each font document's title, slug, weightName, and subfamily.
12
+ *
13
+ * @param {Object} params
14
+ * @param {Object} params.client - Sanity client
15
+ * @param {string} params.typefaceName
16
+ * @param {Object} params.slug - Typeface slug object
17
+ * @param {string[]} params.weightKeywordList
18
+ * @param {string[]} params.italicKeywordList
19
+ * @param {boolean} params.preserveShortenedNames
20
+ * @param {Function} params.setStatus
21
+ * @param {Function} params.setError
22
+ * @returns {Promise<Object>}
23
+ */
24
+ export const renameFontDocuments = async ({
25
+ client,
26
+ typefaceName,
27
+ slug,
28
+ weightKeywordList,
29
+ italicKeywordList,
30
+ preserveShortenedNames = false,
31
+ setStatus,
32
+ setError,
33
+ }) => {
34
+ try {
35
+ if (!typefaceName) {
36
+ setStatus('Typeface needs a title');
37
+ setError(true);
38
+ console.error('Typeface needs title');
39
+ return { success: false, message: 'Typeface needs title' };
40
+ }
41
+
42
+ setStatus('Fetching font documents...');
43
+ const slugCurrent = slug?.current || typefaceName;
44
+
45
+ const query = await client.fetch(
46
+ `*[_type == "typeface" && slug.current == $slugCurrent][0]{
47
+ "fonts": styles.fonts[]->{ _id, title, subfamily, fileInput, style, weight, _key }
48
+ }`,
49
+ { slugCurrent }
50
+ );
51
+ const fontDocuments = query?.fonts || [];
52
+
53
+ if (fontDocuments.length === 0) {
54
+ setStatus('No font documents found for this typeface');
55
+ setError(true);
56
+ return { success: false, message: 'No font documents found for this typeface' };
57
+ }
58
+
59
+ setStatus(`Found ${fontDocuments.length} font documents to process...`);
60
+
61
+ let updatedCount = 0;
62
+ let skippedCount = 0;
63
+ let errorCount = 0;
64
+
65
+ for (let i = 0; i < fontDocuments.length; i++) {
66
+ const fontDoc = fontDocuments[i];
67
+ setStatus(`Processing font ${i + 1}/${fontDocuments.length}: ${fontDoc.title}`);
68
+
69
+ if (!fontDoc.fileInput?.ttf?.asset?._ref && !fontDoc.fileInput?.otf?.asset?._ref) {
70
+ console.log(`Skipping ${fontDoc.title} — no TTF or OTF file found`);
71
+ skippedCount++;
72
+ continue;
73
+ }
74
+
75
+ try {
76
+ let file;
77
+ let ttfAsset;
78
+ let otfAsset;
79
+
80
+ if (fontDoc.fileInput?.ttf?.asset?._ref) {
81
+ ttfAsset = await client.getDocument(fontDoc.fileInput.ttf.asset._ref);
82
+ }
83
+
84
+ if (!ttfAsset?.url) {
85
+ if (fontDoc.fileInput?.otf?.asset?._ref) {
86
+ otfAsset = await client.getDocument(fontDoc.fileInput.otf.asset._ref);
87
+ }
88
+ if (!otfAsset?.url) {
89
+ console.log(`Skipping ${fontDoc.title} — no TTF or OTF URL`);
90
+ skippedCount++;
91
+ continue;
92
+ }
93
+ setStatus(`Fetching OTF file for ${fontDoc.title}...`);
94
+ const res = await fetch(otfAsset.url);
95
+ file = await res.blob();
96
+ } else {
97
+ setStatus(`Fetching TTF file for ${fontDoc.title}...`);
98
+ const res = await fetch(ttfAsset.url);
99
+ file = await res.blob();
100
+ }
101
+
102
+ const fontBuffer = await readFontFile(file);
103
+ const font = fontkit.create(fontBuffer);
104
+
105
+ const { weightName, subfamilyName, fontTitle } = extractFontMetadata(
106
+ font,
107
+ typefaceName,
108
+ weightKeywordList,
109
+ italicKeywordList,
110
+ preserveShortenedNames,
111
+ );
112
+
113
+ const slugValue = fontTitle.toLowerCase().replace(/\s+/g, '-');
114
+
115
+ setStatus(`Updating font ${i + 1}/${fontDocuments.length}: ${fontTitle}`);
116
+ await client.patch(fontDoc._id)
117
+ .set({
118
+ title: fontTitle,
119
+ slug: { _type: 'slug', current: slugValue },
120
+ weightName: weightName,
121
+ subfamily: subfamilyName,
122
+ })
123
+ .commit();
124
+
125
+ updatedCount++;
126
+ } catch (err) {
127
+ console.error(`Error processing ${fontDoc.title}:`, err);
128
+ errorCount++;
129
+ }
130
+ }
131
+
132
+ const resultMessage = `Renamed ${updatedCount} of ${fontDocuments.length} font documents (${skippedCount} skipped, ${errorCount} errors)`;
133
+ setStatus(resultMessage);
134
+
135
+ return {
136
+ success: true,
137
+ message: resultMessage,
138
+ stats: { total: fontDocuments.length, updated: updatedCount, skipped: skippedCount, errors: errorCount },
139
+ };
140
+ } catch (err) {
141
+ console.error('Error renaming font documents:', err);
142
+ setError(true);
143
+ setStatus(`Error: ${err.message}`);
144
+ return { success: false, message: `Error: ${err.message}` };
145
+ }
146
+ };
@@ -1,65 +1,65 @@
1
- // Converts arbitrary strings into valid Sanity document IDs (lowercase, hyphens, no special characters)
2
- import slugify from 'slugify';
3
-
4
- /**
5
- * Sanitizes a string into a valid Sanity document ID.
6
- *
7
- * Sanity ID requirements:
8
- * - Must start with a letter or underscore (not a number or hyphen)
9
- * - Can only contain lowercase letters (a-z), numbers (0-9), hyphens (-), and underscores (_)
10
- * - Must be between 1 and 128 characters
11
- *
12
- * @param {string} str - The raw string (e.g. font title or filename) to sanitize
13
- * @returns {string} A valid Sanity document ID
14
- */
15
- export function sanitizeForSanityId(str) {
16
- if (!str || typeof str !== 'string') {
17
- return 'font-' + Date.now();
18
- }
19
-
20
- let sanitized = str.toLowerCase().trim();
21
-
22
- // Replace common symbols before slugify
23
- sanitized = sanitized.replace(/\+/g, 'plus');
24
- sanitized = sanitized.replace(/&/g, 'and');
25
- sanitized = sanitized.replace(/@/g, 'at');
26
-
27
- sanitized = slugify(sanitized, {
28
- replacement: '-',
29
- remove: /[^\w\s-]/g,
30
- lower: true,
31
- strict: true,
32
- locale: 'en',
33
- trim: true,
34
- });
35
-
36
- // Strip any characters that still aren't lowercase-alphanumeric, hyphens, or underscores
37
- sanitized = sanitized.replace(/[^a-z0-9\-_]/g, '-');
38
-
39
- // Collapse repeated hyphens and strip leading/trailing hyphens or underscores
40
- sanitized = sanitized.replace(/-+/g, '-');
41
- sanitized = sanitized.replace(/^[-_]+|[-_]+$/g, '');
42
-
43
- // IDs must not start with a number or hyphen
44
- if (sanitized && !/^[a-z_]/.test(sanitized)) {
45
- sanitized = 'font_' + sanitized;
46
- }
47
-
48
- if (!sanitized) {
49
- sanitized = 'font_' + Date.now();
50
- }
51
-
52
- // Sanity hard-caps IDs at 128 characters
53
- if (sanitized.length > 128) {
54
- const hash = Math.random().toString(36).substring(2, 8);
55
- sanitized = sanitized.substring(0, 120) + '_' + hash;
56
- }
57
-
58
- // Paranoid final validation
59
- if (!/^[a-z_][a-z0-9\-_]*$/.test(sanitized)) {
60
- console.warn(`ID sanitization produced invalid result: "${sanitized}", using fallback`);
61
- sanitized = 'font_' + Date.now();
62
- }
63
-
64
- return sanitized;
65
- }
1
+ // Converts arbitrary strings into valid Sanity document IDs (lowercase, hyphens, no special characters)
2
+ import slugify from 'slugify';
3
+
4
+ /**
5
+ * Sanitizes a string into a valid Sanity document ID.
6
+ *
7
+ * Sanity ID requirements:
8
+ * - Must start with a letter or underscore (not a number or hyphen)
9
+ * - Can only contain lowercase letters (a-z), numbers (0-9), hyphens (-), and underscores (_)
10
+ * - Must be between 1 and 128 characters
11
+ *
12
+ * @param {string} str - The raw string (e.g. font title or filename) to sanitize
13
+ * @returns {string} A valid Sanity document ID
14
+ */
15
+ export function sanitizeForSanityId(str) {
16
+ if (!str || typeof str !== 'string') {
17
+ return 'font-' + Date.now();
18
+ }
19
+
20
+ let sanitized = str.toLowerCase().trim();
21
+
22
+ // Replace common symbols before slugify
23
+ sanitized = sanitized.replace(/\+/g, 'plus');
24
+ sanitized = sanitized.replace(/&/g, 'and');
25
+ sanitized = sanitized.replace(/@/g, 'at');
26
+
27
+ sanitized = slugify(sanitized, {
28
+ replacement: '-',
29
+ remove: /[^\w\s-]/g,
30
+ lower: true,
31
+ strict: true,
32
+ locale: 'en',
33
+ trim: true,
34
+ });
35
+
36
+ // Strip any characters that still aren't lowercase-alphanumeric, hyphens, or underscores
37
+ sanitized = sanitized.replace(/[^a-z0-9\-_]/g, '-');
38
+
39
+ // Collapse repeated hyphens and strip leading/trailing hyphens or underscores
40
+ sanitized = sanitized.replace(/-+/g, '-');
41
+ sanitized = sanitized.replace(/^[-_]+|[-_]+$/g, '');
42
+
43
+ // IDs must not start with a number or hyphen
44
+ if (sanitized && !/^[a-z_]/.test(sanitized)) {
45
+ sanitized = 'font_' + sanitized;
46
+ }
47
+
48
+ if (!sanitized) {
49
+ sanitized = 'font_' + Date.now();
50
+ }
51
+
52
+ // Sanity hard-caps IDs at 128 characters
53
+ if (sanitized.length > 128) {
54
+ const hash = Math.random().toString(36).substring(2, 8);
55
+ sanitized = sanitized.substring(0, 120) + '_' + hash;
56
+ }
57
+
58
+ // Paranoid final validation
59
+ if (!/^[a-z_][a-z0-9\-_]*$/.test(sanitized)) {
60
+ console.warn(`ID sanitization produced invalid result: "${sanitized}", using fallback`);
61
+ sanitized = 'font_' + Date.now();
62
+ }
63
+
64
+ return sanitized;
65
+ }
@@ -1,94 +1,94 @@
1
- // Bulk-updates the price field across all font documents linked to a typeface
2
-
3
- /**
4
- * Sets the same price on every font document referenced by a typeface.
5
- *
6
- * @param {Object} params
7
- * @param {Object} params.client - Sanity client
8
- * @param {string} params.title - Typeface title
9
- * @param {Object} params.slug - Typeface slug object
10
- * @param {string} params.inputPrice - New price value (will be coerced to Number)
11
- * @param {string} params.doc_id - Document ID (used to detect draft state)
12
- * @param {Function} params.setStatus
13
- * @param {Function} params.setError
14
- * @returns {Promise<Object>}
15
- */
16
- export const updateFontPrices = async ({
17
- client,
18
- title,
19
- slug,
20
- inputPrice,
21
- doc_id,
22
- setStatus,
23
- setError,
24
- }) => {
25
- try {
26
- if (!title) {
27
- setStatus('Typeface needs a title');
28
- setError(true);
29
- console.error('Typeface needs title');
30
- return { success: false, message: 'Typeface needs title' };
31
- }
32
-
33
- if (!slug?.current) {
34
- setStatus('Typeface needs a slug');
35
- setError(true);
36
- console.error('Typeface needs slug');
37
- return { success: false, message: 'Typeface needs slug' };
38
- }
39
-
40
- const price = Number(inputPrice);
41
- if (isNaN(price)) {
42
- setStatus('Invalid price value');
43
- setError(true);
44
- console.error('Invalid price value');
45
- return { success: false, message: 'Invalid price value' };
46
- }
47
-
48
- setStatus('Fetching typeface document...');
49
- const typeface = await client.fetch(
50
- `*[_type == "typeface" && slug.current == $slug][0]`,
51
- { slug: slug.current }
52
- );
53
-
54
- if (!typeface) {
55
- setStatus('Typeface not found');
56
- setError(true);
57
- console.error('Typeface not found');
58
- return { success: false, message: 'Typeface not found' };
59
- }
60
-
61
- if (!typeface.styles?.fonts?.length) {
62
- setStatus('No fonts found in typeface');
63
- setError(true);
64
- console.error('No fonts found in typeface');
65
- return { success: false, message: 'No fonts found in typeface' };
66
- }
67
-
68
- const fontRefs = typeface.styles.fonts;
69
- setStatus(`Updating prices for ${fontRefs.length} fonts...`);
70
-
71
- let updatedCount = 0;
72
- for (let i = 0; i < fontRefs.length; i++) {
73
- try {
74
- await client.patch(fontRefs[i]._ref).set({ price, sell: price > 0 }).commit();
75
- updatedCount++;
76
- setStatus(`Updated ${updatedCount}/${fontRefs.length} fonts...`);
77
- } catch (err) {
78
- console.error(`Error updating font ${fontRefs[i]._ref}:`, err);
79
- }
80
- }
81
-
82
- const successMessage = `Successfully updated prices for ${updatedCount} fonts to $${price}`;
83
- setStatus(successMessage);
84
- console.log(successMessage);
85
-
86
- return { success: true, message: successMessage, updatedCount };
87
- } catch (err) {
88
- const errorMessage = `Error: ${err.message}`;
89
- console.error('Error updating font prices:', err);
90
- setError(true);
91
- setStatus(errorMessage);
92
- return { success: false, message: errorMessage };
93
- }
94
- };
1
+ // Bulk-updates the price field across all font documents linked to a typeface
2
+
3
+ /**
4
+ * Sets the same price on every font document referenced by a typeface.
5
+ *
6
+ * @param {Object} params
7
+ * @param {Object} params.client - Sanity client
8
+ * @param {string} params.title - Typeface title
9
+ * @param {Object} params.slug - Typeface slug object
10
+ * @param {string} params.inputPrice - New price value (will be coerced to Number)
11
+ * @param {string} params.doc_id - Document ID (used to detect draft state)
12
+ * @param {Function} params.setStatus
13
+ * @param {Function} params.setError
14
+ * @returns {Promise<Object>}
15
+ */
16
+ export const updateFontPrices = async ({
17
+ client,
18
+ title,
19
+ slug,
20
+ inputPrice,
21
+ doc_id,
22
+ setStatus,
23
+ setError,
24
+ }) => {
25
+ try {
26
+ if (!title) {
27
+ setStatus('Typeface needs a title');
28
+ setError(true);
29
+ console.error('Typeface needs title');
30
+ return { success: false, message: 'Typeface needs title' };
31
+ }
32
+
33
+ if (!slug?.current) {
34
+ setStatus('Typeface needs a slug');
35
+ setError(true);
36
+ console.error('Typeface needs slug');
37
+ return { success: false, message: 'Typeface needs slug' };
38
+ }
39
+
40
+ const price = Number(inputPrice);
41
+ if (isNaN(price)) {
42
+ setStatus('Invalid price value');
43
+ setError(true);
44
+ console.error('Invalid price value');
45
+ return { success: false, message: 'Invalid price value' };
46
+ }
47
+
48
+ setStatus('Fetching typeface document...');
49
+ const typeface = await client.fetch(
50
+ `*[_type == "typeface" && slug.current == $slug][0]`,
51
+ { slug: slug.current }
52
+ );
53
+
54
+ if (!typeface) {
55
+ setStatus('Typeface not found');
56
+ setError(true);
57
+ console.error('Typeface not found');
58
+ return { success: false, message: 'Typeface not found' };
59
+ }
60
+
61
+ if (!typeface.styles?.fonts?.length) {
62
+ setStatus('No fonts found in typeface');
63
+ setError(true);
64
+ console.error('No fonts found in typeface');
65
+ return { success: false, message: 'No fonts found in typeface' };
66
+ }
67
+
68
+ const fontRefs = typeface.styles.fonts;
69
+ setStatus(`Updating prices for ${fontRefs.length} fonts...`);
70
+
71
+ let updatedCount = 0;
72
+ for (let i = 0; i < fontRefs.length; i++) {
73
+ try {
74
+ await client.patch(fontRefs[i]._ref).set({ price, sell: price > 0 }).commit();
75
+ updatedCount++;
76
+ setStatus(`Updated ${updatedCount}/${fontRefs.length} fonts...`);
77
+ } catch (err) {
78
+ console.error(`Error updating font ${fontRefs[i]._ref}:`, err);
79
+ }
80
+ }
81
+
82
+ const successMessage = `Successfully updated prices for ${updatedCount} fonts to $${price}`;
83
+ setStatus(successMessage);
84
+ console.log(successMessage);
85
+
86
+ return { success: true, message: successMessage, updatedCount };
87
+ } catch (err) {
88
+ const errorMessage = `Error: ${err.message}`;
89
+ console.error('Error updating font prices:', err);
90
+ setError(true);
91
+ setStatus(errorMessage);
92
+ return { success: false, message: errorMessage };
93
+ }
94
+ };