Skip to content

07: How to Create, Transform, and Download Images#5

Merged
CodingWithTushar merged 1 commit into
mainfrom
07--How-to-Create,-Transform,-and-Download-Images
Jul 21, 2026
Merged

07: How to Create, Transform, and Download Images#5
CodingWithTushar merged 1 commit into
mainfrom
07--How-to-Create,-Transform,-and-Download-Images

Conversation

@CodingWithTushar

@CodingWithTushar CodingWithTushar commented Jul 21, 2026

Copy link
Copy Markdown
Owner
  • Added lesson content and step-by-step guide for image creation and transformation.
  • Implemented example code:
    • Server-side utilities for image processing (resize, crop, convert).
    • Client-side demo UI for uploading images, applying transformations, previewing, and downloading.
  • Added sample/demo images and assets used by the lesson.
  • Added tests that cover the main image transform utilities.
  • Updated documentation / README with usage instructions and API examples.
  • Small styling and accessibility improvements for the demo UI.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The static home page is replaced with a client-side social media image creator. Authenticated uploads return a Cloudinary public ID, selected formats render transformed previews, and users can download PNG files. Next.js, TypeScript, and Cloudinary configuration are updated.

Changes

Social image creator

Layer / File(s) Summary
Runtime and build configuration
.vscode/settings.json, package.json, tsconfig.json, next.config.ts, lib/cloudinary.ts
Updates the Next.js version, TypeScript settings, image configuration, build validation, and Cloudinary environment variable names.
Authenticated upload flow
utils/helpers.ts, app/api/upload-image/route.ts
Retrieves the authenticated user synchronously and uses the result in the upload request handler.
Social image creation interface
app/page.tsx
Adds upload progress, social format selection, Cloudinary preview rendering, transformation loading state, and format-based PNG downloads.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No author description was provided, so relatedness can't be assessed beyond the changes themselves. Add a brief description of the image creator feature and related config updates.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately reflects the main change: a UI for creating, transforming, and downloading images.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@CodingWithTushar
CodingWithTushar merged commit 8d718c0 into main Jul 21, 2026
1 check was pending

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
app/page.tsx (1)

130-141: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Clear the transforming state on image load error.

If the image fails to load or the Cloudinary transformation errors out, onLoad will not trigger, leaving the loading spinner visible indefinitely. Adding an onError handler ensures the UI can recover from failures.

🛠️ Proposed fix
                         ref={imgRef}
                         onLoad={() => setIsTransforming(false)}
+                        onError={() => setIsTransforming(false)}
                       />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/page.tsx` around lines 130 - 141, Update the CldImage instance in the
image transformation UI to add an onError handler that calls
setIsTransforming(false), matching the existing onLoad behavior so failed image
loads or transformations also clear the spinner.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/api/upload-image/route.ts`:
- Line 8: Update the route handler’s requireUser call to await the asynchronous
authorization check before continuing with upload processing, preserving the
returned user ID for subsequent logic and ensuring rejected authentication
prevents the upload.

In `@app/page.tsx`:
- Around line 78-80: Correct the misspelled “Soical” text in the main heading
rendered by the h1 element, changing it to “Social” while preserving the
existing heading styling and content.
- Around line 40-43: Update the fetch call in the image upload flow to use an
absolute `/api/upload-image` request path instead of the relative path,
preserving the existing POST method and formData body.
- Around line 8-14: Correct the user-facing keys in socialFormats by changing
“Instagram Protrait” to “Instagram Portrait” and “Twtter Header” to “Twitter
Header”; leave the associated dimensions and aspect ratios unchanged.
- Around line 124-128: Update the transforming overlay in the isTransforming
conditional to replace the Tailwind v3 bg-opacity-50 utility with
bg-base-100/50, preserving the existing translucent overlay styling and layout
classes.

In `@next.config.ts`:
- Around line 6-13: Restrict the `images.remotePatterns` configuration in
`next.config.ts` by replacing the wildcard hostname with the specific trusted
image-hosting domains the application uses. Preserve HTTPS and add one pattern
per approved domain; do not allow arbitrary external hosts.

In `@utils/helpers.ts`:
- Line 8: Update requireUser() to await auth() before destructuring userId,
ensuring the authorization check receives the resolved authentication data and
does not always fail.

---

Nitpick comments:
In `@app/page.tsx`:
- Around line 130-141: Update the CldImage instance in the image transformation
UI to add an onError handler that calls setIsTransforming(false), matching the
existing onLoad behavior so failed image loads or transformations also clear the
spinner.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ec83b4ed-70e8-45b6-a596-dc7fa077782c

📥 Commits

Reviewing files that changed from the base of the PR and between bbd2a44 and a686a79.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • .vscode/settings.json
  • app/api/upload-image/route.ts
  • app/page.tsx
  • lib/cloudinary.ts
  • next.config.ts
  • package.json
  • tsconfig.json
  • utils/helpers.ts

export async function POST(request: NextRequest) {
try {
const userId = await requireUser();
const userId = requireUser();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Missing await causes an Authentication Bypass vulnerability.

requireUser is an async function. Calling it without await returns a Promise instead of blocking execution until the authorization check completes. If the check fails, the Promise rejects asynchronously, but the route handler continues executing. This allows attackers to bypass authorization and successfully upload images directly to Cloudinary.

🔒️ Proposed fix
-    const userId = requireUser();
+    const userId = await requireUser();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const userId = requireUser();
const userId = await requireUser();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/upload-image/route.ts` at line 8, Update the route handler’s
requireUser call to await the asynchronous authorization check before continuing
with upload processing, preserving the returned user ID for subsequent logic and
ensuring rejected authentication prevents the upload.

Comment thread app/page.tsx
Comment on lines +8 to +14
const socialFormats = {
"Instagram Square (1:1)": { width: 1080, height: 1080, aspectRatio: "1:1" },
"Instagram Protrait (4:5)": { width: 1080, height: 1350, aspectRatio: "4:5" },
"Twitter Post (16:9)": { width: 1200, height: 675, aspectRatio: "16:9" },
"Twtter Header (3:1)": { width: 1500, height: 500, aspectRatio: "3:1" },
"Facebook Cover (205:78)": { width: 820, height: 312, aspectRatio: "205:78" },
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix typos in social format names.

There are typos in the object keys which are displayed to the user in the dropdown selection.

📝 Proposed fix
 const socialFormats = {
   "Instagram Square (1:1)": { width: 1080, height: 1080, aspectRatio: "1:1" },
-  "Instagram Protrait (4:5)": { width: 1080, height: 1350, aspectRatio: "4:5" },
+  "Instagram Portrait (4:5)": { width: 1080, height: 1350, aspectRatio: "4:5" },
   "Twitter Post (16:9)": { width: 1200, height: 675, aspectRatio: "16:9" },
-  "Twtter Header (3:1)": { width: 1500, height: 500, aspectRatio: "3:1" },
+  "Twitter Header (3:1)": { width: 1500, height: 500, aspectRatio: "3:1" },
   "Facebook Cover (205:78)": { width: 820, height: 312, aspectRatio: "205:78" },
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const socialFormats = {
"Instagram Square (1:1)": { width: 1080, height: 1080, aspectRatio: "1:1" },
"Instagram Protrait (4:5)": { width: 1080, height: 1350, aspectRatio: "4:5" },
"Twitter Post (16:9)": { width: 1200, height: 675, aspectRatio: "16:9" },
"Twtter Header (3:1)": { width: 1500, height: 500, aspectRatio: "3:1" },
"Facebook Cover (205:78)": { width: 820, height: 312, aspectRatio: "205:78" },
};
const socialFormats = {
"Instagram Square (1:1)": { width: 1080, height: 1080, aspectRatio: "1:1" },
"Instagram Portrait (4:5)": { width: 1080, height: 1350, aspectRatio: "4:5" },
"Twitter Post (16:9)": { width: 1200, height: 675, aspectRatio: "16:9" },
"Twitter Header (3:1)": { width: 1500, height: 500, aspectRatio: "3:1" },
"Facebook Cover (205:78)": { width: 820, height: 312, aspectRatio: "205:78" },
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/page.tsx` around lines 8 - 14, Correct the user-facing keys in
socialFormats by changing “Instagram Protrait” to “Instagram Portrait” and
“Twtter Header” to “Twitter Header”; leave the associated dimensions and aspect
ratios unchanged.

Comment thread app/page.tsx
Comment on lines +40 to +43
const response = await fetch("api/upload-image", {
method: "POST",
body: formData,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use an absolute path for the API request.

Using a relative path ("api/upload-image") works when the component is rendered on the root page (/), but it will fail if this component is ever reused on a nested route (e.g., /dashboard/create). Prepend a slash to make it an absolute path.

🐛 Proposed fix
-      const response = await fetch("api/upload-image", {
+      const response = await fetch("/api/upload-image", {
         method: "POST",
         body: formData,
       });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const response = await fetch("api/upload-image", {
method: "POST",
body: formData,
});
const response = await fetch("/api/upload-image", {
method: "POST",
body: formData,
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/page.tsx` around lines 40 - 43, Update the fetch call in the image upload
flow to use an absolute `/api/upload-image` request path instead of the relative
path, preserving the existing POST method and formData body.

Comment thread app/page.tsx
Comment on lines +78 to +80
<h1 className="text-3xl font-bold mb-6 text-center">
Soical Media Image Creator
</h1>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix typo in the main heading.

📝 Proposed fix
           <h1 className="text-3xl font-bold mb-6 text-center">
-            Soical Media Image Creator
+            Social Media Image Creator
           </h1>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<h1 className="text-3xl font-bold mb-6 text-center">
Soical Media Image Creator
</h1>
<h1 className="text-3xl font-bold mb-6 text-center">
Social Media Image Creator
</h1>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/page.tsx` around lines 78 - 80, Correct the misspelled “Soical” text in
the main heading rendered by the h1 element, changing it to “Social” while
preserving the existing heading styling and content.

Comment thread app/page.tsx
Comment on lines +124 to +128
{isTransforming && (
<div className="absolute inset-0 flex items-center justify-center bg-base-100 bg-opacity-50 z-10">
<span className="loading loading-spinner loading-lg"></span>
</div>
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Tailwind CSS version
npm ls tailwindcss

Repository: CodingWithTushar/learningsaas

Length of output: 667


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== app/page.tsx snippet ==\n'
sed -n '116,134p' app/page.tsx

printf '\n== Tailwind config files ==\n'
git ls-files | rg '(^|/)(tailwind|postcss)\.(config|mjs|js|ts|cjs)$|^app/page\.tsx$'

printf '\n== bg-opacity usage ==\n'
rg -n 'bg-opacity-\d+' app tailwind.config.* postcss.config.* package.json 2>/dev/null || true

Repository: CodingWithTushar/learningsaas

Length of output: 1247


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== package.json dependencies ==\n'
sed -n '1,220p' package.json

printf '\n== CSS files mentioning tailwind/daisyui/base-100 ==\n'
git ls-files '*.css' '*.pcss' '*.scss' | xargs -r rg -n 'tailwind|daisyui|base-100|`@theme`|`@plugin`' || true

Repository: CodingWithTushar/learningsaas

Length of output: 1473


app/page.tsx:125 — Replace bg-opacity-50 with slash opacity syntax.
bg-opacity-50 won’t apply in Tailwind v4; use bg-base-100/50 so the overlay stays translucent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/page.tsx` around lines 124 - 128, Update the transforming overlay in the
isTransforming conditional to replace the Tailwind v3 bg-opacity-50 utility with
bg-base-100/50, preserving the existing translucent overlay styling and layout
classes.

Comment thread next.config.ts
Comment on lines +6 to +13
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**",
},
],
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Restrict remotePatterns to prevent open image proxying.

Using a wildcard ** for the hostname allows the Next.js Image Optimization API to proxy and optimize images from any HTTPS domain on the internet. This effectively turns your application into an open proxy, which can lead to excessive bandwidth usage, increased costs, and potential abuse (e.g., SSRF).

Restrict the hostname to the specific domains your application expects to load images from.

🛡️ Proposed fix (assuming Cloudinary)
   images: {
     remotePatterns: [
       {
         protocol: "https",
-        hostname: "**",
+        hostname: "res.cloudinary.com",
       },
     ],
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**",
},
],
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "res.cloudinary.com",
},
],
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@next.config.ts` around lines 6 - 13, Restrict the `images.remotePatterns`
configuration in `next.config.ts` by replacing the wildcard hostname with the
specific trusted image-hosting domains the application uses. Preserve HTTPS and
add one pattern per approved domain; do not allow arbitrary external hosts.

Comment thread utils/helpers.ts

export const requireUser = async () => {
const { userId } = await auth();
const { userId } = auth();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify `@clerk/nextjs` version in use
npm ls `@clerk/nextjs`

Repository: CodingWithTushar/learningsaas

Length of output: 543


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## package.json\n'
sed -n '1,220p' package.json

printf '\n## utils/helpers.ts\n'
cat -n utils/helpers.ts

printf '\n## references to auth()\n'
rg -n "\bauth\(\)" -S .

Repository: CodingWithTushar/learningsaas

Length of output: 2513


🌐 Web query:

Clerk @clerk/nextjs/server auth() return type v7.5.13 async sync Next.js 15

💡 Result:

In @clerk/nextjs v7.5.13, the auth helper is an asynchronous function [1][2]. It must be awaited to access the Auth object [3][4]. When using Next.js 15, which employs asynchronous dynamic APIs, you must call auth as follows [1]: import { auth } from '@clerk/nextjs/server'; export default async function Page { const { userId, sessionId, getToken } = await auth; //... } Key details regarding this behavior include: - Asynchronous Nature: The transition to an async auth helper was introduced to align with Next.js's move toward asynchronous request-based APIs [1][2]. - Return Value: Awaiting auth returns an Auth object containing properties such as userId, sessionId, sessionClaims, and methods like getToken [3][5]. - Usage: It is designed for use in server-side contexts within the App Router, including Server Components, Route Handlers, and Server Actions [6][7]. - Protect Method: Authorization checks are handled via the auth.protect method, which is available directly on the auth export, rather than being returned by the auth function itself [3][1].

Citations:


🌐 Web query:

site:clerk.com docs auth() @clerk/nextjs/server Promise Next.js 15

💡 Result:

In Next.js 15, the auth() helper from @clerk/nextjs/server is an asynchronous function that must be awaited [1][2]. It is used within the App Router to access authentication state in Server Components, Route Handlers, and Server Actions [3][4]. Key characteristics of auth() in Next.js 15: Asynchronous Usage: Because Next.js 15 utilizes asynchronous request APIs, auth() must be awaited [1]. Usage: import { auth } from '@clerk/nextjs/server'; export default async function Page { const { userId, isAuthenticated, redirectToSignIn } = await auth; if (!isAuthenticated) return redirectToSignIn; return

User ID: {userId}
; } Protection with protect: The protect() method is now accessed as a property of auth rather than being part of the returned object [1]. It is used to easily guard routes or operations. - In Pages/Server Components: await auth.protect() will redirect unauthenticated users to the sign-in page [3][5]. - In Route Handlers/Server Actions: await auth.protect() will return a 401 (Unauthorized) error if the user is not authenticated [6][5]. Requirements: - Configuration: auth() requires clerkMiddleware() to be properly configured in your application [3]. - Environment: It is strictly for server-side use (Server Components, Route Handlers, and Server Actions) [3]. This asynchronous pattern was introduced in @clerk/nextjs v6 to maintain compatibility with Next.js 15's architectural changes [1]. If you are migrating older code, Clerk provides a codemod to assist with updating auth() calls [1].

Citations:


Restore await on auth(). In requireUser(), destructuring userId from the unresolved promise leaves it undefined, so the authorization check always fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@utils/helpers.ts` at line 8, Update requireUser() to await auth() before
destructuring userId, ensuring the authorization check receives the resolved
authentication data and does not always fail.

@coderabbitai coderabbitai Bot mentioned this pull request Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant