Closed labi1240 closed 6 months ago
030285bb5e
)[!TIP] I can email you next time I complete a pull request if you set up your email here!
I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.
src/app/api/users/upload/route.ts
✓ https://github.com/labi1240/project101/commit/6f35c4e6b31a13bda5420e8d17958de605821057 Edit
Create src/app/api/users/upload/route.ts with contents:
• Create a new file `src/app/api/users/upload/route.ts` for the Cloudinary image upload API route.
• Import necessary modules from 'next', 'cloudinary', and any utility functions needed for handling requests.
• Configure Cloudinary using environment variables for secure API access.
• Implement a POST method to handle image uploads. The method should: - Parse the incoming request to retrieve the image file. - Use Cloudinary's SDK to upload the image file. - Respond with the URL of the uploaded image or an error message if the upload fails.
• Ensure to handle errors gracefully and return appropriate HTTP status codes.
src/app/api/users/upload/route.ts
✓ Edit
Check src/app/api/users/upload/route.ts with contents:
Ran GitHub Actions for 6f35c4e6b31a13bda5420e8d17958de605821057:
src/app/api/users/signup/route.ts
✓ https://github.com/labi1240/project101/commit/0a51212681cf110ae64a2249c0f07bb3ae3eb0c2 Edit
Modify src/app/api/users/signup/route.ts with contents:
• Modify the `src/app/api/users/signup/route.ts` to include logic for handling an image URL.
• In the `POST` method, after validating the user's data, check if an image URL is provided in the request body.
• If an image URL is present, include it in the newUser object to be saved in the database. This might involve updating the User model to include an image URL field if not already present.
• Ensure that the image URL is optional, so users can still sign up without uploading an image.
--- +++ @@ -12,7 +12,7 @@ export async function POST(request: NextRequest) { try { const reqBody = await request.json() - const { username, email, password } = reqBody + const { username, email, password, imageUrl } = reqBody console.log(reqBody); @@ -30,7 +30,8 @@ const newUser = new User({ username, email, - password: hashedPassword + password: hashedPassword, + imageUrl: imageUrl || '' // imageUrl is optional }) const savedUser = await newUser.save()
src/app/api/users/signup/route.ts
✓ Edit
Check src/app/api/users/signup/route.ts with contents:
Ran GitHub Actions for 0a51212681cf110ae64a2249c0f07bb3ae3eb0c2:
src/app/api/users/me/route.ts
✓ https://github.com/labi1240/project101/commit/377e8878ecc0ca4e917bab1d19eab4671dd10fbc Edit
Modify src/app/api/users/me/route.ts with contents:
• Optionally, prepare for future integration by ensuring the user's profile information, including the image URL, is returned in the `GET` method of `src/app/api/users/me/route.ts`.
• This involves selecting the image URL field along with other user information and including it in the response.
--- +++ @@ -9,7 +9,7 @@ try { const userId = await getDataFromToken(request); - const user = await User.findOne({ _id: userId }).select("-password"); + const user = await User.findOne({ _id: userId }).select("-password imageUrl"); return NextResponse.json({ mesaaage: "User found", data: user
src/app/api/users/me/route.ts
✓ Edit
Check src/app/api/users/me/route.ts with contents:
Ran GitHub Actions for 377e8878ecc0ca4e917bab1d19eab4671dd10fbc:
I have finished reviewing the code for completeness. I did not find errors for sweep/please_setup_the_cloudinary_for_uploadin_100a7
.
💡 To recreate the pull request edit the issue title or description. Something wrong? Let us know.
This is an automated message generated by Sweep AI.
None
)[!TIP] I can email you next time I complete a pull request if you set up your email here!
I found the following snippets in your repository. I will now analyze these snippets and come up with a plan.
src/app/api/users/signup/route.ts
✓ https://github.com/labi1240/project101/commit/3847b68b7e741fb25f968bf99bf6944b4af8afe2 Edit
Modify src/app/api/users/signup/route.ts with contents:
• Import Cloudinary SDK at the top of the file: `import cloudinary from 'cloudinary'`.
• Configure Cloudinary in the file using environment variables for secure API access: `cloudinary.v2.config({ cloud_name: process.env.CLOUDINARY_CLOUD_NAME, api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET });`.
• Modify the `POST` function to include image upload logic. After validating the user data but before creating the new user in the database, check if an image file is included in the request. If so, use Cloudinary's upload method (`cloudinary.v2.uploader.upload`) to upload the image. Store the returned URL in a variable.
• Add the image URL to the `newUser` object before saving it to the database, e.g., `newUser.profileImageUrl = uploadedImageUrl;`.
• Ensure environment variables for Cloudinary (`CLOUDINARY_CLOUD_NAME`, `CLOUDINARY_API_KEY`, `CLOUDINARY_API_SECRET`) are added to your `.env` file.
--- +++ @@ -1,6 +1,9 @@ import { connect } from "@/database/dbConfig/dbConfig"; // Adjusted if dbConfig is indeed in the database folder import User from "@/database/models/userModel"; // Adjusted to use the absolute path alias import { NextRequest, NextResponse } from "next/server"; +import cloudinary from 'cloudinary'; + +cloudinary.v2.config({ cloud_name: process.env.CLOUDINARY_CLOUD_NAME, api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET }); import bcryptjs from "bcryptjs"; import { sendEmail } from "@/database/helpers/mailer"; @@ -33,7 +36,17 @@ password: hashedPassword }) - const savedUser = await newUser.save() + // Assuming 'request' contains the image file in a field named 'image' +const imageFile = request.files?.image; +let uploadedImageUrl = ''; +if (imageFile) { + const result = await cloudinary.v2.uploader.upload(imageFile.path); + uploadedImageUrl = result.url; +} + +newUser.profileImageUrl = uploadedImageUrl; + +const savedUser = await newUser.save() console.log(savedUser); //send verification email
src/app/api/users/signup/route.ts
✓ Edit
Check src/app/api/users/signup/route.ts with contents:
Ran GitHub Actions for 3847b68b7e741fb25f968bf99bf6944b4af8afe2:
src/app/api/users/updateProfile/route.ts
✓ https://github.com/labi1240/project101/commit/00969edfa028cd6044e1961b193019a174bf7874 Edit
Create src/app/api/users/updateProfile/route.ts with contents:
• Create a new route handler for updating user profiles, including image uploads.
• Import necessary modules at the top of the file: `import cloudinary from 'cloudinary'`, `import User from "@/database/models/userModel";`, and any other necessary imports for handling requests and responses.
• Configure Cloudinary as described in the modification of the signup route.
• Implement a `POST` or `PATCH` function to handle profile updates. This function should extract user data and the image file from the request, upload the image to Cloudinary if present, update the user document in the database with any provided data and the new image URL, and return an appropriate response.
• Ensure this route is properly exported and imported into the main API routing setup.
src/app/api/users/updateProfile/route.ts
✓ Edit
Check src/app/api/users/updateProfile/route.ts with contents:
Ran GitHub Actions for 00969edfa028cd6044e1961b193019a174bf7874:
package.json
! No changes made Edit
Modify package.json with contents:
• Add the Cloudinary Node.js SDK to the dependencies: Run `npm install cloudinary` or add `"cloudinary": "latest"` to the `dependencies` section manually.
package.json
✗ Edit
Check package.json with contents:
I have finished reviewing the code for completeness. I did not find errors for sweep/please_setup_the_cloudinary_for_uploadin
.
💡 To recreate the pull request edit the issue title or description. Something wrong? Let us know.
This is an automated message generated by Sweep AI.
for example i have added these components which was shared their boildercode for nextjs app router components. I have setup the route.ts files for login, signup, me, logout, verifyemail in api folder you can check by route.ts file extension. I need your help to setup a update/route.ts file for me similar to my setup route.ts files which are provide me crud operations funcnality using mongoose. I want to add the image upload feature when user signup and directed to profile page there or i have 1 more account page (Account-page) you can setup the cloudinary over there too.
import React, { useState, useCallback, forwardRef, SyntheticEvent } from 'react'; import Image, { ImageProps } from 'next/image'; import { getTransformations } from '@cloudinary-util/util'; import { transformationPlugins } from '@cloudinary-util/url-loader'; import type { ImageOptions, ConfigOptions } from '@cloudinary-util/url-loader';
import { pollForProcessingImage } from '../../lib/cloudinary'; import { getCldImageUrl } from '../../helpers/getCldImageUrl';
import { cloudinaryLoader } from '../../loaders/cloudinary-loader';
export type CldImageProps = Omit<ImageProps, 'src' | 'quality'> & ImageOptions & { config?: ConfigOptions; preserveTransformations?: boolean; src: string; unoptimized?: boolean; };
const CldImage = forwardRef<HTMLImageElement, CldImageProps>(function CldImage(props, ref) { let hasThrownError = false;
const CLD_OPTIONS = [ 'deliveryType', 'preserveTransformations', 'strictTransformations', 'assetType', ];
transformationPlugins.forEach(({ props }: { props: Record<string, unknown> }) => { const pluginProps = Object.keys(props); pluginProps.forEach(prop => { if ( CLD_OPTIONS.includes(prop) ) { throw new Error(
Option ${prop} already exists!
); } CLD_OPTIONS.push(prop); }); });// Construct the base Image component props by filtering out Cloudinary-specific props
const imageProps: ImageProps = { alt: props.alt, src: props.src, };
(Object.keys(props) as Array)
.filter(key => typeof key === 'string' && !CLD_OPTIONS.includes(key))
.forEach(key => imageProps[key as keyof ImageProps] = props[key]);
const defaultImgKey = (Object.keys(imageProps) as Array).map(key =>
${key}:${imageProps[key]}
).join(';'); const [imgKey, setImgKey] = useState(defaultImgKey);// Construct Cloudinary-specific props by looking for values for any of the supported prop keys
type CldOptions = Omit<ImageOptions, 'src'>;
const cldOptions: CldOptions = {};
CLD_OPTIONS.forEach((key) => { const prop = props[key as keyof ImageOptions]; if ( prop ) { cldOptions[key as keyof CldOptions] = prop || undefined; } });
// Try to preserve the original transformations from the Cloudinary URL passed in // to the component. This only works if the URL has a version number on it and otherwise // will fail to load
if (props.preserveTransformations) { try { const transformations = getTransformations(props.src).map(t => t.join(',')); cldOptions.rawTransformations = [...transformations.flat(), ...(props.rawTransformations || [])]; } catch(e) { console.warn(
Failed to preserve transformations: ${(e as Error).message}
) } }// The unoptimized flag is intended to remove all optimizations including quality, format, and sizing // via responsive sizing. When passing this in, it also prevents the
loader
from running, thus // breaking this component. This rewrites thesrc
to construct a fully formed Cloudinary URL // that also disables format and quality transformations, to deliver it as unoptimized // See about unoptimized not working with loader: https://github.com/vercel/next.js/issues/50764const IMAGE_OPTIONS: { unoptimized?: boolean } = (process.env.__NEXT_IMAGE_OPTS || {}) as unknown as object;
if ( props.unoptimized === true || IMAGE_OPTIONS?.unoptimized === true ) { imageProps.src = getCldImageUrl({ ...cldOptions, width: imageProps.width, height: imageProps.height, src: imageProps.src as string, format: 'default', quality: 'default', }, props.config); }
/**
handleOnError */
async function onError(options: SyntheticEvent<HTMLImageElement, Event>) { let pollForImage = true;
// The onError function should never fire more than once. The use case for tracking it // at all outside of the standard Next Image flow is for scenarios like when Cloudinary // is processing an image where we want to try to update the UI upon completion. // If this fires a second time, it is likely because of another issue, which will end // up triggering an infinite loop if the resulting image keeps erroring and // this function sets a key using the current time to force refresh the UI
if ( hasThrownError ) return;
hasThrownError = true;
if ( typeof props.onError === 'function' ) { const onErrorResult = props.onError(options);
if ( typeof onErrorResult === 'boolean' && onErrorResult === false ) { pollForImage = false; } } else if ( typeof props.onError === 'boolean' && props.onError === false ) { pollForImage = false; }
// Give an escape hatch in case the user wants to handle the error themselves // or if they want to disable polling for the image
if ( pollForImage === false ) return;
const image = options.target as HTMLImageElement const result = await pollForProcessingImage({ src: image.src })
if ( result ) { setImgKey(
${defaultImgKey};${Date.now()}
); } }const handleOnError = useCallback(onError, [pollForProcessingImage, defaultImgKey]);
// Copypasta from https://github.com/prismicio/prismic-next/pull/79/files // Thanks Angelo! // TODO: Remove once https://github.com/vercel/next.js/issues/52216 is resolved.
let ResolvedImage = Image;
if ("default" in ResolvedImage) { ResolvedImage = (ResolvedImage as unknown as { default: typeof Image }).default; }
return ( <ResolvedImage key={imgKey} {...imageProps} loader={(loaderOptions) => cloudinaryLoader({ loaderOptions, imageProps, cldOptions, cldConfig: props.config })} onError={handleOnError} ref={ref} /> ); });
export default CldImage; export { default } from './CldImage'; export type { CldImageProps } from './CldImage'; import React from 'react'; import Head from 'next/head';
import { CldImageProps } from '../CldImage/CldImage'; import { getCldOgImageUrl } from '../../helpers/getCldOgImageUrl'; import { OG_IMAGE_WIDTH, OG_IMAGE_HEIGHT } from '../../constants/sizes';
const TWITTER_CARD = 'summary_large_image';
export type CldOgImageProps = CldImageProps & { excludeTags?: Array;
keys?: object;
twitterTitle?: string;
}
const CldOgImage = ({ excludeTags = [], twitterTitle, keys = {}, ...props }: CldOgImageProps) => { const { alt } = props;
// We need to separately handle the width and the height to allow our user to pass in // a custom value, but also we need to know this at the component level so that we can // use it when rendering the meta tags
let { width = OG_IMAGE_WIDTH, height = OG_IMAGE_HEIGHT } = props;
// Normalize the width and height
width = typeof width === 'string' ? parseInt(width) : width; height = typeof height === 'string' ? parseInt(height) : height;
// Render the final URLs. We use two different format versions to deliver // webp for Twitter as it supports it (and we can control with tags) where // other platforms may not support webp, so we deliver jpg
const ogImageUrl = getCldOgImageUrl({ ...props, width, height });
const twitterImageUrl = getCldOgImageUrl({ ...props, width, height, format: props.format || 'webp', });
const metaKeys = { 'og:image': 'og-image', 'og:image:secure_url': 'og-image-secureurl', 'og:image:width': 'og-image-width', 'og:image:height': 'og-image-height', 'og:image:alt': 'og-image-alt', 'twitter:title': 'twitter-title', 'twitter:card': 'twitter-card', 'twitter:image': 'twitter-image', ...keys }
// We need to include the tags within the Next.js Head component rather than // direcly adding them inside of the Head otherwise we get unexpected results
); }
export default CldOgImage; export { default } from './CldOgImage'; export type { CldOgImageProps } from './CldOgImage'; import React from 'react'; import CldUploadWidget, { CldUploadWidgetProps } from '../CldUploadWidget';
export interface CldUploadButtonProps extends Omit<CldUploadWidgetProps, 'children'> { className?: string; children?: JSX.Element | string | Array<JSX.Element|string>; onClick?: Function; }
const CldUploadButton = ({ className, children, onClick, onError, onOpen, onUpload, onAbort, onBatchCancelled, onClose, onDisplayChanged, onPublicId, onQueuesEnd, onQueuesStart, onRetry, onShowCompleted, onSourceChanged, onSuccess, onTags, onUploadAdded, options, signatureEndpoint, uploadPreset, ...props }: CldUploadButtonProps) => {
return ( <> <CldUploadWidget onError={onError} onOpen={onOpen} onUpload={onUpload} onAbort={onAbort} onBatchCancelled={onBatchCancelled} onClose={onClose} onDisplayChanged={onDisplayChanged} onPublicId={onPublicId} onQueuesEnd={onQueuesEnd} onQueuesStart={onQueuesStart} onRetry={onRetry} onShowCompleted={onShowCompleted} onSourceChanged={onSourceChanged} onSuccess={onSuccess} onTags={onTags} onUploadAdded={onUploadAdded} options={options} signatureEndpoint={signatureEndpoint} uploadPreset={uploadPreset}
); };
export default CldUploadButton;export { default } from './CldUploadButton'; export type { CldUploadButtonProps } from './CldUploadButton';import React, { useState, useEffect, useRef } from 'react'; import Script from 'next/script'; import { CloudinaryUploadWidgetResults, CloudinaryUploadWidgetInstanceMethods, CloudinaryUploadWidgetInstanceMethodCloseOptions, CloudinaryUploadWidgetInstanceMethodDestroyOptions, CloudinaryUploadWidgetInstanceMethodOpenOptions, CloudinaryUploadWidgetSources, CloudinaryUploadWidgetError } from '@cloudinary-util/types';
import { triggerOnIdle } from '../../lib/util';
import { CldUploadEventCallback, CldUploadWidgetCloudinaryInstance, CldUploadWidgetProps, CldUploadWidgetWidgetInstance, } from './CldUploadWidget.types';
import {checkForCloudName} from "../../lib/cloudinary";
const WIDGET_WATCHED_EVENTS = [ 'success', ];
const WIDGET_EVENTS: { [key: string]: string } = { 'abort': 'onAbort', 'batch-cancelled': 'onBatchCancelled', 'close': 'onClose', 'display-changed': 'onDisplayChanged', 'publicid': 'onPublicId', 'queues-end': 'onQueuesEnd', 'queues-start': 'onQueuesStart', 'retry': 'onRetry', 'show-completed': 'onShowCompleted', 'source-changed': 'onSourceChanged', 'success': 'onSuccess', 'tags': 'onTags', 'upload-added': 'onUploadAdded', }
const CldUploadWidget = ({ children, onError, onOpen, onUpload, options, signatureEndpoint, uploadPreset, ...props }: CldUploadWidgetProps) => { const cloudinary: CldUploadWidgetCloudinaryInstance = useRef(); const widget: CldUploadWidgetWidgetInstance = useRef();
const signed = !!signatureEndpoint;
const [error, setError] = useState<CloudinaryUploadWidgetError | undefined>(undefined); const [results, setResults] = useState<CloudinaryUploadWidgetResults | undefined>(undefined); const [isScriptLoading, setIsScriptLoading] = useState(true);
// When creating a signed upload, you need to provide both your Cloudinary API Key // as well as a signature generator function that will sign any paramters // either on page load or during the upload process. Read more about signed uploads at: // https://cloudinary.com/documentation/upload_widget#signed_uploads
const uploadOptions = { cloudName: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, uploadPreset: uploadPreset || process.env.NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET, apiKey: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY, ...options, };
//Check if Cloud Name exists checkForCloudName(process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME);
if ( signed ) { uploadOptions.uploadSignature = generateSignature;
}
// Handle result states and callbacks
useEffect(() => { if ( typeof results === 'undefined' ) return;
}, [results])
/**
@description Stores the Cloudinary window instance to a ref when the widget script loads */
function handleOnLoad() { setIsScriptLoading(false);
if ( !cloudinary.current ) { cloudinary.current = (window as any).cloudinary; }
// To help improve load time of the widget on first instance, use requestIdleCallback // to trigger widget creation. Optional.
triggerOnIdle(() => { if ( !widget.current ) { widget.current = createWidget(); } }); }
useEffect(() => { return () => { widget.current?.destroy(); widget.current = undefined; } }, [])
/**
@description Makes a request to an endpoint to sign Cloudinary parameters as part of widget creation */
function generateSignature(callback: Function, paramsToSign: object) { if ( typeof signatureEndpoint === 'undefined' ) { throw Error('Failed to generate signature: signatureEndpoint undefined.') } fetch(signatureEndpoint, { method: 'POST', body: JSON.stringify({ paramsToSign, }), headers: { 'Content-Type': 'application/json' } }) .then((r) => r.json()) .then(({ signature }) => { callback(signature); }); }
/**
https://cloudinary.com/documentation/upload_widget_reference#instance_methods */
function invokeInstanceMethod< TMethod extends keyof CloudinaryUploadWidgetInstanceMethods
if (typeof widget?.current[method] === "function") { return widget.currentmethod; } }
function close(options?: CloudinaryUploadWidgetInstanceMethodCloseOptions) { invokeInstanceMethod('close', [options]); }
function destroy(options?: CloudinaryUploadWidgetInstanceMethodDestroyOptions) { return invokeInstanceMethod('destroy', [options]); }
function hide() { invokeInstanceMethod('hide'); }
function isDestroyed() { return invokeInstanceMethod('isDestroyed'); }
function isMinimized() { return invokeInstanceMethod('isMinimized'); }
function isShowing() { return invokeInstanceMethod('isShowing'); }
function minimize() { invokeInstanceMethod('minimize'); }
function open(widgetSource?: CloudinaryUploadWidgetSources, options?: CloudinaryUploadWidgetInstanceMethodOpenOptions) { invokeInstanceMethod('open', [widgetSource, options]);
if ( typeof onOpen === 'function' ) { onOpen(widget.current); } }
function show() { invokeInstanceMethod('show'); }
function update() { invokeInstanceMethod('update'); }
const instanceMethods: CloudinaryUploadWidgetInstanceMethods = { close, destroy, hide, isDestroyed, isMinimized, isShowing, minimize, open, show, update, }
/**
@description Creates a new instance of the Cloudinary widget and stores in a ref */
function createWidget() { return cloudinary.current?.createUploadWidget(uploadOptions, (uploadError: CloudinaryUploadWidgetError, uploadResult: CloudinaryUploadWidgetResults) => { if ( uploadError && uploadError !== null ) { setError(uploadError);
}
if ( typeof uploadResult?.event === 'string' ) { if ( WIDGET_WATCHED_EVENTS.includes(uploadResult?.event) ) { setResults(uploadResult); }
} }); }
return ( <> {typeof children === 'function' && children({ cloudinary: cloudinary.current, widget: widget.current, results, error, isLoading: isScriptLoading, ...instanceMethods, })} <Script id={
cloudinary-uploadwidget-${Math.floor(Math.random() * 100)}
} src="https://upload-widget.cloudinary.com/global/all.js" onLoad={handleOnLoad} onError={(e) => console.error(Failed to load Cloudinary Upload Widget: ${e.message}
)} /> </> ); };export default CldUploadWidget;import { CloudinaryUploadWidgetOptions, CloudinaryUploadWidgetResults, CloudinaryUploadWidgetInstanceMethods, CloudinaryUploadWidgetError } from '@cloudinary-util/types';
export type CldUploadWidgetCloudinaryInstance = any; export type CldUploadWidgetWidgetInstance = any;
export interface CldUploadWidgetProps { children?: ({ cloudinary, widget, open, results, error }: CldUploadWidgetPropsChildren) => JSX.Element; onError?: CldUploadEventCallbackError; onOpen?: CldUploadEventCallbackWidgetOnly; /**
export type CldUploadWidgetPropsChildren = { cloudinary: CldUploadWidgetCloudinaryInstance; widget: CldUploadWidgetWidgetInstance; error?: CloudinaryUploadWidgetError; isLoading?: boolean; results?: CloudinaryUploadWidgetResults; } & CloudinaryUploadWidgetInstanceMethods;
export type CldUploadEventCallback = (results: CloudinaryUploadWidgetResults, widget: CldUploadEventCallbackWidget) => void; export type CldUploadEventCallbackNoOptions = (results: CloudinaryUploadWidgetResults, widget: CldUploadWidgetWidgetInstance) => void; export type CldUploadEventCallbackWidgetOnly = (widget: CldUploadWidgetWidgetInstance) => void; export type CldUploadEventCallbackError = (error: CloudinaryUploadWidgetError, widget: CldUploadEventCallbackWidget) => void;
export type CldUploadEventCallbackWidget = { widget: CldUploadWidgetWidgetInstance; } & CloudinaryUploadWidgetInstanceMethods;export { default } from './CldUploadWidget'; export type { CldUploadWidgetProps, CldUploadWidgetPropsChildren } from './CldUploadWidget.types';import nextPkg from 'next/package.json'; import pkg from '../../package.json';
export const NEXT_CLOUDINARY_ANALYTICS_PRODUCT_ID = 'A'; export const NEXT_CLOUDINARY_ANALYTICS_ID = 'V'; export const NEXT_VERSION = normalizeVersion(nextPkg.version); export const NEXT_CLOUDINARY_VERSION = normalizeVersion(pkg.version);
function normalizeVersion(version: string) { let normalized = version; if ( normalized.includes('-') ) { normalized = normalized.split('-')[0]; } return normalized; }export const OG_IMAGE_WIDTH = 1200; export const OG_IMAGE_HEIGHT = 627; helpers import { constructCloudinaryUrl } from '@cloudinary-util/url-loader'; import type { ImageOptions, ConfigOptions, AnalyticsOptions } from '@cloudinary-util/url-loader';
import { NEXT_CLOUDINARY_ANALYTICS_PRODUCT_ID, NEXT_CLOUDINARY_ANALYTICS_ID, NEXT_CLOUDINARY_VERSION, NEXT_VERSION } from '../constants/analytics'; import {checkForCloudName} from "../lib/cloudinary";
/**
export type GetCldImageUrlOptions = ImageOptions; export type GetCldImageUrlConfig = ConfigOptions; export type GetCldImageUrlAnalytics = AnalyticsOptions;
export function getCldImageUrl(options: GetCldImageUrlOptions, config?: GetCldImageUrlConfig, analytics?: GetCldImageUrlAnalytics) { // @ts-expect-error Property 'cloud' does not exist on type 'CloudinaryConfigurationOptions'. const cloudName = config?.cloud?.cloudName ?? process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME; checkForCloudName(cloudName); return constructCloudinaryUrl({ options, config: Object.assign({ cloud: { cloudName: cloudName }, }, config), analytics: Object.assign({ product: NEXT_CLOUDINARY_ANALYTICS_PRODUCT_ID, sdkCode: NEXT_CLOUDINARY_ANALYTICS_ID, sdkSemver: NEXT_CLOUDINARY_VERSION, techVersion: NEXT_VERSION, feature: '' }, analytics) }); }import { OG_IMAGE_WIDTH, OG_IMAGE_HEIGHT } from '../constants/sizes';
import { getCldImageUrl } from './getCldImageUrl'; import type { GetCldImageUrlOptions } from './getCldImageUrl';
/**
export type GetCldOgImageUrlOptions = GetCldImageUrlOptions;
export function getCldOgImageUrl(options: GetCldOgImageUrlOptions) { return getCldImageUrl({ ...options, format: options.format || 'jpg', width: options.width || OG_IMAGE_WIDTH, height: options.height || OG_IMAGE_HEIGHT, crop: options.crop || { type: 'fill', gravity: 'center', source: true } }); }/**
export interface PollForProcessingImageOptions { src: string; }
export async function pollForProcessingImage(options: PollForProcessingImageOptions): Promise {
const { src } = options;
try {
await new Promise((resolve, reject) => {
fetch(src).then(res => {
if ( !res.ok ) {
reject(res);
return;
}
resolve(res);
});
});
} catch(e: any) {
// Timeout for 200ms before trying to fetch again to avoid overwhelming requests
} return true; }
export function checkForCloudName(cloudName: string | undefined) { if (!cloudName) { throw new Error('A Cloudinary Cloud name is required, please make sure NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME is set and configured in your environment.'); } }/**
export function triggerOnIdle(callback: any) { if ( window && 'requestIdleCallback' in window ) { return requestIdleCallback(callback); } return setTimeout(() => callback(), 1); }import { ImageProps } from 'next/image';
import { getCldImageUrl } from '../helpers/getCldImageUrl';
export interface CloudinaryLoaderCldOptions { }
export interface CloudinaryLoaderLoaderOptions { height?: string | number; width?: string | number; }
export interface CloudinaryLoader { loaderOptions: CloudinaryLoaderLoaderOptions; imageProps: ImageProps; cldOptions: CloudinaryLoaderCldOptions; cldConfig?: object; }
export function cloudinaryLoader({ loaderOptions, imageProps, cldOptions, cldConfig = {} }: CloudinaryLoader) { const options = { ...imageProps, ...cldOptions }
options.width = typeof options.width === 'string' ? parseInt(options.width) : options.width; options.height = typeof options.height === 'string' ? parseInt(options.height) : options.height;
// // The loader options are used to create dynamic sizing when working with responsive images // // so these should override the default options collected from the props alone if // // the results are different. While we don't always use the height in the loader logic, // // we always pass it here, as the usage is determined later based on cropping.js
if ( typeof loaderOptions?.width === 'number' && typeof options.width === 'number' && loaderOptions.width !== options.width ) { const multiplier = loaderOptions.width / options.width;
} else if ( typeof loaderOptions?.width === 'number' && typeof options?.width !== 'number' ) { // If we don't have a width on the options object, this may mean that the component is using // the fill option: https://nextjs.org/docs/pages/api-reference/components/image#fill // The Fill option does not allow someone to pass in a width or a height // If this is the case, we still need to define a width for sizing optimization but also // for responsive sizing to take effect, so we can utilize the loader width for the base width options.width = loaderOptions?.width; }
// @ts-ignore return getCldImageUrl(options, cldConfig); }import { ImageProps } from 'next/image';
import { getCldImageUrl } from '../helpers/getCldImageUrl';
export interface CloudinaryLoaderCldOptions { }
export interface CloudinaryLoaderLoaderOptions { height?: string | number; width?: string | number; }
export interface CloudinaryLoader { loaderOptions: CloudinaryLoaderLoaderOptions; imageProps: ImageProps; cldOptions: CloudinaryLoaderCldOptions; cldConfig?: object; }
export function cloudinaryLoader({ loaderOptions, imageProps, cldOptions, cldConfig = {} }: CloudinaryLoader) { const options = { ...imageProps, ...cldOptions }
options.width = typeof options.width === 'string' ? parseInt(options.width) : options.width; options.height = typeof options.height === 'string' ? parseInt(options.height) : options.height;
// // The loader options are used to create dynamic sizing when working with responsive images // // so these should override the default options collected from the props alone if // // the results are different. While we don't always use the height in the loader logic, // // we always pass it here, as the usage is determined later based on cropping.js
if ( typeof loaderOptions?.width === 'number' && typeof options.width === 'number' && loaderOptions.width !== options.width ) { const multiplier = loaderOptions.width / options.width;
} else if ( typeof loaderOptions?.width === 'number' && typeof options?.width !== 'number' ) { // If we don't have a width on the options object, this may mean that the component is using // the fill option: https://nextjs.org/docs/pages/api-reference/components/image#fill // The Fill option does not allow someone to pass in a width or a height // If this is the case, we still need to define a width for sizing optimization but also // for responsive sizing to take effect, so we can utilize the loader width for the base width options.width = loaderOptions?.width; }
// @ts-ignore return getCldImageUrl(options, cldConfig); }export { default as CldImage } from './components/CldImage'; export type { CldImageProps } from './components/CldImage';
export { default as CldOgImage } from './components/CldOgImage'; export type { CldOgImageProps } from './components/CldOgImage';
export { default as CldUploadButton } from './components/CldUploadButton'; export type { CldUploadButtonProps } from './components/CldUploadButton';
export { default as CldUploadWidget } from './components/CldUploadWidget'; export type { CldUploadWidgetProps, CldUploadWidgetPropsChildren } from './components/CldUploadWidget'; export type { CloudinaryUploadWidgetOptions, CloudinaryUploadWidgetResults, CloudinaryUploadWidgetInfo, CloudinaryUploadWidgetInstanceMethods, CloudinaryUploadWidgetInstanceMethodCloseOptions, CloudinaryUploadWidgetInstanceMethodDestroyOptions, CloudinaryUploadWidgetInstanceMethodOpenOptions, CloudinaryUploadWidgetInstanceMethodUpdateOptions, CloudinaryUploadWidgetSources, CloudinaryUploadWidgetError } from '@cloudinary-util/types';
export { default as CldVideoPlayer } from './components/CldVideoPlayer'; export type { CldVideoPlayerProps, CldVideoPlayerPropsLogo } from './components/CldVideoPlayer'; export type { CloudinaryVideoPlayer, CloudinaryVideoPlayerOptions, CloudinaryVideoPlayerOptionsColors, CloudinaryVideoPlayerOptionsLogo } from '@cloudinary-util/types';
export { cloudinaryLoader } from './loaders/cloudinary-loader'; export type { CloudinaryLoader, CloudinaryLoaderLoaderOptions, CloudinaryLoaderCldOptions } from './loaders/cloudinary-loader';
export { getCldImageUrl } from './helpers/getCldImageUrl'; export type { GetCldImageUrlOptions, GetCldImageUrlConfig, GetCldImageUrlAnalytics } from './helpers/getCldImageUrl';
export { getCldOgImageUrl } from './helpers/getCldOgImageUrl'; export type { GetCldOgImageUrlOptions } from './helpers/getCldOgImageUrl';
export { getCldVideoUrl } from './helpers/getCldVideoUrl'; export type { GetCldVideoUrlOptions, GetCldVideoUrlConfig, GetCldVideoUrlAnalytics } from './helpers/getCldVideoUrl';import { describe, it, expect } from 'vitest';
import { cloudinaryLoader } from '../../src/loaders/cloudinary-loader';
const cldConfig = { cloud: { cloudName: 'test-cloud' }, }
describe('Cloudinary Loader', () => { describe('cloudinaryLoader', () => { it('should return a Cloudinary URL with basic features', async () => { const imageProps = { height: '600', sizes: '100vw', src: 'images/turtle', width: '960', }
}) });import { vi, describe, it, beforeEach, afterAll, expect } from 'vitest';
import { getCldImageUrl } from '../../src/helpers/getCldImageUrl';
describe('Cloudinary', () => { const OLD_ENV = process.env;
beforeEach(() => { vi.resetModules() process.env = { ...OLD_ENV }; });
afterAll(() => { process.env = OLD_ENV; });
describe('getCldImageUrl', () => { it('should pass', () => { const cloudName = 'customtestcloud';
}); })"use client";
import { CldImage, CldUploadWidget, CldUploadButton, CldVideoPlayer } from '../../../'; import { getCldImageUrl, getCldOgImageUrl, getCldVideoUrl } from '../../../';
import '../../../dist/cld-video-player.css';
export default function Home() { console.log(getCldImageUrl({ src: 'images/turtle' })) console.log(getCldOgImageUrl({ src: 'images/turtle' })) console.log(getCldVideoUrl({ src: 'videos/mountain-stars' })) return ( <> <div style={{ marginBottom: '2em' }}>
CldImage
) }
Checklist
- [X] Create `src/app/api/users/upload/route.ts` ✓ https://github.com/labi1240/project101/commit/6f35c4e6b31a13bda5420e8d17958de605821057 [Edit](https://github.com/labi1240/project101/edit/sweep/please_setup_the_cloudinary_for_uploadin_100a7/src/app/api/users/upload/route.ts) - [X] Running GitHub Actions for `src/app/api/users/upload/route.ts` ✓ [Edit](https://github.com/labi1240/project101/edit/sweep/please_setup_the_cloudinary_for_uploadin_100a7/src/app/api/users/upload/route.ts) - [X] Modify `src/app/api/users/signup/route.ts` ✓ https://github.com/labi1240/project101/commit/0a51212681cf110ae64a2249c0f07bb3ae3eb0c2 [Edit](https://github.com/labi1240/project101/edit/sweep/please_setup_the_cloudinary_for_uploadin_100a7/src/app/api/users/signup/route.ts) - [X] Running GitHub Actions for `src/app/api/users/signup/route.ts` ✓ [Edit](https://github.com/labi1240/project101/edit/sweep/please_setup_the_cloudinary_for_uploadin_100a7/src/app/api/users/signup/route.ts) - [X] Modify `src/app/api/users/me/route.ts` ✓ https://github.com/labi1240/project101/commit/377e8878ecc0ca4e917bab1d19eab4671dd10fbc [Edit](https://github.com/labi1240/project101/edit/sweep/please_setup_the_cloudinary_for_uploadin_100a7/src/app/api/users/me/route.ts) - [X] Running GitHub Actions for `src/app/api/users/me/route.ts` ✓ [Edit](https://github.com/labi1240/project101/edit/sweep/please_setup_the_cloudinary_for_uploadin_100a7/src/app/api/users/me/route.ts)