Skip to content

Commit b8fec96

Browse files
authored
✨ feat[sanity]: improved image loading with proper sizing and formatting
✨ feat[sanity]: improved image loading with proper sizing and formatting
2 parents e390f4c + 4fae1da commit b8fec96

File tree

5 files changed

+205
-68
lines changed

5 files changed

+205
-68
lines changed

src/__tests__/Prosjekter/ProsjektCard.test.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ interface ButtonProps {
1212
children: React.ReactNode;
1313
}
1414

15-
// Mock the Button component
1615
jest.mock("@/components/UI/Button.component", () => {
1716
return function MockButton({ href, children }: ButtonProps) {
1817
return <a href={href}>{children}</a>;
1918
};
2019
});
2120

22-
// Mock the urlFor function
23-
jest.mock("@/lib/sanity/helpers", () => ({
21+
jest.mock("@/lib/sanity/client", () => ({
2422
urlFor: jest.fn().mockReturnValue({
25-
url: jest.fn().mockReturnValue("/test-image.jpg"),
23+
width: jest.fn().mockReturnThis(),
24+
height: jest.fn().mockReturnThis(),
25+
fit: jest.fn().mockReturnThis(),
26+
quality: jest.fn().mockReturnThis(),
27+
auto: jest.fn().mockReturnThis(),
28+
url: jest.fn().mockReturnValue("/test-image.jpg"), // The URL the tests expect
2629
}),
2730
}));
2831

@@ -36,7 +39,14 @@ const mockProjectProps: Project = {
3639
name: "Test Project",
3740
description: "This is a test project",
3841
subdescription: "This is a subdescription",
39-
projectimage: "/test-image.jpg",
42+
// Use a valid Sanity image object structure
43+
projectimage: {
44+
_type: "image",
45+
asset: {
46+
_ref: "image-abcde12345-100x100-jpg",
47+
_type: "reference",
48+
},
49+
},
4050
urlwww: [{ url: "https://example.com", _key: "1", _type: "link" }],
4151
urlgithub: [{ url: "https://github.com/example", _key: "1", _type: "link" }],
4252
};
@@ -58,7 +68,7 @@ describe("ProsjektCard", () => {
5868
expect(screen.getByText(expectedContent.name)).toBeInTheDocument();
5969
expect(screen.getByText(expectedContent.description)).toBeInTheDocument();
6070
expect(
61-
screen.getByText(expectedContent.subdescription),
71+
screen.getByText(expectedContent.subdescription)
6272
).toBeInTheDocument();
6373
});
6474

@@ -100,12 +110,12 @@ describe("ProsjektCard", () => {
100110
expect(visitButton).toBeInTheDocument();
101111
expect(visitButton.closest("a")).toHaveAttribute(
102112
"href",
103-
expectedLinks.visit.href,
113+
expectedLinks.visit.href
104114
);
105115
expect(githubButton).toBeInTheDocument();
106116
expect(githubButton.closest("a")).toHaveAttribute(
107117
"href",
108-
expectedLinks.github.href,
118+
expectedLinks.github.href
109119
);
110120
});
111121
});

src/components/Prosjekter/ProsjektCard.component.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React from "react";
44
import Button from "@/components/UI/Button.component";
55
import BounceInScroll from "@/components/Animations/BounceInScroll.component";
66

7-
import { urlFor } from "@/lib/sanity/helpers";
7+
import { urlFor } from "@/lib/sanity/client";
88

99
import type { Project } from "@/types/sanity.types";
1010

@@ -38,21 +38,25 @@ const ProsjektCard: React.FC<Project> = ({
3838
data-testid="projectcard"
3939
>
4040
<BounceInScroll viewAmount={0.3} instant={featured}>
41-
<div className="relative w-full h-48 md:h-60">
41+
<div className="relative w-full md:h-60">
4242
<div className="w-full h-full p-5 md:pb-[20px] relative overflow-hidden flex justify-center md:h-[340px]">
4343
{projectimage && (
4444
<img
4545
className="transition-all duration-300 ease-in-out hover:-translate-y-1 hover:shadow-[0_2px_20px_rgba(60,255,60,0.35)]"
46-
width="600"
47-
height="340"
48-
src={urlFor(projectimage).url() as string}
46+
width={600}
47+
src={urlFor(projectimage)
48+
.width(600)
49+
.fit("max")
50+
.quality(100)
51+
.auto("format")
52+
.url()}
4953
alt={name}
5054
/>
5155
)}
5256
</div>
5357
</div>
54-
<div className="md:mt-16 p-2 flex flex-col justify-between min-h-[250px] xl:min-h-[275px]">
55-
<h1 className="xl:mt-4 text-xl text-center font-bold py-2 text-slate-100">
58+
<div className="md:mt-[4.5rem] md:p-2 flex flex-col justify-between min-h-[250px] xl:min-h-[275px]">
59+
<h1 className="lg:mt-4 text-xl text-center font-bold py-2 text-slate-100">
5660
{name}
5761
</h1>
5862
<h2 className="text-md px-4 text-gray-300">{description}</h2>

src/lib/sanity/client.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import imageUrlBuilder from "@sanity/image-url";
12
import { createClient } from "next-sanity";
3+
import type { Project } from "@/types/sanity.types";
24

35
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || "41s7iutf";
46
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET || "production";
@@ -10,3 +12,9 @@ export const client = createClient({
1012
apiVersion,
1113
useCdn: true,
1214
});
15+
16+
const builder = imageUrlBuilder(client);
17+
18+
export function urlFor(source: NonNullable<Project["projectimage"]>) {
19+
return builder.image(source);
20+
}

src/lib/sanity/queries.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const projectsQuery = groq`
1818
...,
1919
_key,
2020
},
21-
"projectimage": projectimage.asset->url,
21+
projectimage,
2222
featured,
2323
featureOrder
2424
}

src/types/sanity.types.ts

Lines changed: 167 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ export type Geopoint = {
6868
alt?: number;
6969
};
7070

71+
export type Slug = {
72+
_type: "slug";
73+
current?: string;
74+
source?: string;
75+
};
76+
7177
export type Herocontent = {
7278
_type: "herocontent";
7379
text?: string;
@@ -106,23 +112,50 @@ export type Link = {
106112

107113
export type Navigation = {
108114
_id: string;
109-
_type: "Navigation";
115+
_type: "navigation";
110116
_createdAt: string;
111117
_updatedAt: string;
112118
_rev: string;
113119
title?: string;
114-
slug?: Slug;
115-
navigation?: Array<
116-
{
117-
_key: string;
118-
} & Link
119-
>;
120+
links?: Array<{
121+
title?: string;
122+
name?: string;
123+
hash?: string;
124+
href?: string;
125+
externalLink?: boolean;
126+
icon?: "RiHome4Line" | "RiProjectorLine" | "RiFileList3Line" | "RiGithubLine" | "RiMailLine";
127+
_key: string;
128+
}>;
120129
};
121130

122-
export type Slug = {
123-
_type: "slug";
124-
current?: string;
125-
source?: string;
131+
export type Cv = {
132+
_id: string;
133+
_type: "cv";
134+
_createdAt: string;
135+
_updatedAt: string;
136+
_rev: string;
137+
keyQualifications?: Array<string>;
138+
experience?: Array<{
139+
period?: string;
140+
company?: string;
141+
role?: string;
142+
description?: string;
143+
_key: string;
144+
}>;
145+
education?: Array<{
146+
period?: string;
147+
institution?: string;
148+
degree?: string;
149+
description?: string;
150+
_key: string;
151+
}>;
152+
volunteerWork?: Array<{
153+
period?: string;
154+
organization?: string;
155+
role?: string;
156+
description?: string;
157+
_key: string;
158+
}>;
126159
};
127160

128161
export type Page = {
@@ -133,16 +166,12 @@ export type Page = {
133166
_rev: string;
134167
title?: string;
135168
header?: string;
136-
hero?: Array<
137-
{
138-
_key: string;
139-
} & Herocontent
140-
>;
141-
content?: Array<
142-
{
143-
_key: string;
144-
} & Pagecontent
145-
>;
169+
hero?: Array<{
170+
_key: string;
171+
} & Herocontent>;
172+
content?: Array<{
173+
_key: string;
174+
} & Pagecontent>;
146175
};
147176

148177
export type Project = {
@@ -152,7 +181,7 @@ export type Project = {
152181
_updatedAt: string;
153182
_rev: string;
154183
id?: number;
155-
name: string;
184+
name?: string;
156185
description?: string;
157186
subdescription?: string;
158187
projectcategory?: {
@@ -161,17 +190,23 @@ export type Project = {
161190
_weak?: boolean;
162191
[internalGroqTypeReferenceTo]?: "category";
163192
};
164-
urlwww?: Array<
165-
{
166-
_key: string;
167-
} & Link
168-
>;
169-
urlgithub?: Array<
170-
{
171-
_key: string;
172-
} & Link
173-
>;
174-
projectimage: string;
193+
urlwww?: Array<{
194+
_key: string;
195+
} & Link>;
196+
urlgithub?: Array<{
197+
_key: string;
198+
} & Link>;
199+
projectimage?: {
200+
asset?: {
201+
_ref: string;
202+
_type: "reference";
203+
_weak?: boolean;
204+
[internalGroqTypeReferenceTo]?: "sanity.imageAsset";
205+
};
206+
hotspot?: SanityImageHotspot;
207+
crop?: SanityImageCrop;
208+
_type: "image";
209+
};
175210
featured?: boolean;
176211
featureOrder?: number;
177212
};
@@ -243,23 +278,103 @@ export type Category = {
243278
name?: string;
244279
};
245280

246-
export type AllSanitySchemaTypes =
247-
| SanityImagePaletteSwatch
248-
| SanityImagePalette
249-
| SanityImageDimensions
250-
| SanityFileAsset
251-
| Geopoint
252-
| Herocontent
253-
| Pagecontent
254-
| Link
255-
| Navigation
256-
| Slug
257-
| Page
258-
| Project
259-
| SanityImageCrop
260-
| SanityImageHotspot
261-
| SanityImageAsset
262-
| SanityAssetSourceData
263-
| SanityImageMetadata
264-
| Category;
281+
export type AllSanitySchemaTypes = SanityImagePaletteSwatch | SanityImagePalette | SanityImageDimensions | SanityFileAsset | Geopoint | Slug | Herocontent | Pagecontent | Link | Navigation | Cv | Page | Project | SanityImageCrop | SanityImageHotspot | SanityImageAsset | SanityAssetSourceData | SanityImageMetadata | Category;
265282
export declare const internalGroqTypeReferenceTo: unique symbol;
283+
// Source: ./src/lib/sanity/queries.ts
284+
// Variable: projectsQuery
285+
// Query: *[_type == "project"] | order(featureOrder asc) { id, name, description, subdescription, projectcategory->{ _id, title }, urlwww[]{ ..., _key, }, urlgithub[]{ ..., _key, }, projectimage, featured, featureOrder }
286+
export type ProjectsQueryResult = Array<{
287+
id: number | null;
288+
name: string | null;
289+
description: string | null;
290+
subdescription: string | null;
291+
projectcategory: {
292+
_id: string;
293+
title: null;
294+
} | null;
295+
urlwww: Array<{
296+
_key: string;
297+
_type: "link";
298+
title?: string;
299+
url?: string;
300+
external?: boolean;
301+
}> | null;
302+
urlgithub: Array<{
303+
_key: string;
304+
_type: "link";
305+
title?: string;
306+
url?: string;
307+
external?: boolean;
308+
}> | null;
309+
projectimage: {
310+
asset?: {
311+
_ref: string;
312+
_type: "reference";
313+
_weak?: boolean;
314+
[internalGroqTypeReferenceTo]?: "sanity.imageAsset";
315+
};
316+
hotspot?: SanityImageHotspot;
317+
crop?: SanityImageCrop;
318+
_type: "image";
319+
} | null;
320+
featured: boolean | null;
321+
featureOrder: number | null;
322+
}>;
323+
// Variable: cvQuery
324+
// Query: *[_type == "cv"][0] { keyQualifications, experience[] { period, company, role, description }, education[] { period, institution, degree, description }, volunteerWork[] { period, organization, role, description } }
325+
export type CvQueryResult = {
326+
keyQualifications: Array<string> | null;
327+
experience: Array<{
328+
period: string | null;
329+
company: string | null;
330+
role: string | null;
331+
description: string | null;
332+
}> | null;
333+
education: Array<{
334+
period: string | null;
335+
institution: string | null;
336+
degree: string | null;
337+
description: string | null;
338+
}> | null;
339+
volunteerWork: Array<{
340+
period: string | null;
341+
organization: string | null;
342+
role: string | null;
343+
description: string | null;
344+
}> | null;
345+
} | null;
346+
// Variable: pageContentQuery
347+
// Query: *[_type == 'page' && title match 'Hjem'][0]{ "id": _id, title, hero, content }
348+
export type PageContentQueryResult = {
349+
id: string;
350+
title: string | null;
351+
hero: Array<{
352+
_key: string;
353+
} & Herocontent> | null;
354+
content: Array<{
355+
_key: string;
356+
} & Pagecontent> | null;
357+
} | null;
358+
// Variable: navigationQuery
359+
// Query: *[_type == "navigation"][0] { title, links[] { title, name, hash, href, externalLink } }
360+
export type NavigationQueryResult = {
361+
title: string | null;
362+
links: Array<{
363+
title: string | null;
364+
name: string | null;
365+
hash: string | null;
366+
href: string | null;
367+
externalLink: boolean | null;
368+
}> | null;
369+
} | null;
370+
371+
// Query TypeMap
372+
import "@sanity/client";
373+
declare module "@sanity/client" {
374+
interface SanityQueries {
375+
"\n *[_type == \"project\"] | order(featureOrder asc) {\n id,\n name,\n description,\n subdescription,\n projectcategory->{\n _id,\n title\n },\n urlwww[]{\n ...,\n _key,\n },\n urlgithub[]{\n ...,\n _key,\n },\n projectimage,\n featured,\n featureOrder\n }\n": ProjectsQueryResult;
376+
"\n *[_type == \"cv\"][0] {\n keyQualifications,\n experience[] {\n period,\n company,\n role,\n description\n },\n education[] {\n period,\n institution,\n degree,\n description\n },\n volunteerWork[] {\n period,\n organization,\n role,\n description\n }\n }\n": CvQueryResult;
377+
"\n *[_type == 'page' && title match 'Hjem'][0]{\n \"id\": _id, \n title, \n hero, \n content\n }\n": PageContentQueryResult;
378+
"\n *[_type == \"navigation\"][0] {\n title,\n links[] {\n title,\n name,\n hash,\n href,\n externalLink\n }\n }\n": NavigationQueryResult;
379+
}
380+
}

0 commit comments

Comments
 (0)