Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 1 addition & 3 deletions app/api/contacts/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { PrismaClient } from '@prisma/client';
import prisma from '@/lib/prisma';
import { isAuthenticated } from '@/lib/auth';

const prisma = new PrismaClient();

export async function GET(
request: NextRequest,
context: { params: Promise<{ id: string }> }
Expand Down
7 changes: 4 additions & 3 deletions app/api/contacts/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { PrismaClient } from '@prisma/client';
import prisma from '@/lib/prisma';
import { isAuthenticated } from '@/lib/auth';

const prisma = new PrismaClient();

export async function GET(request: NextRequest) {
try {
// Check if user is authenticated
Expand All @@ -19,6 +17,9 @@ export async function GET(request: NextRequest) {
}

const contacts = await prisma.contact.findMany({
include: {
paymentDestinations: true,
},
orderBy: [
{ firstName: 'asc' },
{ lastName: 'asc' },
Expand Down
19 changes: 19 additions & 0 deletions app/api/prisms/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ export async function GET(request: NextRequest) {
}

const prisms = await prisma.prism.findMany({
include: {
splits: {
include: {
paymentDestination: {
include: {
contact: {
select: {
id: true,
firstName: true,
lastName: true,
screenName: true,
email: true,
},
},
},
},
},
},
},
orderBy: {
createdAt: 'desc',
},
Expand Down
184 changes: 69 additions & 115 deletions app/components/PrismCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@

import Link from 'next/link';

const backgroundImage = "https://www.figma.com/api/mcp/asset/72b8d852-b32d-4ef9-8172-702438e2ed8e";
const eyeIcon = "https://www.figma.com/api/mcp/asset/62285819-7cca-4389-853d-b265d2b380ab";
const activeDotIcon = "https://www.figma.com/api/mcp/asset/03c77357-b591-4692-a64b-5e9f7bd4664a";
const icon1 = "https://www.figma.com/api/mcp/asset/20e39bbb-9ca1-4674-9d21-a8b96abb2207";
const icon2 = "https://www.figma.com/api/mcp/asset/f56321b8-5a73-41f8-8107-334aad1f1b50";
const icon3 = "https://www.figma.com/api/mcp/asset/c75d0b2d-d6f4-4358-864b-d042460e4e23";
const defaultAvatar = "https://www.figma.com/api/mcp/asset/75fef49e-9d96-4ddd-8b5f-d484c4c78024";

interface Contact {
id: string;
firstName: string | null;
Expand Down Expand Up @@ -50,10 +42,9 @@ export default function PrismCard({
return `${day} ${month} ${year}`;
};

const cardBackgroundImage = thumbnail || backgroundImage;
const formattedDate = formatDate(createdAt);
const formattedAmount = typeof totalDeposited === 'number'
? `$${totalDeposited.toLocaleString()}`
const formattedAmount = typeof totalDeposited === 'number'
? `$${totalDeposited.toLocaleString()}`
: totalDeposited;

const getPrimaryAccountName = (): string => {
Expand All @@ -66,129 +57,92 @@ export default function PrismCard({
return 'Unknown';
};

const getInitials = (): string => {
if (!primaryAccount) return '?';
if (primaryAccount.firstName) {
return (primaryAccount.firstName[0] + (primaryAccount.lastName?.[0] || '')).toUpperCase();
}
if (primaryAccount.screenName) return primaryAccount.screenName[0].toUpperCase();
return '?';
};
Comment on lines +60 to +67
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle email-only contacts in initials fallback.

On Line 65–66, getInitials() returns ? when only email is present, even though getPrimaryAccountName() can show that email. This creates inconsistent identity rendering for valid contact shapes.

Suggested fix
   const getInitials = (): string => {
     if (!primaryAccount) return '?';
     if (primaryAccount.firstName) {
       return (primaryAccount.firstName[0] + (primaryAccount.lastName?.[0] || '')).toUpperCase();
     }
     if (primaryAccount.screenName) return primaryAccount.screenName[0].toUpperCase();
+    if (primaryAccount.email?.trim()) return primaryAccount.email.trim()[0].toUpperCase();
     return '?';
   };
📝 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 getInitials = (): string => {
if (!primaryAccount) return '?';
if (primaryAccount.firstName) {
return (primaryAccount.firstName[0] + (primaryAccount.lastName?.[0] || '')).toUpperCase();
}
if (primaryAccount.screenName) return primaryAccount.screenName[0].toUpperCase();
return '?';
};
const getInitials = (): string => {
if (!primaryAccount) return '?';
if (primaryAccount.firstName) {
return (primaryAccount.firstName[0] + (primaryAccount.lastName?.[0] || '')).toUpperCase();
}
if (primaryAccount.screenName) return primaryAccount.screenName[0].toUpperCase();
if (primaryAccount.email?.trim()) return primaryAccount.email.trim()[0].toUpperCase();
return '?';
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/PrismCard.tsx` around lines 60 - 67, getInitials currently
returns '?' when primaryAccount only has an email, causing inconsistent fallback
with getPrimaryAccountName; update getInitials to check primaryAccount.email and
use the first character of the email's local-part (text before '@') as the
initial (uppercased) when firstName and screenName are absent. Ensure you
reference the primaryAccount.email value, safely handle missing/empty email, and
keep existing behaviors for firstName and screenName in the getInitials
function.


return (
<Link href={`/prisms/${id}`} className="block">
<div className="relative w-full max-w-[313px] min-h-[320px] h-[320px] rounded-[12px] overflow-hidden border border-white/25 bg-[#0a0f0e] shadow-[0_0_0_1px_rgba(255,255,255,0.12)] hover:border-white/40 transition-colors cursor-pointer">
{/* Background Image */}
<img
src={cardBackgroundImage}
alt={name}
className="absolute inset-0 w-full h-full object-cover object-center pointer-events-none rounded-[12px]"
/>

<div className="relative w-full max-w-[313px] min-h-[320px] h-[320px] rounded-[12px] overflow-hidden border-2 border-white bg-white shadow-lg cursor-pointer hover:shadow-xl transition-shadow">
{/* Top Section */}
<div className="absolute top-0 left-0 right-0 z-10">
<div className="backdrop-blur-[11.6px] bg-black/60 rounded-tl-[12px] rounded-tr-[12px] rounded-br-[12px] p-2 flex flex-col gap-4 w-full">
{/* Date Row */}
{formattedDate && (
<div className="flex items-center gap-[3px] px-1">
<div className="w-[14px] h-[14px] shrink-0 relative">
<div className="absolute inset-[21.61%_12.35%_21.64%_12.34%]">
<div className="absolute inset-[-7.34%_-5.53%]">
<img src={eyeIcon} alt="Eye icon" className="w-full h-full object-contain" />
</div>
</div>
</div>
<p className="text-white text-[11px] font-medium leading-[14px] whitespace-nowrap">
{formattedDate}
</p>
</div>
)}
<div className="p-4 flex flex-col gap-3 border-b border-gray-200">
{/* Date Row */}
{formattedDate && (
<div className="flex items-center gap-1.5">
<svg className="w-3.5 h-3.5 text-gray-500 shrink-0" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" />
</svg>
<p className="text-gray-500 text-[11px] font-medium leading-[14px] whitespace-nowrap">
{formattedDate}
</p>
</div>
)}

{/* Title and Status Row */}
<div className="flex items-center gap-3 h-[14px] px-1">
<div className="flex items-center justify-center">
<p className="text-white text-[18px] font-medium leading-[14px] text-center">
{name}
{/* Title and Status Row */}
<div className="flex items-center gap-3">
<p className="text-black text-[18px] font-semibold leading-tight">
{name}
</p>
{active && (
<div className="flex items-center gap-1 bg-emerald-100 px-2 py-0.5 rounded-full">
<div className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
<p className="text-emerald-700 text-[11px] font-medium leading-[14px]">
Active
</p>
</div>
{active && (
<div className="flex items-center gap-1">
<div className="w-1 h-1 shrink-0">
<img src={activeDotIcon} alt="Active" className="w-full h-full object-contain" />
</div>
<p className="text-[#00ffc8] text-[11px] font-medium leading-[14px]">
Active
</p>
</div>
)}
</div>
)}
</div>
</div>

{/* Middle Section - Amount */}
<div className="p-4 flex-1">
<p className="text-black text-[32px] font-bold leading-tight">
{formattedAmount}
</p>
<p className="text-gray-500 text-[10px] font-medium mt-1">
Total deposited
</p>
</div>

{/* Bottom Section */}
<div className="absolute bottom-0 left-0 right-0 z-10 bg-gradient-to-t from-black/90 from-[17.903%] to-[rgba(102,102,102,0)] to-[252.28%] backdrop-blur-[10.55px] rounded-[12px] p-3 flex flex-col gap-4">
{/* Amount and Icons Row */}
<div className="flex items-center justify-between w-full">
<div className="flex flex-col gap-2 items-start justify-center">
<div className="flex items-center justify-center">
<p className="text-white text-[32px] font-bold leading-[14px]">
{formattedAmount}
</p>
</div>
<div className="flex items-center justify-center">
<p className="text-[#838383] text-[10px] font-medium leading-[14px]">
Total deposited
</p>
</div>
<div className="absolute bottom-0 left-0 right-0 p-4 bg-gray-50 border-t border-gray-200 rounded-b-[12px] flex flex-col gap-3">
{/* Badges */}
<div className="flex items-center gap-2">
<div className="border border-gray-300 flex items-center justify-center px-3 py-1.5 rounded-full shrink-0">
<p className="text-gray-700 text-[11px] font-medium text-center">
{memberCount} {memberCount === 1 ? 'Member' : 'Members'}
</p>
</div>
<div className="flex gap-2 h-[41px] items-center">
<div className="h-[27px] w-[25.951px] shrink-0">
<img src={icon1} alt="" className="w-full h-full object-contain" />
</div>
<div className="h-[27.898px] w-[27.881px] shrink-0">
<img src={icon2} alt="" className="w-full h-full object-contain" />
</div>
<div className="flex h-[25.358px] items-center justify-center w-[16.664px]">
<div className="flex-none rotate-90">
<div className="h-[16.664px] w-[25.358px]">
<img src={icon3} alt="" className="w-full h-full object-contain" />
</div>
</div>
{category && (
<div className="border border-gray-300 flex items-center justify-center px-3 py-1.5 rounded-full shrink-0">
<p className="text-gray-700 text-[11px] font-medium text-center">
{category}
</p>
</div>
</div>
)}
</div>

{/* Bottom Row: Badges and Primary Account */}
<div className="flex items-center justify-between w-full">
{/* Badges */}
<div className="flex items-center gap-[7px]">
<div className="border-[0.5px] border-white flex items-center justify-center p-[10px] rounded-full shrink-0">
<p className="text-white text-[11px] font-medium leading-[11.2px] text-center">
{memberCount} Members
</p>
</div>
{category && (
<div className="border-[0.5px] border-white flex items-center justify-center p-[10px] rounded-full shrink-0">
<p className="text-white text-[11px] font-medium leading-[11.2px] text-center">
{category}
</p>
</div>
)}
{/* Primary Account */}
<div className="flex gap-2 items-center">
<div className="rounded-full shrink-0 w-[28px] h-[28px] bg-emerald-500 flex items-center justify-center">
<span className="text-white text-[11px] font-bold">{getInitials()}</span>
</div>

{/* Primary Account */}
<div className="flex gap-2 items-center p-1 rounded-[12px]">
<div className="relative rounded-full shrink-0 w-[28px] h-[28px] overflow-clip">
<img
src={defaultAvatar}
alt={getPrimaryAccountName()}
className="w-full h-full object-cover"
/>
</div>
<div className="flex flex-col gap-2 items-start">
<p className="text-white text-[11px] font-medium leading-[14px] whitespace-nowrap">
{getPrimaryAccountName()}
</p>
<p className="text-white/50 text-[11px] font-medium leading-[14px] whitespace-nowrap">
Primary Account
</p>
</div>
<div className="flex flex-col">
<p className="text-black text-[11px] font-medium leading-[14px] whitespace-nowrap">
{getPrimaryAccountName()}
</p>
<p className="text-gray-400 text-[11px] font-medium leading-[14px] whitespace-nowrap">
Primary Account
</p>
</div>
</div>
</div>
</div>
</Link>
);
}

Loading