Skip to content

Commit 0dc796a

Browse files
Miriadbuilder
andcommitted
feat: update schema and components from Cloudinary to Sanity native images
Schema changes: - coverImage: cloudinary.asset → image (with hotspot) - content[]: cloudinary.asset block → image block - videoCloudinary: cloudinary.asset → file - ogImage: cloudinary.asset → image - Remove cloudinarySchemaPlugin from sanity.config.ts Component rewrites: - cover-image.tsx: CldImage → Next.js Image + urlForImage() - block-image.tsx: CldImage → Next.js Image + urlForImage() - cover-video.tsx: CldVideoPlayer → native HTML video with Sanity CDN - cover-media.tsx: update type checks for Sanity refs - avatar.tsx: CldImage → Next.js Image + urlForImage() - portable-text.tsx: cloudinary.asset handler → image handler - youtube.tsx, youtube-short.tsx, pro-benefits.tsx, user-related.tsx: remove CloudinaryAsset type refs, use asset._ref checks New file: - sanity/lib/image.ts: urlForImage() helper using @sanity/image-url Co-authored-by: builder <builder@miriad.systems>
1 parent 41b30a3 commit 0dc796a

File tree

15 files changed

+195
-239
lines changed

15 files changed

+195
-239
lines changed

components/avatar.tsx

Lines changed: 44 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,55 @@
11
"use client";
22

3-
import { CldImage } from "next-cloudinary";
3+
import Image from "next/image";
44
import type { Author } from "@/sanity/types";
55
import Link from "next/link";
6-
import { stegaClean } from "@sanity/client/stega";
6+
import { urlForImage } from "@/sanity/lib/image";
77

88
interface Props {
9-
name?: string;
10-
href?: string;
11-
coverImage: Exclude<Author["coverImage"], undefined> | undefined;
12-
imgSize?: string;
13-
width?: number;
14-
height?: number;
9+
name?: string;
10+
href?: string;
11+
coverImage: Exclude<Author["coverImage"], undefined> | undefined;
12+
imgSize?: string;
13+
width?: number;
14+
height?: number;
1515
}
1616

1717
export default function Avatar({
18-
name,
19-
href,
20-
coverImage,
21-
imgSize,
22-
width,
23-
height,
18+
name,
19+
href,
20+
coverImage,
21+
imgSize,
22+
width,
23+
height,
2424
}: Props) {
25-
const source = stegaClean(coverImage);
26-
if (!href && source?.public_id) {
27-
return (
28-
<div className={`${imgSize ? imgSize : "w-12 h-12 mr-4"}`}>
29-
<CldImage
30-
className="w-full h-auto aspect-square rounded-md object-cover"
31-
width={width || 48}
32-
height={height || 48}
33-
alt={source?.context?.custom?.alt || ""}
34-
src={source.public_id}
35-
config={{
36-
url: {
37-
secureDistribution: "media.codingcat.dev",
38-
privateCdn: true,
39-
},
40-
}}
41-
/>
42-
</div>
43-
);
44-
}
45-
if (href && source?.public_id) {
46-
return (
47-
<Link className="flex items-center text-xl" href={href}>
48-
{source?.public_id && (
49-
<div className={`${imgSize ? imgSize : "w-12 h-12 mr-4"}`}>
50-
<CldImage
51-
className="w-full h-auto aspect-square rounded-md object-cover"
52-
width={width || 48}
53-
height={height || 48}
54-
alt={source?.context?.custom?.alt || ""}
55-
src={source.public_id}
56-
config={{
57-
url: {
58-
secureDistribution: "media.codingcat.dev",
59-
privateCdn: true,
60-
},
61-
}}
62-
/>
63-
</div>
64-
)}
65-
{name && (
66-
<div className="text-xl font-bold text-pretty hover:underline">
67-
{name}
68-
</div>
69-
)}
70-
</Link>
71-
);
72-
}
73-
return <></>;
25+
const imageUrl = coverImage?.asset?._ref
26+
? urlForImage(coverImage)?.width(width || 48).height(height || 48).url()
27+
: null;
28+
29+
if (!imageUrl) return <></>;
30+
31+
const imageElement = (
32+
<div className={`${imgSize ? imgSize : "w-12 h-12 mr-4"}`}>
33+
<Image
34+
className="w-full h-auto aspect-square rounded-md object-cover"
35+
width={width || 48}
36+
height={height || 48}
37+
alt={coverImage?.alt || ""}
38+
src={imageUrl}
39+
/>
40+
</div>
41+
);
42+
43+
if (!href) return imageElement;
44+
45+
return (
46+
<Link className="flex items-center text-xl" href={href}>
47+
{imageElement}
48+
{name && (
49+
<div className="text-xl font-bold text-pretty hover:underline">
50+
{name}
51+
</div>
52+
)}
53+
</Link>
54+
);
7455
}

components/block-image.tsx

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,35 @@
1-
import type { CloudinaryAsset } from "@/sanity/types";
2-
import CloudinaryImage from "@/components/cloudinary-image";
1+
import Image from "next/image";
2+
import { urlForImage } from "@/sanity/lib/image";
33

4-
import { getCldImageUrl } from "next-cloudinary";
5-
6-
interface CoverImageProps {
7-
image: CloudinaryAsset;
4+
interface BlockImageProps {
5+
image: any;
86
}
97

10-
export default async function BlockImage(props: CoverImageProps) {
11-
const { image: originalImage } = props;
8+
export default function BlockImage(props: BlockImageProps) {
9+
const { image } = props;
1210

13-
let image;
14-
if (originalImage?.public_id) {
15-
const imageUrl = getCldImageUrl({
16-
src: originalImage.public_id,
17-
width: 100,
18-
});
19-
const response = await fetch(imageUrl);
20-
const arrayBuffer = await response.arrayBuffer();
21-
const buffer = Buffer.from(arrayBuffer);
22-
const base64 = buffer.toString("base64");
23-
const dataUrl = `data:${response.type};base64,${base64}`;
11+
const imageUrl = image?.asset?._ref
12+
? urlForImage(image)?.width(1920).height(1080).url()
13+
: null;
2414

25-
image = (
26-
<CloudinaryImage
27-
className="w-full h-auto"
28-
width={1920}
29-
height={1080}
30-
sizes="100vw"
31-
alt={originalImage?.context?.custom?.alt || ""}
32-
src={originalImage?.public_id}
33-
placeholder="blur"
34-
blurDataURL={dataUrl}
35-
config={{
36-
url: {
37-
secureDistribution: "media.codingcat.dev",
38-
privateCdn: true,
39-
},
40-
}}
41-
/>
42-
);
43-
} else {
44-
image = <div className="bg-slate-50" style={{ paddingTop: "50%" }} />;
45-
}
15+
if (!imageUrl) {
16+
return (
17+
<div className="shadow-md transition-shadow duration-200 group-hover:shadow-lg sm:mx-0">
18+
<div className="bg-slate-50" style={{ paddingTop: "50%" }} />
19+
</div>
20+
);
21+
}
4622

47-
return (
48-
<div className="shadow-md transition-shadow duration-200 group-hover:shadow-lg sm:mx-0">
49-
{image}
50-
</div>
51-
);
23+
return (
24+
<div className="shadow-md transition-shadow duration-200 group-hover:shadow-lg sm:mx-0">
25+
<Image
26+
className="w-full h-auto"
27+
width={1920}
28+
height={1080}
29+
sizes="100vw"
30+
alt={image?.alt || ""}
31+
src={imageUrl}
32+
/>
33+
</div>
34+
);
5235
}

components/cover-image.tsx

Lines changed: 34 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,42 @@
1-
import type { CloudinaryAsset } from "@/sanity/types";
2-
import CloudinaryImage from "@/components/cloudinary-image";
3-
import { getCldImageUrl } from "next-cloudinary";
4-
import { stegaClean } from "@sanity/client/stega";
1+
import Image from "next/image";
2+
import { urlForImage } from "@/sanity/lib/image";
53

64
interface CoverImageProps {
7-
image: CloudinaryAsset | null | undefined;
8-
priority?: boolean;
9-
className?: string;
10-
width?: number;
11-
height?: number;
12-
quality?: number | `${number}`;
5+
image: any;
6+
priority?: boolean;
7+
className?: string;
8+
width?: number;
9+
height?: number;
10+
quality?: number;
1311
}
1412

15-
export default async function CoverImage(props: CoverImageProps) {
16-
const {
17-
image: originalImage,
18-
priority,
19-
className,
20-
width,
21-
height,
22-
quality,
23-
} = props;
13+
export default function CoverImage(props: CoverImageProps) {
14+
const { image, priority, className, width, height, quality } = props;
2415

25-
const source = stegaClean(originalImage);
16+
const imageUrl = image?.asset?._ref
17+
? urlForImage(image)?.width(width || 1920).height(height || 1080).quality(quality || 80).url()
18+
: null;
2619

27-
const getImageUrl = async (src: string) => {
28-
const imageUrl = getCldImageUrl({
29-
src,
30-
width: 100,
31-
});
32-
const response = await fetch(imageUrl);
33-
const arrayBuffer = await response.arrayBuffer();
34-
const buffer = Buffer.from(arrayBuffer);
35-
const base64 = buffer.toString("base64");
36-
return `data:${response.type};base64,${base64}`;
37-
};
20+
if (!imageUrl) {
21+
return (
22+
<div className="transition-shadow duration-200 shadow-md group-hover:shadow-lg sm:mx-0">
23+
<div className="bg-background" style={{ paddingTop: "50%" }} />
24+
</div>
25+
);
26+
}
3827

39-
let image: JSX.Element | undefined;
40-
if (source?.public_id) {
41-
image = (
42-
<CloudinaryImage
43-
className={className || "w-full h-auto aspect-video rounded-md"}
44-
width={width || 1920}
45-
height={height || 1080}
46-
priority={priority}
47-
quality={quality || "auto"}
48-
sizes="(max-width: 768px) 100vw,(max-width: 1200px) 50vw, 33vw"
49-
alt={source?.context?.custom?.alt || ""}
50-
src={source?.public_id}
51-
placeholder="blur"
52-
blurDataURL={await getImageUrl(source.public_id)}
53-
/>
54-
);
55-
} else {
56-
image = <div className="bg-background" style={{ paddingTop: "50%" }} />;
57-
}
58-
59-
return (
60-
<div className="transition-shadow duration-200 shadow-md group-hover:shadow-lg sm:mx-0">
61-
{image}
62-
</div>
63-
);
28+
return (
29+
<div className="transition-shadow duration-200 shadow-md group-hover:shadow-lg sm:mx-0">
30+
<Image
31+
className={className || "w-full h-auto aspect-video rounded-md"}
32+
width={width || 1920}
33+
height={height || 1080}
34+
priority={priority}
35+
quality={quality || 80}
36+
sizes="(max-width: 768px) 100vw,(max-width: 1200px) 50vw, 33vw"
37+
alt={image?.alt || ""}
38+
src={imageUrl}
39+
/>
40+
</div>
41+
);
6442
}

components/cover-media.tsx

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,36 @@
1-
import type { CloudinaryAsset } from "@/sanity/types";
21
import dynamic from "next/dynamic";
32

43
const YouTube = dynamic(() =>
5-
import("@/components/youtube").then((mod) => mod.YouTube),
4+
import("@/components/youtube").then((mod) => mod.YouTube),
65
);
76
const CoverImage = dynamic(() => import("@/components/cover-image"));
87
const CoverVideo = dynamic(() => import("@/components/cover-video"));
98

109
export interface CoverMediaProps {
11-
cloudinaryImage: CloudinaryAsset | null | undefined;
12-
cloudinaryVideo: CloudinaryAsset | null | undefined;
13-
youtube: string | null | undefined;
14-
className?: string;
10+
cloudinaryImage: any;
11+
cloudinaryVideo: any;
12+
youtube: string | null | undefined;
13+
className?: string;
1514
}
1615

1716
export default function CoverMedia(props: CoverMediaProps) {
18-
const { cloudinaryImage, cloudinaryVideo, youtube, className } = props;
17+
const { cloudinaryImage, cloudinaryVideo, youtube, className } = props;
1918

20-
if (cloudinaryVideo?.public_id) {
21-
return (
22-
<CoverVideo cloudinaryVideo={cloudinaryVideo} className={className} />
23-
);
24-
}
25-
if (youtube) {
26-
return (
27-
<YouTube
28-
youtube={youtube}
29-
image={cloudinaryImage}
30-
className={className}
31-
/>
32-
);
33-
}
34-
return (
35-
<CoverImage image={cloudinaryImage} priority={true} className={className} />
36-
);
19+
if (cloudinaryVideo?.asset?._ref) {
20+
return (
21+
<CoverVideo cloudinaryVideo={cloudinaryVideo} className={className} />
22+
);
23+
}
24+
if (youtube) {
25+
return (
26+
<YouTube
27+
youtube={youtube}
28+
image={cloudinaryImage}
29+
className={className}
30+
/>
31+
);
32+
}
33+
return (
34+
<CoverImage image={cloudinaryImage} priority={true} className={className} />
35+
);
3736
}

0 commit comments

Comments
 (0)