Skip to content

Instantly share code, notes, and snippets.

@mikecebul
Created August 5, 2024 20:38
Show Gist options
  • Save mikecebul/f7c5c4328ada36168b7dcf241cda57ee to your computer and use it in GitHub Desktop.
Save mikecebul/f7c5c4328ada36168b7dcf241cda57ee to your computer and use it in GitHub Desktop.
Payload array field component for Link Cards. fetchRandomImage() hook to fetch an image from Unsplash to speed up editing. AddHTTPS hook to fix to add 'https://' to the url. And RowLabel() to use title as row label.
import { FieldHook, FieldHookArgs } from "payload";
export const addHTTPS: FieldHook = ({ operation, value }) => {
if (operation === "create" || operation === "update") {
if (
typeof value === "string" &&
!value.startsWith("https://") &&
!value.startsWith("http://")
)
return `https://${value}`;
}
return value;
};
import { FieldHook } from "payload";
import fs from "fs";
import path, { extname } from "path";
import { fileTypeFromBuffer } from "file-type";
import { generateSlug } from "@/shared/utils";
async function storeFileLocally(
url: string,
fileName: string
): Promise<string> {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
const data = Buffer.from(buffer);
const fileExtension = extname(url).slice(1);
const filePath = `/tmp/${fileName}.${fileExtension}`;
fs.writeFileSync(filePath, data);
return filePath;
}
export const fetchRandomImage =
(query: string[]): FieldHook =>
async ({ value, req, siblingData }) => {
if (!value) {
const url = `https://api.unsplash.com/photos/random?client_id=${
process.env.UNSPLASH_ACCESS_KEY
}&query=${query.join(",")}`;
try {
const response = await fetch(url);
const data = await response.json();
const imageUrl = data.urls.regular;
const altDescription = data.alt_description;
const fileName = data.slug;
const localFilePath = await storeFileLocally(imageUrl, fileName);
// Read the file buffer
const fileBuffer = fs.readFileSync(localFilePath);
const fileType = await fileTypeFromBuffer(fileBuffer);
const mimeType = fileType?.mime ?? "image/jpeg";
// Create the image file object
const imageFile = {
data: fileBuffer,
mimetype: mimeType,
name: generateSlug(siblingData.title),
size: fileBuffer.length,
};
// Upload the image to the media collection
const image = await req.payload.create({
collection: "media",
data: {
alt: altDescription,
},
file: imageFile,
req,
});
// Clean up the temporary file
fs.unlinkSync(localFilePath);
return image.id;
} catch (error) {
console.error("Error uploading image:", error);
}
}
return value;
};
import { Field } from "payload";
import { RowLabel } from "./RowLabel";
import { addHTTPS } from "./addHTTPS";
import { fetchRandomImage } from "./fetchRandomImage";
export const linkCards: Field = {
name: "links",
type: "array",
interfaceName: "LinkCards",
admin: {
components: {
RowLabel,
},
},
fields: [
{
name: "title",
type: "text",
required: true,
},
{
name: "description",
type: "textarea",
required: true,
},
{
name: "imageUploadOption",
label: "Image Upload Option",
type: "radio",
defaultValue: "generate",
admin: {
condition: (_, siblingData) => !siblingData.image,
},
options: [
{
label: "Generate image",
value: "generate",
},
{
label: "Upload image",
value: "manual",
},
],
},
{
name: "image",
type: "upload",
relationTo: "media",
hooks: {
beforeChange: [fetchRandomImage(["child", "adhd"])],
},
admin: {
condition: (_, siblingData) => {
return (
!!siblingData.image ||
(siblingData.imageUploadOption &&
siblingData.imageUploadOption === "manual")
);
},
},
},
{
name: "href",
label: "Url",
type: "text",
required: true,
hooks: {
beforeValidate: [addHTTPS],
},
},
],
};
"use client";
import type { RowLabelComponent } from "payload";
import { useRowLabel } from "@payloadcms/ui";
export const RowLabel: RowLabelComponent = () => {
const { data, rowNumber } = useRowLabel<{ title: string }>();
return (
<div className="text-orange-400 capitalize">
{`${rowNumber} - ${data.title || "Untitled"}`}
</div>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment