Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion app/[username]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,21 @@ export default async function PublicProfile({

return (
<main className={`min-h-screen relative px-4 py-16 theme-${user.theme || "default"}`}>
{user.backgroundImage && (
<>
<div
className="fixed inset-0 z-[-2]"
style={{
backgroundImage: `url(${user.backgroundImage})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
/>
<div className="fixed inset-0 z-[-1] bg-black/40 backdrop-blur-[2px]" />
</>
)}
<ShareProfileButton />
<div className="mx-auto max-w-md">
<div className="mx-auto max-w-md relative z-10">
<ProfileCard
user={{
name: user.name,
Expand Down
30 changes: 30 additions & 0 deletions app/api/user/background/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { NextRequest, NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";

export async function PATCH(req: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const body = await req.json();
const { backgroundImage } = body;

if (backgroundImage !== null && typeof backgroundImage !== "string") {
return NextResponse.json({ error: "Invalid backgroundImage" }, { status: 400 });
}

const updatedUser = await prisma.user.update({
where: { email: session.user.email },
data: { backgroundImage },
});

return NextResponse.json({ success: true, backgroundImage: updatedUser.backgroundImage }, { status: 200 });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} catch (error) {
console.error("Failed to update background image:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}
61 changes: 61 additions & 0 deletions app/dashboard/AppearanceSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,24 @@ import { LayoutStyle } from "@/app/[username]/types/type";
export function AppearanceSection({
initialTheme,
initialLayout,
initialBackgroundImage,
onUpdateTheme,
onUpdateLayout,
onUpdateBackgroundImage,
}: {
initialTheme: string;
initialLayout: LayoutStyle;
initialBackgroundImage?: string;
onUpdateTheme: (theme: string) => void;
onUpdateLayout: (layout: LayoutStyle) => void;
onUpdateBackgroundImage: (url: string | null) => void;
}) {
const [selectedTheme, setSelectedTheme] = useState(initialTheme || "default");
const [selectedLayout, setSelectedLayout] = useState(initialLayout || "LIST");
const [backgroundImage, setBackgroundImage] = useState(initialBackgroundImage || "");
const [savingTheme, setSavingTheme] = useState(false);
const [savingLayout, setSavingLayout] = useState(false);
const [savingBg, setSavingBg] = useState(false);

async function handleSaveTheme(themeId: string) {
setSelectedTheme(themeId);
Expand Down Expand Up @@ -90,6 +96,34 @@ export function AppearanceSection({
}
}

async function handleSaveBackgroundImage() {
setSavingBg(true);
try {
const csrfToken = await getCsrfToken();
const res = await fetch("/api/user/background", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
"x-csrf-token": csrfToken,
},
body: JSON.stringify({ backgroundImage: backgroundImage || null }),
});

if (!res.ok) {
throw new Error("Failed to save background image");
}

const data = await res.json();
onUpdateBackgroundImage(data.backgroundImage);
toast.success("Background image updated!");
} catch (error) {
toast.error("Failed to update background image");
console.error(error);
} finally {
setSavingBg(false);
}
}

return (
<section className="space-y-10">
{/* Layout Section */}
Expand Down Expand Up @@ -169,6 +203,33 @@ export function AppearanceSection({
))}
</div>
</div>

{/* Background Image Section */}
<div className="space-y-6">
<div className="space-y-1">
<h2 className="text-xl font-semibold">Custom Background</h2>
<p className="text-sm text-muted-foreground">
Set a custom background image URL for your public profile.
</p>
</div>
<div className="max-w-md flex gap-2">
<input
type="url"
placeholder="https://example.com/image.jpg"
className="flex-1 px-3 py-2 border rounded-md text-sm"
value={backgroundImage}
onChange={(e) => setBackgroundImage(e.target.value)}
disabled={savingBg}
/>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<button
onClick={handleSaveBackgroundImage}
disabled={savingBg}
className="px-4 py-2 bg-primary text-primary-foreground rounded-md text-sm font-medium disabled:opacity-50"
>
{savingBg ? "Saving..." : "Save"}
</button>
</div>
</div>
</section>
);
}
5 changes: 5 additions & 0 deletions app/dashboard/DashboardClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default function DashboardClient({
initialSeoTitle,
initialSeoDescription,
initialLayout,
initialBackgroundImage,
qrCode,
enableEmailCapture,
subscribers = [],
Expand All @@ -29,13 +30,15 @@ export default function DashboardClient({
initialSeoTitle?: string;
initialSeoDescription?: string;
initialLayout?: LayoutStyle;
initialBackgroundImage?: string | null;
qrCode?: React.ReactNode;
enableEmailCapture?: boolean;
subscribers?: { id: string; email: string; createdAt: Date }[];
}) {
const [links, setLinks] = useState(initialLinks);
const [theme, setTheme] = useState(initialTheme || "default");
const [layoutStyle, setLayoutStyle] = useState<LayoutStyle>(initialLayout || "LIST");
const [backgroundImage, setBackgroundImage] = useState<string | null>(initialBackgroundImage || "");
const [seoTitle, setSeoTitle] = useState(initialSeoTitle || "");
const [seoDescription, setSeoDescription] = useState(initialSeoDescription || "");
const [activeTab, setActiveTab] = useState<"links" | "appearance" | "seo">("links");
Expand Down Expand Up @@ -399,8 +402,10 @@ export default function DashboardClient({
<AppearanceSection
initialTheme={theme}
initialLayout={layoutStyle}
initialBackgroundImage={backgroundImage ?? undefined}
onUpdateTheme={setTheme}
onUpdateLayout={setLayoutStyle}
onUpdateBackgroundImage={setBackgroundImage}
/>
) : (
<SeoSection
Expand Down
1 change: 1 addition & 0 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default async function DashboardPage() {
initialLinks={nestedLinks}
initialTheme={user.theme}
initialLayout={user.layoutStyle}
initialBackgroundImage={user.backgroundImage}
initialSeoTitle={user.seoTitle || ""}
initialSeoDescription={user.seoDescription || ""}
qrCode={<QRCode />}
Expand Down
1 change: 1 addition & 0 deletions lib/userLookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const publicProfileSelect = {
username: true,
bio: true,
image: true,
backgroundImage: true,
theme: true,
themeType: true,
themeColor: true,
Expand Down
3 changes: 3 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ model User {
email String @unique
emailVerified DateTime?
image String?
backgroundImage String?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Schema declarations:"
rg -n 'backgroundImage' prisma/schema.prisma

echo "Migration references:"
rg -n 'backgroundImage' prisma/migrations 2>/dev/null || true

Repository: vishnukothakapu/linkid

Length of output: 292


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files relevant to Prisma/migrations:"
git ls-files | rg '(^|/)prisma(/|$)|schema\.prisma|migration\.)|migrations' | sed -n '1,200p'

echo
echo "schema.prisma around declarations:"
sed -n '1,40p;200,245p' prisma/schema.prisma

echo
echo "Changed files summary:"
git diff --stat

Repository: vishnukothakapu/linkid

Length of output: 381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files relevant to Prisma/migrations:"
git ls-files | rg '(^|/)prisma(/|$)|schema\.prisma|migration|index\.d\.prisma|migrations' | sed -n '1,220p' || true

echo
echo "schema.prisma around backgroundImage declarations:"
sed -n '1,40p;200,245p' prisma/schema.prisma

echo
echo "Changed files summary:"
git diff --stat

Repository: vishnukothakapu/linkid

Length of output: 3722


Add migration coverage for backgroundImage.

backgroundImage exists on User, ProfileDraft, and ProfileVersion, but no migration SQL adds these columns. Add and apply a migration that introduces the missing nullable columns before the new server code makes User.backgroundImage readable/writable.

🤖 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 `@prisma/schema.prisma` at line 20, Add migration coverage for the nullable
backgroundImage field on the User, ProfileDraft, and ProfileVersion Prisma
models. Create and apply a migration that adds the corresponding columns before
relying on the server code’s User.backgroundImage reads and writes, preserving
the existing schema definitions.


password String?

Expand Down Expand Up @@ -214,6 +215,7 @@ model ProfileDraft {
username String?
bio String?
image String?
backgroundImage String?
Comment thread
coderabbitai[bot] marked this conversation as resolved.
themeType String?
themeColor String?
themeCustom String?
Expand All @@ -232,6 +234,7 @@ model ProfileVersion {
username String?
bio String?
image String?
backgroundImage String?
themeType String?
themeColor String?
themeCustom String?
Expand Down
Loading