Skip to content

Commit 00a8356

Browse files
authored
Merge pull request #577 from codesailor4/feat/AgencyProfiles
Feat/agency profiles
2 parents 98d70c8 + 76436f5 commit 00a8356

6 files changed

Lines changed: 1281 additions & 9 deletions

File tree

app/agencies/[id]/page.tsx

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import NextImage from 'next/image';
5+
import Link from 'next/link';
6+
import { useParams } from 'next/navigation';
7+
import Button from '@/app/components/ui/Button';
8+
import { Skeleton } from '@/app/components/ui/Skeleton';
9+
import {
10+
MapPin,
11+
Star,
12+
ShieldCheck,
13+
Briefcase,
14+
MessageSquare,
15+
Image as ImageIcon,
16+
Mail,
17+
Users,
18+
} from 'lucide-react';
19+
20+
type TabKey = 'portfolio' | 'teams' | 'services' | 'reviews';
21+
22+
export default function AgencyProfilePage() {
23+
const params = useParams();
24+
const agencyId = params.id as string;
25+
const [activeTab, setActiveTab] = useState<TabKey>('portfolio');
26+
27+
// Mock data for demonstration
28+
const agency = {
29+
id: agencyId,
30+
name: 'Creative Minds Agency',
31+
tagline: 'A team of talented designers and developers',
32+
verified: true,
33+
rating: 4.8,
34+
reviewCount: 24,
35+
walletAddress: 'GABC...123',
36+
bio: 'We are a full-service creative agency specializing in design, development, and digital marketing. Our team of 15+ professionals delivers high-quality work for clients worldwide.',
37+
avatar: '',
38+
coverImage: '',
39+
teams: [
40+
{
41+
id: 'team-1',
42+
name: 'Design Team',
43+
description: 'UI/UX and graphic design specialists',
44+
members: [
45+
{ id: 'u1', name: 'Sarah Johnson', role: 'Lead Designer', avatar: '', rating: 4.9 },
46+
{ id: 'u2', name: 'Mike Chen', role: 'UI Designer', avatar: '', rating: 4.7 },
47+
{ id: 'u3', name: 'Emma Wilson', role: 'Illustrator', avatar: '', rating: 4.8 },
48+
],
49+
},
50+
{
51+
id: 'team-2',
52+
name: 'Development Team',
53+
description: 'Full-stack developers and engineers',
54+
members: [
55+
{ id: 'u4', name: 'David Lee', role: 'Tech Lead', avatar: '', rating: 4.9 },
56+
{ id: 'u5', name: 'Lisa Wang', role: 'Frontend Developer', avatar: '', rating: 4.6 },
57+
],
58+
},
59+
],
60+
portfoliosByTeam: {
61+
'team-1': [
62+
{ id: 'p1', title: 'Brand Identity Package', coverImage: '', items: 12, description: 'Complete brand redesign for a tech startup', teamName: 'Design Team' },
63+
{ id: 'p2', title: 'Mobile App UI Design', coverImage: '', items: 8, description: 'UI/UX design for fitness tracking app', teamName: 'Design Team' },
64+
],
65+
'team-2': [
66+
{ id: 'p3', title: 'E-commerce Platform', coverImage: '', items: 15, description: 'Full-stack e-commerce solution', teamName: 'Development Team' },
67+
],
68+
},
69+
totalEarnings: 125000,
70+
teamEarnings: {
71+
'team-1': 75000,
72+
'team-2': 50000,
73+
},
74+
};
75+
76+
if (!agency) {
77+
return (
78+
<div className="min-h-screen flex items-center justify-center bg-neutral-50 dark:bg-neutral-950">
79+
<div className="text-center">
80+
<div className="w-20 h-20 bg-neutral-200 dark:bg-neutral-800 rounded-full flex items-center justify-center mx-auto mb-4">
81+
<ImageIcon className="w-10 h-10 text-neutral-400" />
82+
</div>
83+
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white mb-2">
84+
Agency Not Found
85+
</h1>
86+
<p className="text-neutral-500 dark:text-neutral-400">
87+
We couldn't find the agency you're looking for.
88+
</p>
89+
</div>
90+
</div>
91+
);
92+
}
93+
94+
const tabs: { key: TabKey; label: string; icon: React.ReactNode }[] = [
95+
{ key: 'portfolio', label: 'Portfolio', icon: <Briefcase className="w-4 h-4" /> },
96+
{ key: 'teams', label: 'Teams', icon: <Users className="w-4 h-4" /> },
97+
{ key: 'services', label: 'Services', icon: <ImageIcon className="w-4 h-4" /> },
98+
{ key: 'reviews', label: 'Reviews', icon: <MessageSquare className="w-4 h-4" /> },
99+
];
100+
101+
const allPortfolios = Object.values(agency.portfoliosByTeam).flat();
102+
103+
return (
104+
<div className="min-h-screen bg-neutral-50 dark:bg-neutral-950">
105+
{/* Cover Image */}
106+
<div className="relative h-56 sm:h-72 bg-gradient-to-r from-primary-600 via-primary-700 to-secondary-600">
107+
{agency.coverImage && (
108+
<img
109+
src={agency.coverImage}
110+
alt={`${agency.name} cover`}
111+
className="w-full h-full object-cover"
112+
/>
113+
)}
114+
<div className="absolute inset-0 bg-gradient-to-t from-black/40 to-transparent" />
115+
</div>
116+
117+
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 -mt-20 relative z-10">
118+
{/* Profile Header */}
119+
<div className="flex flex-col sm:flex-row sm:items-end gap-4 sm:gap-6">
120+
{/* Avatar */}
121+
<div className="relative w-32 h-32 sm:w-36 sm:h-36 rounded-full border-4 border-white dark:border-neutral-900 overflow-hidden bg-neutral-200 dark:bg-neutral-700 shadow-lg flex-shrink-0">
122+
{agency.avatar ? (
123+
<NextImage
124+
src={agency.avatar}
125+
alt={agency.name}
126+
fill
127+
className="object-cover"
128+
sizes="144px"
129+
/>
130+
) : (
131+
<div className="w-full h-full flex items-center justify-center text-neutral-400">
132+
<Building2 className="w-12 h-12" />
133+
</div>
134+
)}
135+
</div>
136+
137+
{/* Name & Info */}
138+
<div className="flex-1 pb-2">
139+
<div className="flex items-center gap-3 flex-wrap">
140+
<h1 className="text-2xl sm:text-3xl font-bold text-neutral-900 dark:text-white">
141+
{agency.name}
142+
<span className="ml-2 text-sm px-2 py-0.5 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400 rounded-full">
143+
Agency
144+
</span>
145+
</h1>
146+
{agency.verified && (
147+
<span className="inline-flex items-center gap-1 px-2 py-0.5 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 text-xs font-medium rounded-full">
148+
<ShieldCheck className="w-3.5 h-3.5" />
149+
Verified
150+
</span>
151+
)}
152+
</div>
153+
{agency.tagline && (
154+
<p className="text-neutral-600 dark:text-neutral-400 mt-1 text-lg">
155+
{agency.tagline}
156+
</p>
157+
)}
158+
<div className="flex items-center gap-4 mt-2 flex-wrap">
159+
<div className="flex items-center gap-1 text-amber-500">
160+
<Star className="w-4 h-4 fill-current" />
161+
<span className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
162+
{agency.rating?.toFixed(1) || '—'}
163+
</span>
164+
<span className="text-sm text-neutral-400">
165+
({agency.reviewCount || 0} reviews)
166+
</span>
167+
</div>
168+
<span className="text-sm text-neutral-400 flex items-center gap-1">
169+
<Users className="w-3.5 h-3.5" />
170+
{agency.teams.reduce((acc, team) => acc + team.members.length, 0)} team members
171+
</span>
172+
{agency.walletAddress && (
173+
<span className="text-sm text-neutral-400 flex items-center gap-1">
174+
<MapPin className="w-3.5 h-3.5" />
175+
Stellar Wallet Connected
176+
</span>
177+
)}
178+
</div>
179+
</div>
180+
181+
{/* CTA Buttons */}
182+
<div className="flex gap-2 sm:pb-2 mt-3 sm:mt-0">
183+
<Button variant="primary" size="md">
184+
<Mail className="w-4 h-4 mr-2" />
185+
Contact Agency
186+
</Button>
187+
<Link
188+
href={`/commissions/new?agencyId=${agency.id}&agencyName=${encodeURIComponent(agency.name)}`}
189+
className="inline-flex items-center justify-center rounded-xl border-2 border-primary-700 px-4 py-2 text-sm font-medium text-primary-700 transition-colors hover:bg-primary-50 dark:hover:bg-primary-900/20"
190+
>
191+
Start a Project
192+
</Link>
193+
</div>
194+
</div>
195+
196+
{/* Teams Count Badge */}
197+
<div className="flex flex-wrap gap-2 mt-5">
198+
<span className="px-3 py-1 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400 text-xs font-medium rounded-full">
199+
{agency.teams.length} teams
200+
</span>
201+
{agency.teams.map((team) => (
202+
<span
203+
key={team.id}
204+
className="px-3 py-1 bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-300 text-xs font-medium rounded-full"
205+
>
206+
{team.name}: {team.members.length} members
207+
</span>
208+
))}
209+
</div>
210+
211+
{/* Bio */}
212+
{agency.bio && (
213+
<p className="mt-4 text-neutral-600 dark:text-neutral-400 leading-relaxed max-w-3xl">
214+
{agency.bio}
215+
</p>
216+
)}
217+
218+
{/* Tabs Navigation */}
219+
<div className="mt-8 border-b border-neutral-200 dark:border-neutral-800">
220+
<nav className="flex gap-0 -mb-px" role="tablist">
221+
{tabs.map((tab) => (
222+
<button
223+
key={tab.key}
224+
role="tab"
225+
aria-selected={activeTab === tab.key}
226+
onClick={() => setActiveTab(tab.key)}
227+
className={`inline-flex items-center gap-2 px-5 py-3 text-sm font-medium border-b-2 transition-colors ${activeTab === tab.key
228+
? 'border-primary-600 text-primary-600 dark:text-primary-400'
229+
: 'border-transparent text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300 hover:border-neutral-300 dark:hover:border-neutral-600'
230+
}`}
231+
>
232+
{tab.icon}
233+
{tab.label}
234+
</button>
235+
))}
236+
</nav>
237+
</div>
238+
239+
{/* Tab Content */}
240+
<div className="py-8">
241+
{activeTab === 'portfolio' && (
242+
<PortfolioTab portfoliosByTeam={agency.portfoliosByTeam} />
243+
)}
244+
{activeTab === 'teams' && (
245+
<TeamsTab teams={agency.teams} teamEarnings={agency.teamEarnings} />
246+
)}
247+
{activeTab === 'services' && (
248+
<div className="text-center py-16">
249+
<FolderOpen className="w-16 h-16 text-neutral-400 mx-auto mb-4" />
250+
<h3 className="text-lg font-semibold text-neutral-700 dark:text-neutral-300 mb-1">
251+
Services coming soon
252+
</h3>
253+
</div>
254+
)}
255+
{activeTab === 'reviews' && (
256+
<div className="text-center py-16">
257+
<MessageSquare className="w-16 h-16 text-neutral-400 mx-auto mb-4" />
258+
<h3 className="text-lg font-semibold text-neutral-700 dark:text-neutral-300 mb-1">
259+
Reviews & Ratings
260+
</h3>
261+
<p className="text-neutral-500">Team ratings and client reviews will appear here</p>
262+
</div>
263+
)}
264+
</div>
265+
</div>
266+
</div>
267+
);
268+
}
269+
270+
function PortfolioTab({ portfoliosByTeam }: { portfoliosByTeam: any }) {
271+
return (
272+
<div className="space-y-12">
273+
{Object.entries(portfoliosByTeam).map(([teamId, portfolios]: [string, any[]]) => (
274+
<div key={teamId} className="border-b border-neutral-200 dark:border-neutral-800 pb-10 last:border-0">
275+
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-6 flex items-center gap-2">
276+
<Briefcase className="w-5 h-5 text-primary-500" />
277+
{portfolios[0]?.teamName || 'Team'} Portfolio
278+
</h3>
279+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
280+
{portfolios.map((portfolio: any) => (
281+
<Link
282+
key={portfolio.id}
283+
href={`/portfolios/${portfolio.id}`}
284+
className="group bg-white dark:bg-neutral-900 rounded-2xl overflow-hidden border border-neutral-200 dark:border-neutral-800 hover:border-primary-300 dark:hover:border-primary-700 hover:shadow-lg transition-all duration-300"
285+
>
286+
<div className="relative h-48 bg-neutral-100 dark:bg-neutral-800 overflow-hidden">
287+
{portfolio.coverImage ? (
288+
<img
289+
src={portfolio.coverImage}
290+
alt={portfolio.title}
291+
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
292+
/>
293+
) : (
294+
<div className="w-full h-full flex items-center justify-center">
295+
<Image className="w-12 h-12 text-neutral-300 dark:text-neutral-600" />
296+
</div>
297+
)}
298+
<div className="absolute top-3 right-3">
299+
<span className="px-2.5 py-1 bg-white/90 dark:bg-neutral-900/90 backdrop-blur-sm text-xs font-medium text-neutral-600 dark:text-neutral-300 rounded-full">
300+
{portfolio.items?.length || 0} items
301+
</span>
302+
</div>
303+
</div>
304+
<div className="p-4">
305+
<h3 className="font-semibold text-neutral-900 dark:text-white group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors line-clamp-1">
306+
{portfolio.title}
307+
</h3>
308+
<p className="text-sm text-neutral-500 dark:text-neutral-400 mt-1 line-clamp-2">
309+
{portfolio.description}
310+
</p>
311+
</div>
312+
</Link>
313+
))}
314+
</div>
315+
</div>
316+
))}
317+
</div>
318+
);
319+
}
320+
321+
function TeamsTab({ teams, teamEarnings }: { teams: any[], teamEarnings: any }) {
322+
return (
323+
<div className="space-y-8">
324+
{teams.map((team) => (
325+
<div key={team.id} className="bg-white dark:bg-neutral-900 rounded-2xl p-6 border border-neutral-200 dark:border-neutral-800">
326+
<div className="flex items-start justify-between mb-6">
327+
<div>
328+
<h3 className="text-xl font-bold text-neutral-900 dark:text-white">{team.name}</h3>
329+
<p className="text-neutral-500 dark:text-neutral-400 mt-1">{team.description}</p>
330+
</div>
331+
<div className="text-right">
332+
<p className="text-sm text-neutral-500 dark:text-neutral-400">Team Earnings</p>
333+
<p className="text-2xl font-bold text-emerald-600">${teamEarnings[team.id]?.toLocaleString() || 0}</p>
334+
</div>
335+
</div>
336+
337+
<h4 className="text-sm font-semibold text-neutral-700 dark:text-neutral-300 mb-4">Team Members ({team.members.length})</h4>
338+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
339+
{team.members.map((member: any) => (
340+
<Link
341+
key={member.id}
342+
href={`/artists/${member.id}`}
343+
className="flex items-center gap-4 p-4 bg-neutral-50 dark:bg-neutral-800 rounded-xl hover:bg-neutral-100 dark:hover:bg-neutral-750 transition-colors"
344+
>
345+
<div className="w-12 h-12 rounded-full bg-neutral-200 dark:bg-neutral-700 overflow-hidden flex-shrink-0">
346+
{member.avatar ? (
347+
<img src={member.avatar} alt={member.name} className="w-full h-full object-cover" />
348+
) : (
349+
<div className="w-full h-full flex items-center justify-center">
350+
<User className="w-6 h-6 text-neutral-400" />
351+
</div>
352+
)}
353+
</div>
354+
<div className="min-w-0">
355+
<p className="font-medium text-neutral-900 dark:text-white truncate">{member.name}</p>
356+
<p className="text-sm text-neutral-500 dark:text-neutral-400 truncate">{member.role}</p>
357+
<div className="flex items-center gap-1 mt-1">
358+
<Star className="w-3 h-3 fill-current text-amber-500" />
359+
<span className="text-xs text-neutral-600 dark:text-neutral-400">{member.rating}</span>
360+
</div>
361+
</div>
362+
</Link>
363+
))}
364+
</div>
365+
</div>
366+
))}
367+
</div>
368+
);
369+
}

0 commit comments

Comments
 (0)