project-portfolio 2.2.1 → 2.2.3
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.
- package/README.md +4 -1
- package/dist/ProjectDetail.d.ts +6 -1
- package/dist/ProjectDetail.d.ts.map +1 -1
- package/dist/ProjectDetail.js +8 -7
- package/dist/ProjectMenuClient.d.ts +9 -3
- package/dist/ProjectMenuClient.d.ts.map +1 -1
- package/dist/ProjectMenuClient.js +37 -40
- package/dist/SimilarProjects.d.ts +6 -1
- package/dist/SimilarProjects.d.ts.map +1 -1
- package/dist/SimilarProjects.js +12 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -273,6 +273,7 @@ Each group renders values as outlined pills. Fields with no value for the curren
|
|
|
273
273
|
| `backPath` | `string` | No | `"/projects"` | Path for the back navigation link |
|
|
274
274
|
| `backLabel` | `string` | No | `"All Projects"` | Label for the back navigation link |
|
|
275
275
|
| `revalidate` | `number` | No | `86400` | Cache revalidation period in seconds (24 hours) |
|
|
276
|
+
| `noCache` | `boolean` | No | `false` | Bypasses the Next.js Data Cache and sets `cache: "no-store"`. Useful during development or for frequently updated projects. |
|
|
276
277
|
|
|
277
278
|
---
|
|
278
279
|
|
|
@@ -438,6 +439,7 @@ Use `variant="card"` to render the same baseball-card style used in `ProjectPort
|
|
|
438
439
|
| `variant` | `"list" \| "card"` | No | `"list"` | `"list"` uses the border-bottom separator style; `"card"` renders full baseball-card style matching `ProjectPortfolio` |
|
|
439
440
|
| `font` | `string` | No | System font stack | Font family string applied to all inline styles |
|
|
440
441
|
| `revalidate` | `number` | No | `86400` | Cache revalidation period in seconds (24 hours) |
|
|
442
|
+
| `noCache` | `boolean` | No | `false` | Bypasses the Next.js Data Cache and sets `cache: "no-store"`. Useful during development or for frequently updated projects. |
|
|
441
443
|
|
|
442
444
|
---
|
|
443
445
|
|
|
@@ -487,7 +489,7 @@ Pass `menuId` to show a specific curated set of projects instead of all projects
|
|
|
487
489
|
| `font` | `string` | No | System font stack | Font family string applied to all inline styles |
|
|
488
490
|
| `maxProjects` | `number` | No | `6` | Maximum number of projects to display |
|
|
489
491
|
| `revalidate` | `number` | No | `86400` | Cache revalidation period in seconds (24 hours) |
|
|
490
|
-
| `noCache` | `boolean` | No | `false` |
|
|
492
|
+
| `noCache` | `boolean` | No | `false` | Bypasses the Next.js Data Cache and sets `cache: "no-store"`. Useful during development or for frequently updated projects. |
|
|
491
493
|
|
|
492
494
|
---
|
|
493
495
|
|
|
@@ -586,6 +588,7 @@ With a curated menu:
|
|
|
586
588
|
| `clientSlug` | `string` | No* | — | Client slug for direct fetch mode |
|
|
587
589
|
| `apiBase` | `string` | No* | — | API base URL for direct fetch mode |
|
|
588
590
|
| `menuId` | `string` | No | — | Slug of a curated menu. Fetches from `/menus/{slug}` for projects. Filters are always shown regardless. |
|
|
591
|
+
| `noCache` | `boolean` | No | `false` | Bypasses the module-level data cache and sets `cache: "no-store"` on all fetch calls. Useful during development or when projects update frequently. |
|
|
589
592
|
| `basePath` | `string` | Yes | — | Base path for project detail links |
|
|
590
593
|
| `viewAllPath` | `string` | Yes | — | Path for the "View All Projects" link |
|
|
591
594
|
| `subtitle` | `string` | No | — | Description shown above the project cards (hidden on mobile) |
|
package/dist/ProjectDetail.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export interface ProjectDetailProps {
|
|
|
15
15
|
* Defaults to 60.
|
|
16
16
|
*/
|
|
17
17
|
revalidate?: number;
|
|
18
|
+
/**
|
|
19
|
+
* When true, bypasses the Next.js Data Cache and always fetches fresh data.
|
|
20
|
+
* Sets fetch cache to "no-store". Useful during development or for frequently updated projects.
|
|
21
|
+
*/
|
|
22
|
+
noCache?: boolean;
|
|
18
23
|
}
|
|
19
|
-
export declare function ProjectDetail({ slug, clientSlug, apiBase, backPath, backLabel, revalidate, }: ProjectDetailProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
24
|
+
export declare function ProjectDetail({ slug, clientSlug, apiBase, backPath, backLabel, revalidate, noCache, }: ProjectDetailProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
20
25
|
//# sourceMappingURL=ProjectDetail.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectDetail.d.ts","sourceRoot":"","sources":["../src/ProjectDetail.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"ProjectDetail.d.ts","sourceRoot":"","sources":["../src/ProjectDetail.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAsED,wBAAsB,aAAa,CAAC,EAClC,IAAI,EACJ,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,SAA0B,EAC1B,UAAe,EACf,OAAe,GAChB,EAAE,kBAAkB,oDAoQpB"}
|
package/dist/ProjectDetail.js
CHANGED
|
@@ -19,12 +19,13 @@ function dedupeByKey(arr) {
|
|
|
19
19
|
return true;
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
const fetchProjectDetail = cache(async (apiBase, clientSlug, slug, revalidate) => {
|
|
22
|
+
const fetchProjectDetail = cache(async (apiBase, clientSlug, slug, revalidate, noCache) => {
|
|
24
23
|
var _a, _b, _c, _d;
|
|
25
|
-
const fetchOpts =
|
|
26
|
-
? {
|
|
27
|
-
:
|
|
24
|
+
const fetchOpts = noCache
|
|
25
|
+
? { cache: "no-store" }
|
|
26
|
+
: revalidate > 0
|
|
27
|
+
? { next: { revalidate } }
|
|
28
|
+
: {};
|
|
28
29
|
try {
|
|
29
30
|
// Single call — /projects/{slug} returns the project AND client.custom_fields_schema
|
|
30
31
|
// with full options embedded. No need for a separate /fields call.
|
|
@@ -51,9 +52,9 @@ const fetchProjectDetail = cache(async (apiBase, clientSlug, slug, revalidate) =
|
|
|
51
52
|
}
|
|
52
53
|
});
|
|
53
54
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
54
|
-
export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/projects", backLabel = "All Projects", revalidate = 60, }) {
|
|
55
|
+
export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/projects", backLabel = "All Projects", revalidate = 60, noCache = false, }) {
|
|
55
56
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
56
|
-
const { project, schema, fieldOptionsMap } = await fetchProjectDetail(apiBase, clientSlug, slug, revalidate);
|
|
57
|
+
const { project, schema, fieldOptionsMap } = await fetchProjectDetail(apiBase, clientSlug, slug, revalidate, noCache);
|
|
57
58
|
if (!project) {
|
|
58
59
|
return (_jsx("div", { style: { textAlign: "center", padding: "6rem 1.5rem", fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" }, children: _jsx("p", { style: { color: "#71717a", fontSize: "18px" }, children: "Project not found." }) }));
|
|
59
60
|
}
|
|
@@ -15,10 +15,16 @@ export interface ProjectMenuClientProps {
|
|
|
15
15
|
clientSlug?: string;
|
|
16
16
|
apiBase?: string;
|
|
17
17
|
/**
|
|
18
|
-
* Optional menu
|
|
19
|
-
* When provided, fetches from /api/v1/clients/{clientSlug}/menus/{
|
|
18
|
+
* Optional menu slug to fetch a specific curated menu instead of all projects.
|
|
19
|
+
* When provided, fetches from /api/v1/clients/{clientSlug}/menus/{slug}
|
|
20
20
|
*/
|
|
21
21
|
menuId?: string;
|
|
22
|
+
/**
|
|
23
|
+
* When true, bypasses the module-level data cache and always fetches fresh data.
|
|
24
|
+
* Also sets fetch cache to "no-store" on all underlying requests.
|
|
25
|
+
* Useful during development or when projects update frequently.
|
|
26
|
+
*/
|
|
27
|
+
noCache?: boolean;
|
|
22
28
|
projects?: Project[];
|
|
23
29
|
schema?: CustomFieldSchema[];
|
|
24
30
|
filterOptions?: {
|
|
@@ -34,5 +40,5 @@ export interface ProjectMenuClientProps {
|
|
|
34
40
|
font?: string;
|
|
35
41
|
maxProjects?: number;
|
|
36
42
|
}
|
|
37
|
-
export declare function ProjectMenuClient({ dataUrl, clientSlug, apiBase, menuId, projects: projectsProp, schema: schemaProp, filterOptions: filterOptionsProp, filterFieldKey: filterFieldKeyProp, filterFieldName: filterFieldNameProp, fieldOptionsMap: fieldOptionsMapProp, subtitle, basePath, viewAllPath, font, maxProjects, }: ProjectMenuClientProps): import("react/jsx-runtime").JSX.Element;
|
|
43
|
+
export declare function ProjectMenuClient({ dataUrl, clientSlug, apiBase, menuId, noCache, projects: projectsProp, schema: schemaProp, filterOptions: filterOptionsProp, filterFieldKey: filterFieldKeyProp, filterFieldName: filterFieldNameProp, fieldOptionsMap: fieldOptionsMapProp, subtitle, basePath, viewAllPath, font, maxProjects, }: ProjectMenuClientProps): import("react/jsx-runtime").JSX.Element;
|
|
38
44
|
//# sourceMappingURL=ProjectMenuClient.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectMenuClient.d.ts","sourceRoot":"","sources":["../src/ProjectMenuClient.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAoB,MAAM,SAAS,CAAA;AAa3E,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"ProjectMenuClient.d.ts","sourceRoot":"","sources":["../src/ProjectMenuClient.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAoB,MAAM,SAAS,CAAA;AAa3E,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;IACpB,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAA;IAC5B,aAAa,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC/C,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAmBD,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,UAAU,EACV,OAAO,EACP,MAAM,EACN,OAAe,EACf,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,UAAU,EAClB,aAAa,EAAE,iBAAiB,EAChC,cAAc,EAAE,kBAAkB,EAClC,eAAe,EAAE,mBAAoC,EACrD,eAAe,EAAE,mBAAwB,EACzC,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,IAAmB,EACnB,WAAe,GAChB,EAAE,sBAAsB,2CAqfxB"}
|
|
@@ -17,25 +17,27 @@ const ACCENT = "oklch(0.78 0.16 85)";
|
|
|
17
17
|
const API_KEY = "pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR";
|
|
18
18
|
const DEFAULT_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
19
19
|
const menuDataCache = new Map();
|
|
20
|
-
export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, menuId, projects: projectsProp, schema: schemaProp, filterOptions: filterOptionsProp, filterFieldKey: filterFieldKeyProp, filterFieldName: filterFieldNameProp = "Project Type", fieldOptionsMap: fieldOptionsMapProp = {}, subtitle, basePath, viewAllPath, font = DEFAULT_FONT, maxProjects = 6, }) {
|
|
20
|
+
export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, menuId, noCache = false, projects: projectsProp, schema: schemaProp, filterOptions: filterOptionsProp, filterFieldKey: filterFieldKeyProp, filterFieldName: filterFieldNameProp = "Project Type", fieldOptionsMap: fieldOptionsMapProp = {}, subtitle, basePath, viewAllPath, font = DEFAULT_FONT, maxProjects = 6, }) {
|
|
21
21
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
22
22
|
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
23
23
|
const [hoveredCard, setHoveredCard] = useState(null);
|
|
24
24
|
const [fetched, setFetched] = useState(null);
|
|
25
25
|
// Self-fetch mode: fires when dataUrl OR (clientSlug + apiBase) are provided.
|
|
26
26
|
// Uses a module-level Promise cache so repeated mounts never re-hit the API.
|
|
27
|
+
// Pass noCache=true to bypass the module cache and always fetch fresh data.
|
|
27
28
|
useEffect(() => {
|
|
28
29
|
const hasDataUrl = !!dataUrl;
|
|
29
30
|
const hasDirectFetch = !!(clientSlug && apiBase);
|
|
30
31
|
if (!hasDataUrl && !hasDirectFetch)
|
|
31
32
|
return;
|
|
32
33
|
let cancelled = false;
|
|
34
|
+
const fetchOpts = noCache ? { cache: "no-store" } : {};
|
|
33
35
|
const cacheKey = dataUrl !== null && dataUrl !== void 0 ? dataUrl : `${clientSlug}:${apiBase}:${menuId !== null && menuId !== void 0 ? menuId : "all"}`;
|
|
34
36
|
async function fetchAndCache() {
|
|
35
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x
|
|
37
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
|
|
36
38
|
// dataUrl mode: fetch from local API route (server-cached, no API key exposed)
|
|
37
39
|
if (dataUrl) {
|
|
38
|
-
const res = await fetch(dataUrl);
|
|
40
|
+
const res = await fetch(dataUrl, fetchOpts);
|
|
39
41
|
const json = res.ok ? await res.json() : {};
|
|
40
42
|
return {
|
|
41
43
|
projects: (_a = json.projects) !== null && _a !== void 0 ? _a : [],
|
|
@@ -46,24 +48,20 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, menuId, projec
|
|
|
46
48
|
fieldOptionsMap: (_f = json.fieldOptionsMap) !== null && _f !== void 0 ? _f : {},
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
|
-
// Menu endpoint mode:
|
|
50
|
-
// Also fetch /projects to get schema for filters (menu endpoint may not return it)
|
|
51
|
+
// Menu endpoint mode: /menus/{slug} for projects, /projects for schema+options.
|
|
51
52
|
if (menuId) {
|
|
52
|
-
const [menuRes, projectsRes
|
|
53
|
-
fetch(`${apiBase}/api/v1/clients/${clientSlug}/menus/${menuId}?api_key=${API_KEY}
|
|
54
|
-
fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=${API_KEY}
|
|
55
|
-
fetch(`${apiBase}/api/v1/clients/${clientSlug}/fields?api_key=${API_KEY}`),
|
|
53
|
+
const [menuRes, projectsRes] = await Promise.all([
|
|
54
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/menus/${menuId}?api_key=${API_KEY}`, fetchOpts),
|
|
55
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=${API_KEY}`, fetchOpts),
|
|
56
56
|
]);
|
|
57
57
|
const menuJson = menuRes.ok ? await menuRes.json() : {};
|
|
58
58
|
const projectsJson = projectsRes.ok ? await projectsRes.json() : {};
|
|
59
|
-
const projects = ((
|
|
60
|
-
|
|
61
|
-
const schema = (_o = (_l = (_k = (_j = menuJson === null || menuJson === void 0 ? void 0 : menuJson.client) === null || _j === void 0 ? void 0 : _j.custom_fields_schema) !== null && _k !== void 0 ? _k : menuJson === null || menuJson === void 0 ? void 0 : menuJson.schema) !== null && _l !== void 0 ? _l : (_m = projectsJson === null || projectsJson === void 0 ? void 0 : projectsJson.client) === null || _m === void 0 ? void 0 : _m.custom_fields_schema) !== null && _o !== void 0 ? _o : [];
|
|
62
|
-
const fieldsJson = fieldsRes.ok ? await fieldsRes.json() : { fields: [] };
|
|
59
|
+
const projects = ((_g = menuJson === null || menuJson === void 0 ? void 0 : menuJson.projects) !== null && _g !== void 0 ? _g : []).filter((p) => p.is_published !== false);
|
|
60
|
+
const schema = (_j = (_h = projectsJson === null || projectsJson === void 0 ? void 0 : projectsJson.client) === null || _h === void 0 ? void 0 : _h.custom_fields_schema) !== null && _j !== void 0 ? _j : [];
|
|
63
61
|
const fieldOptionsMap = {};
|
|
64
|
-
for (const field of
|
|
62
|
+
for (const field of schema) {
|
|
65
63
|
const map = {};
|
|
66
|
-
for (const opt of ((
|
|
64
|
+
for (const opt of ((_k = field.options) !== null && _k !== void 0 ? _k : [])) {
|
|
67
65
|
if (typeof opt === "object" && opt.id && opt.label) {
|
|
68
66
|
map[opt.id] = opt.label;
|
|
69
67
|
map[opt.label] = opt.label;
|
|
@@ -71,9 +69,9 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, menuId, projec
|
|
|
71
69
|
}
|
|
72
70
|
fieldOptionsMap[field.key] = map;
|
|
73
71
|
}
|
|
74
|
-
const filterField = (
|
|
72
|
+
const filterField = (_l = schema.find((f) => f.is_filterable && (f.type === "select" || f.type === "multi-select"))) !== null && _l !== void 0 ? _l : null;
|
|
75
73
|
const filterOptions = filterField
|
|
76
|
-
? ((
|
|
74
|
+
? ((_m = filterField.options) !== null && _m !== void 0 ? _m : []).map((opt) => {
|
|
77
75
|
var _a, _b;
|
|
78
76
|
if (typeof opt === "string")
|
|
79
77
|
return { id: opt.toLowerCase().replace(/\s+/g, "-"), label: opt };
|
|
@@ -84,24 +82,20 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, menuId, projec
|
|
|
84
82
|
projects,
|
|
85
83
|
schema,
|
|
86
84
|
filterOptions,
|
|
87
|
-
filterFieldKey: (
|
|
88
|
-
filterFieldName: (
|
|
85
|
+
filterFieldKey: (_o = filterField === null || filterField === void 0 ? void 0 : filterField.key) !== null && _o !== void 0 ? _o : null,
|
|
86
|
+
filterFieldName: (_p = filterField === null || filterField === void 0 ? void 0 : filterField.name) !== null && _p !== void 0 ? _p : "Project Type",
|
|
89
87
|
fieldOptionsMap,
|
|
90
88
|
};
|
|
91
89
|
}
|
|
92
|
-
// Direct fetch mode
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
]
|
|
97
|
-
const json = projectsRes.ok ? await projectsRes.json() : {};
|
|
98
|
-
const projects = ((_v = json === null || json === void 0 ? void 0 : json.data) !== null && _v !== void 0 ? _v : []).filter((p) => p.is_published !== false);
|
|
99
|
-
const schema = (_x = (_w = json === null || json === void 0 ? void 0 : json.client) === null || _w === void 0 ? void 0 : _w.custom_fields_schema) !== null && _x !== void 0 ? _x : [];
|
|
100
|
-
const fieldsJson = fieldsRes.ok ? await fieldsRes.json() : { fields: [] };
|
|
90
|
+
// Direct fetch mode — single call, /projects returns everything.
|
|
91
|
+
const res = await fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=${API_KEY}`, fetchOpts);
|
|
92
|
+
const json = res.ok ? await res.json() : {};
|
|
93
|
+
const projects = ((_q = json === null || json === void 0 ? void 0 : json.data) !== null && _q !== void 0 ? _q : []).filter((p) => p.is_published !== false);
|
|
94
|
+
const schema = (_s = (_r = json === null || json === void 0 ? void 0 : json.client) === null || _r === void 0 ? void 0 : _r.custom_fields_schema) !== null && _s !== void 0 ? _s : [];
|
|
101
95
|
const fieldOptionsMap = {};
|
|
102
|
-
for (const field of
|
|
96
|
+
for (const field of schema) {
|
|
103
97
|
const map = {};
|
|
104
|
-
for (const opt of ((
|
|
98
|
+
for (const opt of ((_t = field.options) !== null && _t !== void 0 ? _t : [])) {
|
|
105
99
|
if (typeof opt === "object" && opt.id && opt.label) {
|
|
106
100
|
map[opt.id] = opt.label;
|
|
107
101
|
map[opt.label] = opt.label;
|
|
@@ -109,9 +103,9 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, menuId, projec
|
|
|
109
103
|
}
|
|
110
104
|
fieldOptionsMap[field.key] = map;
|
|
111
105
|
}
|
|
112
|
-
const filterField = (
|
|
106
|
+
const filterField = (_u = schema.find((f) => f.is_filterable && (f.type === "select" || f.type === "multi-select"))) !== null && _u !== void 0 ? _u : null;
|
|
113
107
|
const filterOptions = filterField
|
|
114
|
-
? ((
|
|
108
|
+
? ((_v = filterField.options) !== null && _v !== void 0 ? _v : []).map((opt) => {
|
|
115
109
|
var _a, _b;
|
|
116
110
|
if (typeof opt === "string")
|
|
117
111
|
return { id: opt.toLowerCase().replace(/\s+/g, "-"), label: opt };
|
|
@@ -122,23 +116,26 @@ export function ProjectMenuClient({ dataUrl, clientSlug, apiBase, menuId, projec
|
|
|
122
116
|
projects,
|
|
123
117
|
schema,
|
|
124
118
|
filterOptions,
|
|
125
|
-
filterFieldKey: (
|
|
126
|
-
filterFieldName: (
|
|
119
|
+
filterFieldKey: (_w = filterField === null || filterField === void 0 ? void 0 : filterField.key) !== null && _w !== void 0 ? _w : null,
|
|
120
|
+
filterFieldName: (_x = filterField === null || filterField === void 0 ? void 0 : filterField.name) !== null && _x !== void 0 ? _x : "Project Type",
|
|
127
121
|
fieldOptionsMap,
|
|
128
122
|
};
|
|
129
123
|
}
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
124
|
+
// When noCache=true, skip the module cache entirely and always fetch fresh.
|
|
125
|
+
// Otherwise store the Promise on first call and reuse it on every subsequent mount.
|
|
126
|
+
const getOrFetch = noCache
|
|
127
|
+
? fetchAndCache()
|
|
128
|
+
: (menuDataCache.has(cacheKey)
|
|
129
|
+
? menuDataCache.get(cacheKey)
|
|
130
|
+
: (() => { const p = fetchAndCache(); menuDataCache.set(cacheKey, p); return p; })());
|
|
131
|
+
getOrFetch.then((data) => {
|
|
135
132
|
if (!cancelled)
|
|
136
133
|
setFetched(data);
|
|
137
134
|
}).catch(() => {
|
|
138
135
|
// silently fail — render nothing
|
|
139
136
|
});
|
|
140
137
|
return () => { cancelled = true; };
|
|
141
|
-
}, [dataUrl, clientSlug, apiBase, menuId]);
|
|
138
|
+
}, [dataUrl, clientSlug, apiBase, menuId, noCache]);
|
|
142
139
|
// Resolve data: prefer self-fetched, fall back to props
|
|
143
140
|
const projects = (_b = (_a = fetched === null || fetched === void 0 ? void 0 : fetched.projects) !== null && _a !== void 0 ? _a : projectsProp) !== null && _b !== void 0 ? _b : [];
|
|
144
141
|
const schema = (_d = (_c = fetched === null || fetched === void 0 ? void 0 : fetched.schema) !== null && _c !== void 0 ? _c : schemaProp) !== null && _d !== void 0 ? _d : [];
|
|
@@ -20,6 +20,11 @@ export interface SimilarProjectsProps {
|
|
|
20
20
|
maxItems?: number;
|
|
21
21
|
/** Seconds to cache. Defaults to 60 */
|
|
22
22
|
revalidate?: number;
|
|
23
|
+
/**
|
|
24
|
+
* When true, bypasses the Next.js Data Cache and always fetches fresh data.
|
|
25
|
+
* Sets fetch cache to "no-store". Useful during development or for frequently updated projects.
|
|
26
|
+
*/
|
|
27
|
+
noCache?: boolean;
|
|
23
28
|
/**
|
|
24
29
|
* Visual layout for the project cards.
|
|
25
30
|
* - "list" (default) — image + text with border-bottom separator
|
|
@@ -33,5 +38,5 @@ export interface SimilarProjectsProps {
|
|
|
33
38
|
*/
|
|
34
39
|
projectSlugs?: string[];
|
|
35
40
|
}
|
|
36
|
-
export declare function SimilarProjects({ filters, excludeSlug, clientSlug, apiBase, basePath, maxItems, revalidate, variant, projectSlugs, }: SimilarProjectsProps): Promise<import("react/jsx-runtime").JSX.Element | null>;
|
|
41
|
+
export declare function SimilarProjects({ filters, excludeSlug, clientSlug, apiBase, basePath, maxItems, revalidate, noCache, variant, projectSlugs, }: SimilarProjectsProps): Promise<import("react/jsx-runtime").JSX.Element | null>;
|
|
37
42
|
//# sourceMappingURL=SimilarProjects.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SimilarProjects.d.ts","sourceRoot":"","sources":["../src/SimilarProjects.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB;
|
|
1
|
+
{"version":3,"file":"SimilarProjects.d.ts","sourceRoot":"","sources":["../src/SimilarProjects.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB;AAiED,wBAAsB,eAAe,CAAC,EACpC,OAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,QAAY,EACZ,UAAe,EACf,OAAe,EACf,OAAgB,EAChB,YAAY,GACb,EAAE,oBAAoB,2DAiKtB"}
|
package/dist/SimilarProjects.js
CHANGED
|
@@ -18,10 +18,13 @@ function dedupeByKey(arr) {
|
|
|
18
18
|
return true;
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
const fetchSimilarData = cache(async (apiBase, clientSlug, revalidate) => {
|
|
21
|
+
const fetchSimilarData = cache(async (apiBase, clientSlug, revalidate, noCache) => {
|
|
23
22
|
var _a, _b, _c, _d;
|
|
24
|
-
const fetchOpts =
|
|
23
|
+
const fetchOpts = noCache
|
|
24
|
+
? { cache: "no-store" }
|
|
25
|
+
: revalidate > 0
|
|
26
|
+
? { next: { revalidate } }
|
|
27
|
+
: {};
|
|
25
28
|
const API_KEY = "pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR";
|
|
26
29
|
try {
|
|
27
30
|
// Single call — /projects returns projects AND client.custom_fields_schema with full options.
|
|
@@ -47,8 +50,8 @@ const fetchSimilarData = cache(async (apiBase, clientSlug, revalidate) => {
|
|
|
47
50
|
}
|
|
48
51
|
});
|
|
49
52
|
// ─── Component ────────────────────────────────────────────────────────────────
|
|
50
|
-
export async function SimilarProjects({ filters = {}, excludeSlug, clientSlug, apiBase, basePath = "/projects", maxItems = 3, revalidate = 60, variant = "list", projectSlugs, }) {
|
|
51
|
-
const { allProjects, schema, fieldOptionsMap } = await fetchSimilarData(apiBase, clientSlug, revalidate);
|
|
53
|
+
export async function SimilarProjects({ filters = {}, excludeSlug, clientSlug, apiBase, basePath = "/projects", maxItems = 3, revalidate = 60, noCache = false, variant = "list", projectSlugs, }) {
|
|
54
|
+
const { allProjects, schema, fieldOptionsMap } = await fetchSimilarData(apiBase, clientSlug, revalidate, noCache);
|
|
52
55
|
const badgeField = schema.find((f) => f.display_position === "badge_overlay");
|
|
53
56
|
const locationField = schema.find((f) => f.type === "location");
|
|
54
57
|
const font = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
@@ -100,11 +103,13 @@ export async function SimilarProjects({ filters = {}, excludeSlug, clientSlug, a
|
|
|
100
103
|
}
|
|
101
104
|
// ── List variant (default) ────────────────────────────────────────────────────
|
|
102
105
|
return (_jsxs("section", { style: { borderTop: "1px solid #e4e4e7", maxWidth: "1280px", margin: "0 auto", padding: "3rem 1rem 2rem", boxSizing: "border-box", fontFamily: font }, children: [sharedStyles, header, _jsx("div", { className: "chisel-similar-grid", children: similar.map((p) => {
|
|
103
|
-
var _a, _b, _c, _d, _e;
|
|
106
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
104
107
|
const imgUrl = (_d = (_a = p.image_url) !== null && _a !== void 0 ? _a : (_c = (_b = p.media) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url) !== null && _d !== void 0 ? _d : null;
|
|
105
|
-
const
|
|
108
|
+
const badgeRaw = badgeField
|
|
106
109
|
? ((_e = parseMultiValue(p.custom_field_values[badgeField.key])[0]) !== null && _e !== void 0 ? _e : null)
|
|
107
110
|
: null;
|
|
111
|
+
const badgeOptMap = badgeField ? ((_f = fieldOptionsMap[badgeField.key]) !== null && _f !== void 0 ? _f : {}) : {};
|
|
112
|
+
const badge = badgeRaw ? ((_g = badgeOptMap[badgeRaw]) !== null && _g !== void 0 ? _g : badgeRaw) : null;
|
|
108
113
|
const loc = locationField
|
|
109
114
|
? p.custom_field_values[locationField.key]
|
|
110
115
|
: null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-portfolio",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "Self-contained project portfolio components for Next.js App Router. Includes ProjectPortfolio, ProjectPortfolioClient (with built-in filtering), ProjectDetail, SimilarProjects, ProjectMenu, ProjectMenuClient, and GalleryCarousel. Pass a clientSlug and apiBase — done.",
|
|
5
5
|
"keywords": ["nextjs", "react", "portfolio", "projects", "megamenu", "gallery", "filtering"],
|
|
6
6
|
"license": "MIT",
|