project-portfolio 1.7.0 → 1.9.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.
- package/README.md +88 -1
- package/dist/ProjectCard.d.ts.map +1 -1
- package/dist/ProjectCard.js +4 -3
- package/dist/ProjectDetail.d.ts +1 -1
- package/dist/ProjectDetail.d.ts.map +1 -1
- package/dist/ProjectDetail.js +35 -67
- package/dist/ProjectFilters.d.ts.map +1 -1
- package/dist/ProjectFilters.js +1 -14
- package/dist/ProjectMenu.d.ts +17 -0
- package/dist/ProjectMenu.d.ts.map +1 -1
- package/dist/ProjectMenu.js +25 -3
- package/dist/ProjectMenuClient.d.ts +14 -8
- package/dist/ProjectMenuClient.d.ts.map +1 -1
- package/dist/ProjectMenuClient.js +71 -2
- package/dist/SimilarProjects.d.ts +25 -0
- package/dist/SimilarProjects.d.ts.map +1 -0
- package/dist/SimilarProjects.js +77 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -153,9 +153,96 @@ The filter options in the right sidebar are driven automatically by the `is_filt
|
|
|
153
153
|
|
|
154
154
|
---
|
|
155
155
|
|
|
156
|
+
### `SimilarProjects`
|
|
157
|
+
|
|
158
|
+
A standalone similar projects section. Fetches all projects for a client, filters to the same type as the current project, and renders up to 3 matching cards. Designed to be placed after `ProjectDetail` on a project detail page.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
// app/projects/[slug]/page.tsx
|
|
162
|
+
import { ProjectDetail, SimilarProjects } from "project-portfolio"
|
|
163
|
+
|
|
164
|
+
export default async function ProjectPage({ params }: { params: { slug: string } }) {
|
|
165
|
+
return (
|
|
166
|
+
<>
|
|
167
|
+
<ProjectDetail
|
|
168
|
+
slug={params.slug}
|
|
169
|
+
clientSlug="your-client-slug"
|
|
170
|
+
apiBase="https://your-api.com"
|
|
171
|
+
/>
|
|
172
|
+
<SimilarProjects
|
|
173
|
+
currentSlug={params.slug}
|
|
174
|
+
clientSlug="your-client-slug"
|
|
175
|
+
apiBase="https://your-api.com"
|
|
176
|
+
basePath="/projects"
|
|
177
|
+
/>
|
|
178
|
+
</>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
| Prop | Type | Required | Default | Description |
|
|
184
|
+
|---|---|---|---|---|
|
|
185
|
+
| `currentSlug` | `string` | Yes | — | Slug of the current project — excluded from results |
|
|
186
|
+
| `clientSlug` | `string` | Yes | — | Identifies which client's projects to load |
|
|
187
|
+
| `apiBase` | `string` | Yes | — | Base URL of the projects API |
|
|
188
|
+
| `basePath` | `string` | No | `"/projects"` | Base path for project detail links |
|
|
189
|
+
| `font` | `string` | No | System font stack | Font family string applied to all inline styles |
|
|
190
|
+
| `revalidate` | `number` | No | `60` | Cache revalidation period in seconds |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### `ProjectMenuClient`
|
|
195
|
+
|
|
196
|
+
A self-fetching client component version of `ProjectMenu`. Drop it anywhere — including inside `"use client"` components, existing navigation systems, WordPress, Webflow, or any React tree. It fetches its own data on mount when given `clientSlug` and `apiBase`.
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
// Works inside ANY component — no RSC required
|
|
200
|
+
"use client"
|
|
201
|
+
import { ProjectMenuClient } from "project-portfolio"
|
|
202
|
+
|
|
203
|
+
export function MegaMenu() {
|
|
204
|
+
return (
|
|
205
|
+
<ProjectMenuClient
|
|
206
|
+
clientSlug="your-client-slug"
|
|
207
|
+
apiBase="https://your-api.com"
|
|
208
|
+
basePath="/projects"
|
|
209
|
+
viewAllPath="/projects"
|
|
210
|
+
/>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
| Prop | Type | Required | Default | Description |
|
|
216
|
+
|---|---|---|---|---|
|
|
217
|
+
| `clientSlug` | `string` | Yes* | — | Client slug — triggers self-fetching mode when provided with `apiBase` |
|
|
218
|
+
| `apiBase` | `string` | Yes* | — | API base URL — triggers self-fetching mode when provided with `clientSlug` |
|
|
219
|
+
| `basePath` | `string` | Yes | — | Base path for project detail links |
|
|
220
|
+
| `viewAllPath` | `string` | Yes | — | Path for the "View All Projects" link |
|
|
221
|
+
| `subtitle` | `string` | No | — | Description shown above the project cards |
|
|
222
|
+
| `font` | `string` | No | System font stack | Font family string |
|
|
223
|
+
| `maxProjects` | `number` | No | `6` | Maximum number of projects to display |
|
|
224
|
+
|
|
225
|
+
*When not using self-fetching mode, pre-fetched `projects`, `schema`, `filterOptions`, `filterFieldKey`, and `fieldOptionsMap` can be passed directly instead.
|
|
226
|
+
|
|
227
|
+
**Advanced: pre-fetch with `fetchProjectMenuData`**
|
|
228
|
+
|
|
229
|
+
If you want server-side data fetching without RSC (e.g. in a Next.js API route), use `fetchProjectMenuData` and spread the result into `ProjectMenuClient`:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
import { fetchProjectMenuData } from "project-portfolio"
|
|
233
|
+
|
|
234
|
+
// In your API route or server action:
|
|
235
|
+
const data = await fetchProjectMenuData({ clientSlug: "...", apiBase: "..." })
|
|
236
|
+
|
|
237
|
+
// Then pass to the client component:
|
|
238
|
+
<ProjectMenuClient {...data} basePath="/projects" viewAllPath="/projects" />
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
156
243
|
## Important: Server Component Usage
|
|
157
244
|
|
|
158
|
-
All
|
|
245
|
+
All top-level components (`ProjectPortfolio`, `ProjectDetail`, `ProjectMenu`, `SimilarProjects`) are **async Server Components**. They must be rendered in a server context:
|
|
159
246
|
|
|
160
247
|
```tsx
|
|
161
248
|
// CORRECT
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectCard.d.ts","sourceRoot":"","sources":["../src/ProjectCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAmC,MAAM,SAAS,CAAA;AAE1F,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,CAAA;AAE5C,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,2FAA2F;IAC3F,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAeD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,MAAM,EACN,eAAoB,EACpB,QAAQ,EACR,OAAgB,EAChB,QAAsB,GACvB,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"ProjectCard.d.ts","sourceRoot":"","sources":["../src/ProjectCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAmC,MAAM,SAAS,CAAA;AAE1F,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,CAAA;AAE5C,UAAU,gBAAgB;IACxB,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,2FAA2F;IAC3F,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACxD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAeD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,MAAM,EACN,eAAoB,EACpB,QAAQ,EACR,OAAgB,EAChB,QAAsB,GACvB,EAAE,gBAAgB,2CA6PlB"}
|
package/dist/ProjectCard.js
CHANGED
|
@@ -19,6 +19,7 @@ export function ProjectCard({ project, schema, fieldOptionsMap = {}, priority, v
|
|
|
19
19
|
const tagFields = schema
|
|
20
20
|
.filter((f) => f.display_position === "tags" && f.type !== "number")
|
|
21
21
|
.filter((f, i, arr) => arr.findIndex((x) => x.key === f.key) === i);
|
|
22
|
+
// Numeric field for footer stat — the first visible number field (year completed)
|
|
22
23
|
const numericField = schema.find((f) => f.type === "number" && f.display_position !== "hidden");
|
|
23
24
|
const locationField = schema.find((f) => f.type === "location");
|
|
24
25
|
const badgeValue = badgeField
|
|
@@ -34,11 +35,11 @@ export function ProjectCard({ project, schema, fieldOptionsMap = {}, priority, v
|
|
|
34
35
|
if (!numericField)
|
|
35
36
|
return null;
|
|
36
37
|
const val = project.custom_field_values[numericField.key];
|
|
37
|
-
if (val === undefined || val === null)
|
|
38
|
+
if (val === undefined || val === null || val === "")
|
|
38
39
|
return null;
|
|
39
|
-
const numVal = typeof val === "number" ? val : parseFloat(String(val));
|
|
40
|
-
const formatted = isNaN(numVal) ? String(val) : `${numVal.toLocaleString()} SF`;
|
|
41
40
|
const label = numericField.name.replace(/\s*\([^)]+\)/, "");
|
|
41
|
+
const numVal = typeof val === "number" ? val : parseFloat(String(val));
|
|
42
|
+
const formatted = isNaN(numVal) ? String(val) : String(Math.round(numVal));
|
|
42
43
|
return { label, formatted };
|
|
43
44
|
})();
|
|
44
45
|
const compactTags = tagFields.flatMap((field) => parseMultiValue(project.custom_field_values[field.key]));
|
package/dist/ProjectDetail.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface ProjectDetailProps {
|
|
|
5
5
|
clientSlug: string;
|
|
6
6
|
/** Base URL of the projects API */
|
|
7
7
|
apiBase: string;
|
|
8
|
-
/** Base path for the "back" link. Defaults to "/projects" */
|
|
8
|
+
/** Base path for the "back" link and "View All" link. Defaults to "/projects" */
|
|
9
9
|
backPath?: string;
|
|
10
10
|
/** Label for the "back" link. Defaults to "All Projects" */
|
|
11
11
|
backLabel?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectDetail.d.ts","sourceRoot":"","sources":["../src/ProjectDetail.tsx"],"names":[],"mappings":"
|
|
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;CACpB;AAmDD,wBAAsB,aAAa,CAAC,EAClC,IAAI,EACJ,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,SAA0B,EAC1B,UAAe,GAChB,EAAE,kBAAkB,oDAyKpB"}
|
package/dist/ProjectDetail.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cache } from "react";
|
|
3
|
+
// LocationValue is used in locationValue cast below
|
|
3
4
|
import { GalleryCarousel } from "./GalleryCarousel";
|
|
4
5
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
5
6
|
function parseMultiValue(raw) {
|
|
@@ -20,73 +21,27 @@ function dedupeByKey(arr) {
|
|
|
20
21
|
}
|
|
21
22
|
// ─── Data fetching ───────────────────────────────────────────────────────────
|
|
22
23
|
const fetchProjectDetail = cache(async (apiBase, clientSlug, slug, revalidate) => {
|
|
23
|
-
var _a, _b, _c
|
|
24
|
+
var _a, _b, _c;
|
|
24
25
|
const fetchOpts = revalidate > 0
|
|
25
26
|
? { next: { revalidate } }
|
|
26
27
|
: {};
|
|
27
28
|
try {
|
|
28
|
-
const
|
|
29
|
-
fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects/${slug}?api_key=pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR`, fetchOpts),
|
|
30
|
-
fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR`, fetchOpts),
|
|
31
|
-
]);
|
|
32
|
-
// Per API docs, single project response is { client: {..., custom_fields_schema}, data: {...} }
|
|
29
|
+
const projectRes = await fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects/${slug}?api_key=pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR`, fetchOpts);
|
|
33
30
|
const projectJson = projectRes.ok ? await projectRes.json() : null;
|
|
34
31
|
const project = (_a = projectJson === null || projectJson === void 0 ? void 0 : projectJson.data) !== null && _a !== void 0 ? _a : null;
|
|
35
32
|
const schema = dedupeByKey((_c = (_b = projectJson === null || projectJson === void 0 ? void 0 : projectJson.client) === null || _b === void 0 ? void 0 : _b.custom_fields_schema) !== null && _c !== void 0 ? _c : []);
|
|
36
|
-
|
|
37
|
-
const allProjectsJson = allProjectsRes.ok ? await allProjectsRes.json() : null;
|
|
38
|
-
const allProjects = (_d = allProjectsJson === null || allProjectsJson === void 0 ? void 0 : allProjectsJson.data) !== null && _d !== void 0 ? _d : [];
|
|
39
|
-
return { project, schema, allProjects };
|
|
33
|
+
return { project, schema };
|
|
40
34
|
}
|
|
41
|
-
catch (
|
|
42
|
-
return { project: null, schema: []
|
|
35
|
+
catch (_d) {
|
|
36
|
+
return { project: null, schema: [] };
|
|
43
37
|
}
|
|
44
38
|
});
|
|
45
|
-
// ─── Similar Projects sub-component ─────────────────────────────────────────
|
|
46
|
-
function SimilarProjects({ allProjects, currentSlug, badgeValue, badgeField, locationField, basePath, font, }) {
|
|
47
|
-
const similar = allProjects
|
|
48
|
-
.filter((p) => {
|
|
49
|
-
var _a;
|
|
50
|
-
if (p.slug === currentSlug)
|
|
51
|
-
return false;
|
|
52
|
-
if (!badgeValue || !badgeField)
|
|
53
|
-
return true;
|
|
54
|
-
const val = (_a = parseMultiValue(p.custom_field_values[badgeField.key])[0]) !== null && _a !== void 0 ? _a : null;
|
|
55
|
-
return val === badgeValue;
|
|
56
|
-
})
|
|
57
|
-
.slice(0, 3);
|
|
58
|
-
if (similar.length === 0)
|
|
59
|
-
return null;
|
|
60
|
-
return (_jsxs("section", { style: { borderTop: "1px solid #e4e4e7", paddingTop: "3rem" }, children: [_jsxs("div", { style: { display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: "2rem" }, children: [_jsxs("div", { children: [_jsx("p", { style: { fontSize: "11px", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: "#f18a00", margin: "0 0 6px 0" }, children: "More Work" }), _jsx("h2", { style: { color: "#18181b", fontWeight: 700, fontSize: "28px", margin: 0, fontFamily: font }, children: "Similar Projects" })] }), _jsxs("a", { href: basePath, style: { color: "#18181b", fontWeight: 600, fontSize: "15px", textDecoration: "none", display: "flex", alignItems: "center", gap: "6px", fontFamily: font }, children: ["View All", _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M9 18l6-6-6-6" }) })] })] }), _jsx("div", { className: "chisel-similar-grid", children: similar.map((p) => {
|
|
61
|
-
var _a, _b, _c, _d, _e;
|
|
62
|
-
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;
|
|
63
|
-
const badge = badgeField
|
|
64
|
-
? ((_e = parseMultiValue(p.custom_field_values[badgeField.key])[0]) !== null && _e !== void 0 ? _e : null)
|
|
65
|
-
: null;
|
|
66
|
-
const loc = locationField
|
|
67
|
-
? p.custom_field_values[locationField.key]
|
|
68
|
-
: null;
|
|
69
|
-
const locStr = loc ? [loc.city, loc.state].filter(Boolean).join(", ") : null;
|
|
70
|
-
return (_jsxs("a", { href: `${basePath}/${p.slug}`, style: { textDecoration: "none", color: "inherit", display: "block", borderBottom: "1px solid #e4e4e7", fontFamily: font }, children: [_jsxs("div", { style: { position: "relative", width: "100%", height: "220px", overflow: "hidden", backgroundColor: "#f4f4f5", marginBottom: "1rem" }, children: [imgUrl && (_jsx("img", { src: imgUrl, alt: p.title, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } })), badge && (_jsx("span", { style: {
|
|
71
|
-
position: "absolute",
|
|
72
|
-
top: "12px",
|
|
73
|
-
left: "12px",
|
|
74
|
-
backgroundColor: "#f18a00",
|
|
75
|
-
color: "#fff",
|
|
76
|
-
fontSize: "10px",
|
|
77
|
-
fontWeight: 700,
|
|
78
|
-
textTransform: "uppercase",
|
|
79
|
-
letterSpacing: "0.1em",
|
|
80
|
-
padding: "4px 10px",
|
|
81
|
-
}, children: badge }))] }), _jsxs("div", { style: { paddingBottom: "1.25rem" }, children: [_jsx("h3", { style: { color: "#18181b", fontWeight: 700, fontSize: "17px", lineHeight: 1.3, margin: "0 0 8px 0", fontFamily: font }, children: p.title }), locStr && (_jsxs("p", { style: { display: "flex", alignItems: "center", gap: "5px", color: "#71717a", fontSize: "14px", margin: 0 }, children: [_jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0 }, children: [_jsx("path", { d: "M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" }), _jsx("circle", { cx: "12", cy: "10", r: "3" })] }), locStr] }))] })] }, p.id));
|
|
82
|
-
}) })] }));
|
|
83
|
-
}
|
|
84
39
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
85
40
|
export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/projects", backLabel = "All Projects", revalidate = 60, }) {
|
|
86
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
87
|
-
const { project, schema
|
|
41
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
42
|
+
const { project, schema } = await fetchProjectDetail(apiBase, clientSlug, slug, revalidate);
|
|
88
43
|
if (!project) {
|
|
89
|
-
return (
|
|
44
|
+
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." }) }));
|
|
90
45
|
}
|
|
91
46
|
const imageUrl = (_d = (_a = project.image_url) !== null && _a !== void 0 ? _a : (_c = (_b = project.media) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url) !== null && _d !== void 0 ? _d : null;
|
|
92
47
|
const galleryImages = project.image_url
|
|
@@ -94,9 +49,16 @@ export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/pr
|
|
|
94
49
|
: ((_g = (_f = project.media) === null || _f === void 0 ? void 0 : _f.slice(1)) !== null && _g !== void 0 ? _g : []);
|
|
95
50
|
const badgeField = schema.find((f) => f.display_position === "badge_overlay");
|
|
96
51
|
const locationField = schema.find((f) => f.type === "location");
|
|
97
|
-
|
|
52
|
+
// Year field: any number field whose key or name contains "year"
|
|
53
|
+
const yearField = schema.find((f) => f.type === "number" && f.display_position !== "hidden" &&
|
|
54
|
+
(/year/i.test(f.key) || /year/i.test(f.name)));
|
|
55
|
+
// Sq footage field: any field (text or number) whose key or name matches common area/coverage terms
|
|
56
|
+
// Falls back to the first non-year number field so it works regardless of client field naming
|
|
57
|
+
const sqftField = (_h = schema.find((f) => f.display_position !== "hidden" &&
|
|
58
|
+
(/coverage|sq.?ft|square.?foot|area|footage/i.test(f.key) || /coverage|sq.?ft|square.?foot|area|footage/i.test(f.name)))) !== null && _h !== void 0 ? _h : schema.find((f) => f.type === "number" && f.display_position !== "hidden" &&
|
|
59
|
+
!(/year/i.test(f.key) || /year/i.test(f.name)));
|
|
98
60
|
const badgeValue = badgeField
|
|
99
|
-
? ((
|
|
61
|
+
? ((_j = parseMultiValue(project.custom_field_values[badgeField.key])[0]) !== null && _j !== void 0 ? _j : null)
|
|
100
62
|
: null;
|
|
101
63
|
const locationValue = locationField
|
|
102
64
|
? project.custom_field_values[locationField.key]
|
|
@@ -104,20 +66,26 @@ export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/pr
|
|
|
104
66
|
const locationString = locationValue
|
|
105
67
|
? [locationValue.city, locationValue.state].filter(Boolean).join(", ")
|
|
106
68
|
: null;
|
|
107
|
-
const rawCoverage = numericField ? project.custom_field_values[numericField.key] : null;
|
|
108
69
|
const coverageFormatted = (() => {
|
|
109
|
-
|
|
70
|
+
const raw = sqftField ? project.custom_field_values[sqftField.key] : null;
|
|
71
|
+
if (raw === null || raw === undefined || raw === "")
|
|
72
|
+
return null;
|
|
73
|
+
if (typeof raw === "string")
|
|
74
|
+
return raw; // already formatted e.g. "225 SF"
|
|
75
|
+
const n = typeof raw === "number" ? raw : parseFloat(String(raw));
|
|
76
|
+
return isNaN(n) ? String(raw) : `${n.toLocaleString()} SF`;
|
|
77
|
+
})();
|
|
78
|
+
const yearCompleted = (() => {
|
|
79
|
+
const raw = yearField ? project.custom_field_values[yearField.key] : null;
|
|
80
|
+
if (raw === null || raw === undefined)
|
|
110
81
|
return null;
|
|
111
|
-
const n = typeof
|
|
112
|
-
return isNaN(n) ? String(
|
|
82
|
+
const n = typeof raw === "number" ? raw : parseFloat(String(raw));
|
|
83
|
+
return isNaN(n) ? String(raw) : String(Math.round(n));
|
|
113
84
|
})();
|
|
114
85
|
const font = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
115
|
-
return (_jsxs("main", { style: { minHeight: "100vh", backgroundColor: "#fff", fontFamily: font }, children: [_jsxs("section", { className: "chisel-hero-img", style: { position: "relative", width: "100%", overflow: "hidden" }, children: [imageUrl ? (_jsx("img", { src: imageUrl, alt: project.title, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } })) : (_jsx("div", { style: { position: "absolute", inset: 0, backgroundColor: "#27272a" } })), _jsx("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(to top, rgba(0,0,0,0.82) 0%, rgba(0,0,0,0.38) 50%, rgba(0,0,0,0.12) 100%)" } }),
|
|
86
|
+
return (_jsxs("main", { style: { minHeight: "100vh", backgroundColor: "#fff", fontFamily: font }, children: [_jsxs("section", { className: "chisel-hero-img", style: { position: "relative", width: "100%", overflow: "hidden" }, children: [imageUrl ? (_jsx("img", { src: imageUrl, alt: project.title, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } })) : (_jsx("div", { style: { position: "absolute", inset: 0, backgroundColor: "#27272a" } })), _jsx("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(to top, rgba(0,0,0,0.82) 0%, rgba(0,0,0,0.38) 50%, rgba(0,0,0,0.12) 100%)" } }), _jsxs("div", { style: { position: "absolute", bottom: 0, left: 0, right: 0, padding: "0 1.5rem 2.5rem", maxWidth: "900px" }, children: [badgeValue && (_jsx("p", { style: { color: "#f18a00", fontSize: "11px", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", margin: "0 0 10px 0" }, children: badgeValue })), _jsx("h1", { style: { color: "#fff", fontWeight: 700, fontSize: "clamp(28px, 4vw, 52px)", lineHeight: 1.1, margin: "0 0 12px 0", fontFamily: font }, children: project.title }), locationString && (_jsxs("p", { style: { display: "flex", alignItems: "center", gap: "6px", color: "rgba(255,255,255,0.75)", fontSize: "15px", margin: 0 }, children: [_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0 }, children: [_jsx("path", { d: "M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" }), _jsx("circle", { cx: "12", cy: "10", r: "3" })] }), locationString] }))] })] }), _jsxs("section", { style: { borderBottom: "1px solid #e4e4e7", backgroundColor: "#fff" }, children: [_jsx("style", { children: `
|
|
116
87
|
.chisel-stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.25rem; }
|
|
117
88
|
@media (min-width: 768px) { .chisel-stats-grid { grid-template-columns: repeat(4, 1fr); gap: 2rem; } }
|
|
118
|
-
.chisel-similar-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
|
|
119
|
-
@media (min-width: 640px) { .chisel-similar-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
120
|
-
@media (min-width: 1024px) { .chisel-similar-grid { grid-template-columns: repeat(3, 1fr); } }
|
|
121
89
|
.chisel-gallery-placeholder { display: grid; grid-template-columns: 1fr; gap: 12px; }
|
|
122
90
|
@media (min-width: 640px) { .chisel-gallery-placeholder { grid-template-columns: repeat(3, 1fr); } }
|
|
123
91
|
.chisel-hero-img { height: 50vw; min-height: 220px; max-height: 560px; }
|
|
@@ -126,8 +94,8 @@ export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/pr
|
|
|
126
94
|
{ label: "Location", value: locationString !== null && locationString !== void 0 ? locationString : "—" },
|
|
127
95
|
{ label: "Project Type", value: badgeValue !== null && badgeValue !== void 0 ? badgeValue : "—" },
|
|
128
96
|
{ label: "Sq. Footage", value: coverageFormatted !== null && coverageFormatted !== void 0 ? coverageFormatted : "—" },
|
|
129
|
-
{ label: "Completed", value:
|
|
130
|
-
].map(({ label, value }) => (_jsxs("div", { children: [_jsx("p", { style: { fontSize: "10px", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.1em", color: "#a1a1aa", margin: "0 0 6px 0" }, children: label }), _jsx("p", { style: { color: "#18181b", fontWeight: 600, fontSize: "15px", margin: 0 }, children: value })] }, label))) })] }), _jsxs("article", { style: { maxWidth: "1280px", margin: "0 auto", padding: "2rem 1rem", boxSizing: "border-box", display: "flex", flexDirection: "column", gap: "2.5rem" }, children: [(project.blurb || project.description) && (_jsxs("section", { style: { maxWidth: "
|
|
97
|
+
{ label: "Completed", value: yearCompleted !== null && yearCompleted !== void 0 ? yearCompleted : "—" },
|
|
98
|
+
].map(({ label, value }) => (_jsxs("div", { children: [_jsx("p", { style: { fontSize: "10px", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.1em", color: "#a1a1aa", margin: "0 0 6px 0" }, children: label }), _jsx("p", { style: { color: "#18181b", fontWeight: 600, fontSize: "15px", margin: 0 }, children: value })] }, label))) })] }), _jsxs("article", { style: { maxWidth: "1280px", margin: "0 auto", padding: "2rem 1rem", boxSizing: "border-box", display: "flex", flexDirection: "column", gap: "2.5rem" }, children: [(project.blurb || project.description) && (_jsxs("section", { style: { maxWidth: "900px" }, children: [_jsx("h2", { style: { color: "#18181b", fontWeight: 700, fontSize: "22px", margin: "0 0 16px 0", fontFamily: font }, children: "Project Overview" }), project.blurb && (_jsx("p", { style: { color: "#3f3f46", fontSize: "16px", lineHeight: 1.7, margin: "0 0 16px 0" }, children: project.blurb })), project.description && project.description !== project.blurb && (_jsx("p", { style: { color: "#3f3f46", fontSize: "16px", lineHeight: 1.7, margin: 0 }, children: project.description }))] })), _jsxs("section", { children: [_jsx("h2", { style: { color: "#18181b", fontWeight: 700, fontSize: "22px", margin: "0 0 1.5rem 0", fontFamily: font }, children: "Project Gallery" }), galleryImages.length > 0 ? (_jsx(GalleryCarousel, { images: galleryImages, projectTitle: project.title })) : (
|
|
131
99
|
/* Placeholder */
|
|
132
|
-
_jsx("div", { className: "chisel-gallery-placeholder", children: [0, 1, 2].map((i) => (_jsxs("div", { style: { aspectRatio: "4/3", borderRadius: "4px", backgroundColor: "#f9f9f9", border: "1px solid #e4e4e7", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "8px" }, children: [_jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#a1a1aa", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909M3.75 19.5h16.5" }) }), _jsx("p", { style: { color: "#a1a1aa", fontSize: "12px", margin: 0 }, children: "Photos coming soon" })] }, i))) }))] })
|
|
100
|
+
_jsx("div", { className: "chisel-gallery-placeholder", children: [0, 1, 2].map((i) => (_jsxs("div", { style: { aspectRatio: "4/3", borderRadius: "4px", backgroundColor: "#f9f9f9", border: "1px solid #e4e4e7", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "8px" }, children: [_jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#a1a1aa", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909M3.75 19.5h16.5" }) }), _jsx("p", { style: { color: "#a1a1aa", fontSize: "12px", margin: 0 }, children: "Photos coming soon" })] }, i))) }))] })] })] }));
|
|
133
101
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectFilters.d.ts","sourceRoot":"","sources":["../src/ProjectFilters.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"ProjectFilters.d.ts","sourceRoot":"","sources":["../src/ProjectFilters.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAEhD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpD,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAO,EACP,cAAc,EACd,cAAc,EACd,gBAAgB,GACjB,EAAE,mBAAmB,2CAgGrB"}
|
package/dist/ProjectFilters.js
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
function selectOptionsForField(field) {
|
|
3
|
-
const out = [];
|
|
4
|
-
const seen = new Set();
|
|
5
|
-
for (const opt of field.options) {
|
|
6
|
-
const value = typeof opt === "string" ? opt : opt.id;
|
|
7
|
-
const label = typeof opt === "string" ? opt : opt.label;
|
|
8
|
-
if (seen.has(value))
|
|
9
|
-
continue;
|
|
10
|
-
seen.add(value);
|
|
11
|
-
out.push({ value, label });
|
|
12
|
-
}
|
|
13
|
-
return out;
|
|
14
|
-
}
|
|
15
2
|
export function ProjectFilters({ fields, filters, onFilterChange, onClearFilters, hasActiveFilters, }) {
|
|
16
3
|
const deduped = fields.filter((f, i, arr) => arr.findIndex((x) => x.key === f.key) === i);
|
|
17
4
|
return (_jsxs("div", { style: {
|
|
@@ -34,7 +21,7 @@ export function ProjectFilters({ fields, filters, onFilterChange, onClearFilters
|
|
|
34
21
|
color: "#18181b",
|
|
35
22
|
outline: "none",
|
|
36
23
|
cursor: "pointer",
|
|
37
|
-
}, children: [_jsxs("option", { value: "all", children: ["All ", field.name] }),
|
|
24
|
+
}, children: [_jsxs("option", { value: "all", children: ["All ", field.name] }), [...new Set(field.options.map((o) => typeof o === "string" ? o : o.label))].map((option, i) => (_jsx("option", { value: option, children: option }, `${field.key}-opt-${i}`)))] })] }, `filter-${fieldIndex}-${field.key}`));
|
|
38
25
|
}
|
|
39
26
|
if (field.type === "text" || field.type === "location") {
|
|
40
27
|
return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: [_jsx("label", { style: { fontSize: "13px", fontWeight: 500, color: "#71717a" }, children: field.name }), _jsx("input", { type: "text", placeholder: `Search ${field.name.toLowerCase()}...`, value: filters[field.key] || "", onChange: (e) => onFilterChange(field.key, e.target.value), style: {
|
package/dist/ProjectMenu.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Project, CustomFieldSchema } from "./types";
|
|
1
2
|
export interface ProjectMenuProps {
|
|
2
3
|
/** Client slug used to fetch projects */
|
|
3
4
|
clientSlug: string;
|
|
@@ -21,5 +22,21 @@ export interface ProjectMenuProps {
|
|
|
21
22
|
*/
|
|
22
23
|
noCache?: boolean;
|
|
23
24
|
}
|
|
25
|
+
export declare function fetchProjectMenuData({ apiBase, clientSlug, revalidate, noCache, }: {
|
|
26
|
+
apiBase: string;
|
|
27
|
+
clientSlug: string;
|
|
28
|
+
revalidate?: number;
|
|
29
|
+
noCache?: boolean;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
projects: Project[];
|
|
32
|
+
schema: CustomFieldSchema[];
|
|
33
|
+
filterOptions: {
|
|
34
|
+
id: string;
|
|
35
|
+
label: string;
|
|
36
|
+
}[];
|
|
37
|
+
filterFieldKey: string | null;
|
|
38
|
+
filterFieldName: string;
|
|
39
|
+
fieldOptionsMap: Record<string, Record<string, string>>;
|
|
40
|
+
}>;
|
|
24
41
|
export declare function ProjectMenu({ clientSlug, apiBase, basePath, viewAllPath, subtitle, font, maxProjects, revalidate, noCache, }: ProjectMenuProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
25
42
|
//# sourceMappingURL=ProjectMenu.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectMenu.d.ts","sourceRoot":"","sources":["../src/ProjectMenu.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectMenu.d.ts","sourceRoot":"","sources":["../src/ProjectMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAKzD,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAA;IAClB,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,wBAAsB,oBAAoB,CAAC,EACzC,OAAO,EACP,UAAU,EACV,UAAe,EACf,OAAe,GAChB,EAAE;IACD,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,GAAG,OAAO,CAAC;IACV,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,aAAa,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC9C,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACxD,CAAC,CAqBD;AA6CD,wBAAsB,WAAW,CAAC,EAChC,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,WAAW,EACX,QAAQ,EACR,IAA0E,EAC1E,WAAe,EACf,UAAe,EACf,OAAe,GAChB,EAAE,gBAAgB,oDAiClB"}
|
package/dist/ProjectMenu.js
CHANGED
|
@@ -2,7 +2,29 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { cache } from "react";
|
|
3
3
|
import { ProjectMenuClient } from "./ProjectMenuClient";
|
|
4
4
|
const API_KEY = "pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR";
|
|
5
|
-
|
|
5
|
+
export async function fetchProjectMenuData({ apiBase, clientSlug, revalidate = 60, noCache = false, }) {
|
|
6
|
+
var _a, _b, _c, _d;
|
|
7
|
+
const raw = await _fetchMenuData(apiBase, clientSlug, revalidate, noCache);
|
|
8
|
+
const filterField = (_a = raw.schema.find((f) => f.is_filterable && (f.type === "select" || f.type === "multi-select"))) !== null && _a !== void 0 ? _a : null;
|
|
9
|
+
const filterOptions = filterField
|
|
10
|
+
? ((_b = filterField.options) !== null && _b !== void 0 ? _b : []).map((opt) => {
|
|
11
|
+
var _a, _b, _c;
|
|
12
|
+
if (typeof opt === "string") {
|
|
13
|
+
return { id: opt.toLowerCase().replace(/\s+/g, "-"), label: opt };
|
|
14
|
+
}
|
|
15
|
+
return { id: (_a = opt.id) !== null && _a !== void 0 ? _a : String((_b = opt.label) !== null && _b !== void 0 ? _b : "").toLowerCase().replace(/\s+/g, "-"), label: (_c = opt.label) !== null && _c !== void 0 ? _c : "" };
|
|
16
|
+
})
|
|
17
|
+
: [];
|
|
18
|
+
return {
|
|
19
|
+
projects: raw.projects,
|
|
20
|
+
schema: raw.schema,
|
|
21
|
+
filterOptions,
|
|
22
|
+
filterFieldKey: (_c = filterField === null || filterField === void 0 ? void 0 : filterField.key) !== null && _c !== void 0 ? _c : null,
|
|
23
|
+
filterFieldName: (_d = filterField === null || filterField === void 0 ? void 0 : filterField.name) !== null && _d !== void 0 ? _d : "Project Type",
|
|
24
|
+
fieldOptionsMap: raw.fieldOptionsMap,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const _fetchMenuData = cache(async (apiBase, clientSlug, revalidate, noCache = false) => {
|
|
6
28
|
var _a, _b, _c, _d, _e;
|
|
7
29
|
const fetchOpts = noCache
|
|
8
30
|
? { cache: "no-store" }
|
|
@@ -38,7 +60,7 @@ const fetchMenuData = cache(async (apiBase, clientSlug, revalidate, noCache = fa
|
|
|
38
60
|
});
|
|
39
61
|
export async function ProjectMenu({ clientSlug, apiBase, basePath = "/projects", viewAllPath, subtitle, font = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", maxProjects = 6, revalidate = 60, noCache = false, }) {
|
|
40
62
|
var _a, _b, _c, _d;
|
|
41
|
-
const { projects, schema, fieldOptionsMap } = await
|
|
63
|
+
const { projects, schema, fieldOptionsMap } = await _fetchMenuData(apiBase, clientSlug, revalidate, noCache);
|
|
42
64
|
// Find the filterable select field (badge_overlay is our category field)
|
|
43
65
|
const filterField = (_a = schema.find((f) => f.is_filterable && (f.type === "select" || f.type === "multi-select"))) !== null && _a !== void 0 ? _a : null;
|
|
44
66
|
// Build filter option list from schema
|
|
@@ -48,7 +70,7 @@ export async function ProjectMenu({ clientSlug, apiBase, basePath = "/projects",
|
|
|
48
70
|
if (typeof opt === "string") {
|
|
49
71
|
return { id: opt.toLowerCase().replace(/\s+/g, "-"), label: opt };
|
|
50
72
|
}
|
|
51
|
-
return { id: (_a = opt.id) !== null && _a !== void 0 ? _a : String((_b = opt.label) !== null && _b !== void 0 ? _b : "").toLowerCase().replace(/\s+/g, "-"), label: (_c = opt.label) !== null && _c !== void 0 ? _c :
|
|
73
|
+
return { id: (_a = opt.id) !== null && _a !== void 0 ? _a : String((_b = opt.label) !== null && _b !== void 0 ? _b : "").toLowerCase().replace(/\s+/g, "-"), label: (_c = opt.label) !== null && _c !== void 0 ? _c : "" };
|
|
52
74
|
})
|
|
53
75
|
: [];
|
|
54
76
|
return (_jsx(ProjectMenuClient, { projects: projects, schema: schema, filterOptions: filterOptions, filterFieldKey: (_c = filterField === null || filterField === void 0 ? void 0 : filterField.key) !== null && _c !== void 0 ? _c : null, filterFieldName: (_d = filterField === null || filterField === void 0 ? void 0 : filterField.name) !== null && _d !== void 0 ? _d : "Project Type", fieldOptionsMap: fieldOptionsMap, subtitle: subtitle, basePath: basePath, viewAllPath: viewAllPath !== null && viewAllPath !== void 0 ? viewAllPath : basePath, font: font, maxProjects: maxProjects }));
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import type { Project, CustomFieldSchema } from "./types";
|
|
2
|
-
interface ProjectMenuClientProps {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
export interface ProjectMenuClientProps {
|
|
3
|
+
/**
|
|
4
|
+
* Pass clientSlug + apiBase to have the component fetch its own data.
|
|
5
|
+
* Use this in non-RSC environments (client components, WordPress, Webflow, etc.)
|
|
6
|
+
* Alternatively, pass projects + schema + filterOptions directly for SSR usage.
|
|
7
|
+
*/
|
|
8
|
+
clientSlug?: string;
|
|
9
|
+
apiBase?: string;
|
|
10
|
+
projects?: Project[];
|
|
11
|
+
schema?: CustomFieldSchema[];
|
|
12
|
+
filterOptions?: {
|
|
6
13
|
id: string;
|
|
7
14
|
label: string;
|
|
8
15
|
}[];
|
|
9
|
-
filterFieldKey
|
|
16
|
+
filterFieldKey?: string | null;
|
|
10
17
|
filterFieldName?: string;
|
|
11
18
|
fieldOptionsMap?: Record<string, Record<string, string>>;
|
|
12
19
|
subtitle?: string;
|
|
13
20
|
basePath: string;
|
|
14
21
|
viewAllPath: string;
|
|
15
|
-
font
|
|
22
|
+
font?: string;
|
|
16
23
|
maxProjects?: number;
|
|
17
24
|
}
|
|
18
|
-
export declare function ProjectMenuClient({ projects, schema, filterOptions, filterFieldKey, filterFieldName, fieldOptionsMap, subtitle, basePath, viewAllPath, font, maxProjects, }: ProjectMenuClientProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
-
export {};
|
|
25
|
+
export declare function ProjectMenuClient({ clientSlug, apiBase, projects: projectsProp, schema: schemaProp, filterOptions: filterOptionsProp, filterFieldKey: filterFieldKeyProp, filterFieldName: filterFieldNameProp, fieldOptionsMap: fieldOptionsMapProp, subtitle, basePath, viewAllPath, font, maxProjects, }: ProjectMenuClientProps): import("react/jsx-runtime").JSX.Element;
|
|
20
26
|
//# sourceMappingURL=ProjectMenuClient.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectMenuClient.d.ts","sourceRoot":"","sources":["../src/ProjectMenuClient.tsx"],"names":[],"mappings":"
|
|
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;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB,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;AAMD,wBAAgB,iBAAiB,CAAC,EAChC,UAAU,EACV,OAAO,EACP,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,2CA2axB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
4
|
function parseSingleValue(raw) {
|
|
5
5
|
if (typeof raw === "string")
|
|
6
6
|
return raw.replace(/`/g, "").trim();
|
|
@@ -14,9 +14,78 @@ function parseMultiValue(raw) {
|
|
|
14
14
|
return [];
|
|
15
15
|
}
|
|
16
16
|
const ACCENT = "oklch(0.78 0.16 85)";
|
|
17
|
-
|
|
17
|
+
const API_KEY = "pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR";
|
|
18
|
+
const DEFAULT_FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
19
|
+
export function ProjectMenuClient({ clientSlug, apiBase, projects: projectsProp, schema: schemaProp, filterOptions: filterOptionsProp, filterFieldKey: filterFieldKeyProp, filterFieldName: filterFieldNameProp = "Project Type", fieldOptionsMap: fieldOptionsMapProp = {}, subtitle, basePath, viewAllPath, font = DEFAULT_FONT, maxProjects = 6, }) {
|
|
20
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
18
21
|
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
19
22
|
const [hoveredCard, setHoveredCard] = useState(null);
|
|
23
|
+
const [fetched, setFetched] = useState(null);
|
|
24
|
+
// Self-fetch mode: when clientSlug + apiBase are provided, fetch on mount
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!clientSlug || !apiBase)
|
|
27
|
+
return;
|
|
28
|
+
let cancelled = false;
|
|
29
|
+
async function load() {
|
|
30
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
31
|
+
try {
|
|
32
|
+
const [projectsRes, fieldsRes] = await Promise.all([
|
|
33
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=${API_KEY}`),
|
|
34
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/fields?api_key=${API_KEY}`),
|
|
35
|
+
]);
|
|
36
|
+
if (cancelled)
|
|
37
|
+
return;
|
|
38
|
+
const json = projectsRes.ok ? await projectsRes.json() : {};
|
|
39
|
+
const projects = (_a = json === null || json === void 0 ? void 0 : json.data) !== null && _a !== void 0 ? _a : [];
|
|
40
|
+
const schema = (_c = (_b = json === null || json === void 0 ? void 0 : json.client) === null || _b === void 0 ? void 0 : _b.custom_fields_schema) !== null && _c !== void 0 ? _c : [];
|
|
41
|
+
const fieldsJson = fieldsRes.ok ? await fieldsRes.json() : { fields: [] };
|
|
42
|
+
const fieldOptionsMap = {};
|
|
43
|
+
for (const field of ((_d = fieldsJson.fields) !== null && _d !== void 0 ? _d : [])) {
|
|
44
|
+
const map = {};
|
|
45
|
+
for (const opt of ((_e = field.options) !== null && _e !== void 0 ? _e : [])) {
|
|
46
|
+
if (typeof opt === "object" && opt.id && opt.label) {
|
|
47
|
+
map[opt.id] = opt.label;
|
|
48
|
+
map[opt.label] = opt.label;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
fieldOptionsMap[field.key] = map;
|
|
52
|
+
}
|
|
53
|
+
const filterField = (_f = schema.find((f) => f.is_filterable && (f.type === "select" || f.type === "multi-select"))) !== null && _f !== void 0 ? _f : null;
|
|
54
|
+
const filterOptions = filterField
|
|
55
|
+
? ((_g = filterField.options) !== null && _g !== void 0 ? _g : []).map((opt) => {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
if (typeof opt === "string")
|
|
58
|
+
return { id: opt.toLowerCase().replace(/\s+/g, "-"), label: opt };
|
|
59
|
+
return { id: (_a = opt.id) !== null && _a !== void 0 ? _a : "", label: (_b = opt.label) !== null && _b !== void 0 ? _b : "" };
|
|
60
|
+
})
|
|
61
|
+
: [];
|
|
62
|
+
setFetched({
|
|
63
|
+
projects,
|
|
64
|
+
schema,
|
|
65
|
+
filterOptions,
|
|
66
|
+
filterFieldKey: (_h = filterField === null || filterField === void 0 ? void 0 : filterField.key) !== null && _h !== void 0 ? _h : null,
|
|
67
|
+
filterFieldName: (_j = filterField === null || filterField === void 0 ? void 0 : filterField.name) !== null && _j !== void 0 ? _j : "Project Type",
|
|
68
|
+
fieldOptionsMap,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (_k) {
|
|
72
|
+
// silently fail — render nothing
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
load();
|
|
76
|
+
return () => { cancelled = true; };
|
|
77
|
+
}, [clientSlug, apiBase]);
|
|
78
|
+
// Resolve data: prefer self-fetched, fall back to props
|
|
79
|
+
const projects = (_b = (_a = fetched === null || fetched === void 0 ? void 0 : fetched.projects) !== null && _a !== void 0 ? _a : projectsProp) !== null && _b !== void 0 ? _b : [];
|
|
80
|
+
const schema = (_d = (_c = fetched === null || fetched === void 0 ? void 0 : fetched.schema) !== null && _c !== void 0 ? _c : schemaProp) !== null && _d !== void 0 ? _d : [];
|
|
81
|
+
const filterOptions = (_f = (_e = fetched === null || fetched === void 0 ? void 0 : fetched.filterOptions) !== null && _e !== void 0 ? _e : filterOptionsProp) !== null && _f !== void 0 ? _f : [];
|
|
82
|
+
const filterFieldKey = (_h = (_g = fetched === null || fetched === void 0 ? void 0 : fetched.filterFieldKey) !== null && _g !== void 0 ? _g : filterFieldKeyProp) !== null && _h !== void 0 ? _h : null;
|
|
83
|
+
const filterFieldName = (_j = fetched === null || fetched === void 0 ? void 0 : fetched.filterFieldName) !== null && _j !== void 0 ? _j : filterFieldNameProp;
|
|
84
|
+
const fieldOptionsMap = (_k = fetched === null || fetched === void 0 ? void 0 : fetched.fieldOptionsMap) !== null && _k !== void 0 ? _k : fieldOptionsMapProp;
|
|
85
|
+
// Show a minimal skeleton while self-fetching
|
|
86
|
+
if (clientSlug && apiBase && !fetched) {
|
|
87
|
+
return (_jsx("div", { style: { padding: "2rem 2.5rem", fontFamily: font, color: "#a1a1aa", fontSize: "14px" }, children: "Loading projects..." }));
|
|
88
|
+
}
|
|
20
89
|
const displayed = projects.slice(0, maxProjects);
|
|
21
90
|
const badgeField = schema.find((f) => f.display_position === "badge_overlay");
|
|
22
91
|
const tagsField = schema.find((f) => f.display_position === "tags");
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface SimilarProjectsProps {
|
|
2
|
+
/**
|
|
3
|
+
* Key/value pairs to filter projects by custom field values.
|
|
4
|
+
* e.g. { type: "commercial" } or { type: "educational-facilities" }
|
|
5
|
+
* All filters must match (AND logic).
|
|
6
|
+
*/
|
|
7
|
+
filters?: Record<string, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Slug of a project to exclude from results (e.g. the currently viewed project).
|
|
10
|
+
* Optional — omit if you don't need to exclude any project.
|
|
11
|
+
*/
|
|
12
|
+
excludeSlug?: string;
|
|
13
|
+
/** The client slug identifying which client owns these projects */
|
|
14
|
+
clientSlug: string;
|
|
15
|
+
/** Base URL of the projects API */
|
|
16
|
+
apiBase: string;
|
|
17
|
+
/** Base path used to build individual project detail URLs e.g. "/projects" */
|
|
18
|
+
basePath?: string;
|
|
19
|
+
/** Maximum number of projects to show. Defaults to 3 */
|
|
20
|
+
maxItems?: number;
|
|
21
|
+
/** Seconds to cache. Defaults to 60 */
|
|
22
|
+
revalidate?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function SimilarProjects({ filters, excludeSlug, clientSlug, apiBase, basePath, maxItems, revalidate, }: SimilarProjectsProps): Promise<import("react/jsx-runtime").JSX.Element | null>;
|
|
25
|
+
//# sourceMappingURL=SimilarProjects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SimilarProjects.d.ts","sourceRoot":"","sources":["../src/SimilarProjects.tsx"],"names":[],"mappings":"AAKA,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;CACpB;AA8CD,wBAAsB,eAAe,CAAC,EACpC,OAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,QAAY,EACZ,UAAe,GAChB,EAAE,oBAAoB,2DA6GtB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cache } from "react";
|
|
3
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
4
|
+
function parseMultiValue(raw) {
|
|
5
|
+
if (Array.isArray(raw))
|
|
6
|
+
return raw.map(String).map((s) => s.replace(/`/g, "").trim()).filter(Boolean);
|
|
7
|
+
if (typeof raw === "string")
|
|
8
|
+
return raw.replace(/`/g, "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
function dedupeByKey(arr) {
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
return arr.filter((f) => {
|
|
14
|
+
if (seen.has(f.key))
|
|
15
|
+
return false;
|
|
16
|
+
seen.add(f.key);
|
|
17
|
+
return true;
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
// ─── Data fetching ────────────────────────────────────────────────────────────
|
|
21
|
+
const fetchSimilarData = cache(async (apiBase, clientSlug, revalidate) => {
|
|
22
|
+
var _a, _b, _c;
|
|
23
|
+
const fetchOpts = revalidate > 0 ? { next: { revalidate } } : {};
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR`, fetchOpts);
|
|
26
|
+
const json = res.ok ? await res.json() : null;
|
|
27
|
+
const allProjects = (_a = json === null || json === void 0 ? void 0 : json.data) !== null && _a !== void 0 ? _a : [];
|
|
28
|
+
const schema = dedupeByKey((_c = (_b = json === null || json === void 0 ? void 0 : json.client) === null || _b === void 0 ? void 0 : _b.custom_fields_schema) !== null && _c !== void 0 ? _c : []);
|
|
29
|
+
return { allProjects, schema };
|
|
30
|
+
}
|
|
31
|
+
catch (_d) {
|
|
32
|
+
return { allProjects: [], schema: [] };
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
36
|
+
export async function SimilarProjects({ filters = {}, excludeSlug, clientSlug, apiBase, basePath = "/projects", maxItems = 3, revalidate = 60, }) {
|
|
37
|
+
const { allProjects, schema } = await fetchSimilarData(apiBase, clientSlug, revalidate);
|
|
38
|
+
const badgeField = schema.find((f) => f.display_position === "badge_overlay");
|
|
39
|
+
const locationField = schema.find((f) => f.type === "location");
|
|
40
|
+
const font = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
41
|
+
const filterEntries = Object.entries(filters);
|
|
42
|
+
const similar = allProjects
|
|
43
|
+
.filter((p) => {
|
|
44
|
+
// Exclude the specified slug if provided
|
|
45
|
+
if (excludeSlug && p.slug === excludeSlug)
|
|
46
|
+
return false;
|
|
47
|
+
// Every filter key/value must match the project's custom field values
|
|
48
|
+
return filterEntries.every(([key, value]) => {
|
|
49
|
+
const fieldValues = parseMultiValue(p.custom_field_values[key]);
|
|
50
|
+
return fieldValues.some((v) => v.toLowerCase() === value.toLowerCase());
|
|
51
|
+
});
|
|
52
|
+
})
|
|
53
|
+
.slice(0, maxItems);
|
|
54
|
+
if (similar.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
return (_jsxs("section", { style: { borderTop: "1px solid #e4e4e7", paddingTop: "3rem", maxWidth: "1280px", margin: "0 auto", padding: "3rem 1rem 2rem", boxSizing: "border-box", fontFamily: font }, children: [_jsx("style", { children: `
|
|
57
|
+
.chisel-similar-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; }
|
|
58
|
+
@media (min-width: 640px) { .chisel-similar-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
59
|
+
@media (min-width: 1024px) { .chisel-similar-grid { grid-template-columns: repeat(3, 1fr); } }
|
|
60
|
+
` }), _jsxs("div", { style: { display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: "2rem" }, children: [_jsxs("div", { children: [_jsx("p", { style: { fontSize: "11px", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: "oklch(0.78 0.16 85)", margin: "0 0 6px 0" }, children: "More Work" }), _jsx("h2", { style: { color: "#18181b", fontWeight: 700, fontSize: "28px", margin: 0, fontFamily: font }, children: "Similar Projects" })] }), _jsxs("a", { href: basePath, style: { color: "#18181b", fontWeight: 600, fontSize: "15px", textDecoration: "none", display: "flex", alignItems: "center", gap: "6px", fontFamily: font }, children: ["View All", _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M9 18l6-6-6-6" }) })] })] }), _jsx("div", { className: "chisel-similar-grid", children: similar.map((p) => {
|
|
61
|
+
var _a, _b, _c, _d, _e;
|
|
62
|
+
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;
|
|
63
|
+
const badge = badgeField
|
|
64
|
+
? ((_e = parseMultiValue(p.custom_field_values[badgeField.key])[0]) !== null && _e !== void 0 ? _e : null)
|
|
65
|
+
: null;
|
|
66
|
+
const loc = locationField
|
|
67
|
+
? p.custom_field_values[locationField.key]
|
|
68
|
+
: null;
|
|
69
|
+
const locStr = loc ? [loc.city, loc.state].filter(Boolean).join(", ") : null;
|
|
70
|
+
return (_jsxs("a", { href: `${basePath}/${p.slug}`, style: { textDecoration: "none", color: "inherit", display: "block", borderBottom: "1px solid #e4e4e7", fontFamily: font }, children: [_jsxs("div", { style: { position: "relative", width: "100%", height: "220px", overflow: "hidden", backgroundColor: "#f4f4f5", marginBottom: "1rem" }, children: [imgUrl && (_jsx("img", { src: imgUrl, alt: p.title, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } })), badge && (_jsx("span", { style: {
|
|
71
|
+
position: "absolute", top: "12px", left: "12px",
|
|
72
|
+
backgroundColor: "oklch(0.78 0.16 85)", color: "#fff",
|
|
73
|
+
fontSize: "10px", fontWeight: 700, textTransform: "uppercase",
|
|
74
|
+
letterSpacing: "0.1em", padding: "4px 10px",
|
|
75
|
+
}, children: badge }))] }), _jsxs("div", { style: { paddingBottom: "1.25rem" }, children: [_jsx("h3", { style: { color: "#18181b", fontWeight: 700, fontSize: "17px", lineHeight: 1.3, margin: "0 0 8px 0", fontFamily: font }, children: p.title }), locStr && (_jsxs("p", { style: { display: "flex", alignItems: "center", gap: "5px", color: "#71717a", fontSize: "14px", margin: 0 }, children: [_jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0 }, children: [_jsx("path", { d: "M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" }), _jsx("circle", { cx: "12", cy: "10", r: "3" })] }), locStr] }))] })] }, p.id));
|
|
76
|
+
}) })] }));
|
|
77
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,9 +2,13 @@ export { ProjectPortfolio } from "./ProjectPortfolio";
|
|
|
2
2
|
export type { ProjectPortfolioProps } from "./ProjectPortfolio";
|
|
3
3
|
export { ProjectDetail } from "./ProjectDetail";
|
|
4
4
|
export type { ProjectDetailProps } from "./ProjectDetail";
|
|
5
|
+
export { SimilarProjects } from "./SimilarProjects";
|
|
6
|
+
export type { SimilarProjectsProps } from "./SimilarProjects";
|
|
5
7
|
export { GalleryCarousel } from "./GalleryCarousel";
|
|
6
|
-
export { ProjectMenu } from "./ProjectMenu";
|
|
8
|
+
export { ProjectMenu, fetchProjectMenuData } from "./ProjectMenu";
|
|
7
9
|
export type { ProjectMenuProps } from "./ProjectMenu";
|
|
10
|
+
export { ProjectMenuClient } from "./ProjectMenuClient";
|
|
11
|
+
export type { ProjectMenuClientProps } from "./ProjectMenuClient";
|
|
8
12
|
export { ProjectCard } from "./ProjectCard";
|
|
9
13
|
export type { CardVariant } from "./ProjectCard";
|
|
10
14
|
export type { Project, CustomFieldSchema, CustomFieldValue, LocationValue, Media, } from "./types";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACjE,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAChD,YAAY,EACV,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,KAAK,GACN,MAAM,SAAS,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { ProjectPortfolio } from "./ProjectPortfolio";
|
|
2
2
|
export { ProjectDetail } from "./ProjectDetail";
|
|
3
|
+
export { SimilarProjects } from "./SimilarProjects";
|
|
3
4
|
export { GalleryCarousel } from "./GalleryCarousel";
|
|
4
|
-
export { ProjectMenu } from "./ProjectMenu";
|
|
5
|
+
export { ProjectMenu, fetchProjectMenuData } from "./ProjectMenu";
|
|
6
|
+
export { ProjectMenuClient } from "./ProjectMenuClient";
|
|
5
7
|
export { ProjectCard } from "./ProjectCard";
|
package/dist/types.d.ts
CHANGED
|
@@ -37,8 +37,6 @@ export interface Project {
|
|
|
37
37
|
image_url: string | null;
|
|
38
38
|
is_featured: boolean;
|
|
39
39
|
is_published?: boolean;
|
|
40
|
-
/** Completion or project year when provided by the API */
|
|
41
|
-
year?: number | string | null;
|
|
42
40
|
custom_field_values: Record<string, CustomFieldValue>;
|
|
43
41
|
created_at: string;
|
|
44
42
|
updated_at: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,cAAc,GAAG,UAAU,CAAA;IAChE,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,aAAa,EAAE,OAAO,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,eAAe,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAA;CACpE;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,UAAU,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,aAAa,GAAG,IAAI,CAAA;AAEhF,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,OAAO,CAAA;IACpB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,cAAc,GAAG,UAAU,CAAA;IAChE,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,aAAa,EAAE,OAAO,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,eAAe,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAA;CACpE;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,UAAU,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,aAAa,GAAG,IAAI,CAAA;AAEhF,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,OAAO,CAAA;IACpB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;IACrD,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,KAAK,EAAE,CAAA;CACf"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-portfolio",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Self-contained project portfolio components for Next.js App Router. Includes ProjectPortfolio, ProjectDetail, ProjectMenu (megamenu), and GalleryCarousel. Pass a clientSlug and apiBase — done.",
|
|
5
5
|
"keywords": ["nextjs", "react", "portfolio", "projects", "megamenu", "gallery"],
|
|
6
6
|
"license": "MIT",
|