@svadmin/ui 0.38.7 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.css +0 -4
- package/dist/app.theme.css +0 -4
- package/dist/assets/nft/nft-cover-01.jpg +0 -0
- package/dist/assets/nft/nft-cover-02.jpg +0 -0
- package/dist/assets/nft/nft-cover-03.jpg +0 -0
- package/dist/assets/nft/nft-cover-04.jpg +0 -0
- package/dist/assets/nft/nft-cover-05.jpg +0 -0
- package/dist/assets/nft/nft-cover-06.jpg +0 -0
- package/dist/assets/nft/nft-cover-07.jpg +0 -0
- package/dist/assets/nft/nft-cover-08.jpg +0 -0
- package/dist/assets/nft/nft-cover-09.jpg +0 -0
- package/dist/components/AdminApp.svelte +29 -12
- package/dist/components/ApiSettings.svelte +86 -1
- package/dist/components/AutoTable.svelte +40 -13
- package/dist/components/Breadcrumbs.svelte +39 -6
- package/dist/components/Breadcrumbs.svelte.d.ts +5 -1
- package/dist/components/ChatDialog.svelte +4 -2
- package/dist/components/ChatDialog.svelte.d.ts +2 -0
- package/dist/components/DevTools.svelte +3 -1
- package/dist/components/DevTools.svelte.d.ts +4 -1
- package/dist/components/ForgotPasswordPage.svelte +5 -8
- package/dist/components/Header.svelte +5 -3
- package/dist/components/Header.svelte.d.ts +2 -0
- package/dist/components/Layout.svelte +10 -1
- package/dist/components/LoginPage.svelte +5 -8
- package/dist/components/RegisterPage.svelte +5 -8
- package/dist/components/SettingsPage.svelte +14 -2
- package/dist/components/Sidebar.svelte +10 -4
- package/dist/components/SvadminLogo.svelte +16 -0
- package/dist/components/SvadminLogo.svelte.d.ts +7 -0
- package/dist/components/SystemPageShell.svelte +34 -0
- package/dist/components/SystemPageShell.svelte.d.ts +8 -0
- package/dist/components/UpdatePasswordPage.svelte +5 -8
- package/dist/components/account/CompanyProfilePage.svelte +56 -1
- package/dist/components/account/ImportMembersPage.svelte +31 -0
- package/dist/components/account/SettingsEnterprisePage.svelte +172 -19
- package/dist/components/account/SettingsPlainPage.svelte +121 -17
- package/dist/components/account/SettingsSidebarPage.svelte +282 -70
- package/dist/components/account/TeamMembersPage.svelte +95 -4
- package/dist/components/account/UserProfilePage.svelte +206 -0
- package/dist/components/account/UserProfilePage.svelte.d.ts +4 -0
- package/dist/components/network/TeamCrewTablePage.svelte +37 -5
- package/dist/components/network/UserCardsNFTPage.svelte +26 -16
- package/dist/components/profile/ActivityTimeline.svelte +24 -2
- package/dist/components/profile/ActivityTimeline.svelte.d.ts +1 -0
- package/dist/components/profile/ProfileVariantSections.svelte +452 -0
- package/dist/components/profile/ProfileVariantSections.svelte.d.ts +8 -0
- package/dist/components/profile/ProjectsGrid.svelte +38 -8
- package/dist/components/profile/ProjectsGrid.svelte.d.ts +1 -0
- package/dist/components/profile/PublicProfilePage.svelte +47 -26
- package/dist/components/profile/PublicProfilePage.svelte.d.ts +1 -0
- package/dist/components/profile/TeamsShowcase.svelte +31 -12
- package/dist/components/profile/TeamsShowcase.svelte.d.ts +1 -0
- package/dist/components/ui/alert/alert.svelte.d.ts +14 -14
- package/dist/components/ui/avatar/avatar.svelte.d.ts +14 -14
- package/dist/components/ui/badge/badge.svelte.d.ts +20 -20
- package/dist/components/ui/button/button.svelte.d.ts +44 -44
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/router-state.svelte.js +4 -0
- package/package.json +15 -15
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
3
|
+
import * as Card from '../ui/card/index.js';
|
|
4
|
+
import * as Table from '../ui/table/index.js';
|
|
5
|
+
import { Badge } from '../ui/badge/index.js';
|
|
6
|
+
import { Button } from '../ui/button/index.js';
|
|
7
|
+
import { Progress } from '../ui/progress/index.js';
|
|
8
|
+
import ActivityTimeline from './ActivityTimeline.svelte';
|
|
9
|
+
import {
|
|
10
|
+
Briefcase, MapPin, Users, TrendingUp, Gamepad2, Trophy, Play,
|
|
11
|
+
FileText, Image, FileArchive, Star, Award, Medal, Crown, Building2, Globe,
|
|
12
|
+
} from '@lucide/svelte';
|
|
13
|
+
import type { Component } from 'svelte';
|
|
14
|
+
|
|
15
|
+
const i18n = useTranslation();
|
|
16
|
+
|
|
17
|
+
type ProfileVariant = 'default' | 'company' | 'gamer';
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
variant?: ProfileVariant;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let { variant = 'default' }: Props = $props();
|
|
24
|
+
|
|
25
|
+
const initials = (name: string) => name.split(' ').map((n) => n[0]).join('').toUpperCase().slice(0, 2);
|
|
26
|
+
|
|
27
|
+
// Default variant demo data
|
|
28
|
+
const workExperience = [
|
|
29
|
+
{ role: 'Senior Frontend Engineer', company: 'Nebula Labs', period: '2023 - ', current: true },
|
|
30
|
+
{ role: 'Frontend Engineer', company: 'Craftworks Studio', period: '2020 - 2023', current: false },
|
|
31
|
+
{ role: 'UI Developer', company: 'PixelForge', period: '2018 - 2020', current: false },
|
|
32
|
+
];
|
|
33
|
+
const skills = ['TypeScript', 'Svelte', 'Node.js', 'GraphQL', 'Design Systems', 'Accessibility'];
|
|
34
|
+
const badges: { name: string; Icon: Component; tone: string }[] = [
|
|
35
|
+
{ name: 'Top Contributor', Icon: Medal, tone: 'bg-amber-500/10 text-amber-600' },
|
|
36
|
+
{ name: 'Early Adopter', Icon: Star, tone: 'bg-blue-500/10 text-blue-600' },
|
|
37
|
+
{ name: 'Community Mentor', Icon: Award, tone: 'bg-violet-500/10 text-violet-600' },
|
|
38
|
+
{ name: 'Launch Partner', Icon: Crown, tone: 'bg-emerald-500/10 text-emerald-600' },
|
|
39
|
+
];
|
|
40
|
+
const recentUploads: { name: string; size: string; Icon: Component; tone: string }[] = [
|
|
41
|
+
{ name: 'dashboard-spec.fig', size: '4.2 MB', Icon: Image, tone: 'bg-pink-500/10 text-pink-600' },
|
|
42
|
+
{ name: 'release-notes.md', size: '18 KB', Icon: FileText, tone: 'bg-blue-500/10 text-blue-600' },
|
|
43
|
+
{ name: 'assets-bundle.zip', size: '24 MB', Icon: FileArchive, tone: 'bg-amber-500/10 text-amber-600' },
|
|
44
|
+
];
|
|
45
|
+
const contributors = ['Mia Torres', 'Chen Wei', 'Ava Novak', 'Liam Ortiz', 'Sofia Petrova', '+8'];
|
|
46
|
+
|
|
47
|
+
// Company variant demo data
|
|
48
|
+
const highlights = [
|
|
49
|
+
{ label: 'Total revenue', value: '$4.8M', delta: '+18.2%' },
|
|
50
|
+
{ label: 'Active customers', value: '2,340', delta: '+9.4%' },
|
|
51
|
+
{ label: 'Open positions', value: '17', delta: '+3' },
|
|
52
|
+
];
|
|
53
|
+
const openJobs = [
|
|
54
|
+
{ title: 'Senior Product Designer', location: 'Remote', type: 'Full-time' },
|
|
55
|
+
{ title: 'Staff Engineer, Platform', location: 'San Francisco', type: 'Full-time' },
|
|
56
|
+
{ title: 'Developer Advocate', location: 'Berlin', type: 'Contract' },
|
|
57
|
+
];
|
|
58
|
+
const locations = ['San Francisco, US', 'Berlin, DE', 'Singapore, SG'];
|
|
59
|
+
const companyMembers = ['Elena Marsh', 'David Kim', 'Priya Nair', 'Oscar Lund', 'Hana Sato', '+27'];
|
|
60
|
+
const investments = [
|
|
61
|
+
{ round: 'Series B', amount: '$24M', date: '2025-11' },
|
|
62
|
+
{ round: 'Series A', amount: '$9M', date: '2023-06' },
|
|
63
|
+
{ round: 'Seed', amount: '$1.8M', date: '2021-03' },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// Gamer variant demo data
|
|
67
|
+
const favoriteGames = [
|
|
68
|
+
{ name: 'Starfall Odyssey', hours: '412h', rank: 'Diamond II' },
|
|
69
|
+
{ name: 'Neon Drift', hours: '268h', rank: 'Master' },
|
|
70
|
+
{ name: 'Iron Bastion', hours: '190h', rank: 'Platinum I' },
|
|
71
|
+
];
|
|
72
|
+
const tournaments = [
|
|
73
|
+
{ name: 'Winter Clash 2026', result: 'Top 8', prize: '$1,200' },
|
|
74
|
+
{ name: 'City Invitational', result: '1st', prize: '$3,500' },
|
|
75
|
+
{ name: 'Open Qualifier #14', result: '2nd', prize: '$800' },
|
|
76
|
+
];
|
|
77
|
+
const gamerNetwork = ['VortexQueen', 'NoScopeNina', 'PixelPunk', 'GrimReaperX', '+42'];
|
|
78
|
+
const gamerActivities = [
|
|
79
|
+
{ id: 'g1', type: 'posted' as const, user: 'ShadowFox', content: 'Just hit Diamond II in Starfall Odyssey ranked. Clutch final round!', timestamp: '1 hour ago' },
|
|
80
|
+
{ id: 'g2', type: 'created' as const, user: 'ShadowFox', target: 'Winter Clash 2026 highlights', timestamp: '6 hours ago' },
|
|
81
|
+
{ id: 'g3', type: 'joined' as const, user: 'ShadowFox', target: 'City Invitational', timestamp: '2 days ago' },
|
|
82
|
+
{ id: 'g4', type: 'commented' as const, user: 'ShadowFox', target: "VortexQueen's stream", content: 'That flank was insane, gg!', timestamp: '3 days ago' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// Shared projects table demo data
|
|
86
|
+
const projectRows = [
|
|
87
|
+
{ name: 'Dashboard Redesign', progress: 72, people: ['AC', 'SK', 'MJ'], due: '2026-09-15' },
|
|
88
|
+
{ name: 'API Gateway v2', progress: 45, people: ['LW', 'TB'], due: '2026-10-02' },
|
|
89
|
+
{ name: 'Mobile App', progress: 100, people: ['ED', 'AC', 'MK', 'RS'], due: '2026-06-30' },
|
|
90
|
+
{ name: 'Data Pipeline', progress: 20, people: ['NK'], due: '2026-12-01' },
|
|
91
|
+
];
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
{#snippet projectsTable()}
|
|
95
|
+
<Card.Card class="border-border/60">
|
|
96
|
+
<Card.CardHeader class="pb-3">
|
|
97
|
+
<div class="flex items-center justify-between">
|
|
98
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.projects')}</Card.CardTitle>
|
|
99
|
+
<Button variant="ghost" size="sm">{i18n.t('profileSections.viewAll')}</Button>
|
|
100
|
+
</div>
|
|
101
|
+
</Card.CardHeader>
|
|
102
|
+
<Card.CardContent class="p-0">
|
|
103
|
+
<Table.Root>
|
|
104
|
+
<Table.Header>
|
|
105
|
+
<Table.Row>
|
|
106
|
+
<Table.Head>{i18n.t('profileSections.projectName')}</Table.Head>
|
|
107
|
+
<Table.Head class="w-40">{i18n.t('profileSections.progress')}</Table.Head>
|
|
108
|
+
<Table.Head>{i18n.t('profileSections.people')}</Table.Head>
|
|
109
|
+
<Table.Head class="text-right">{i18n.t('profileSections.dueDate')}</Table.Head>
|
|
110
|
+
</Table.Row>
|
|
111
|
+
</Table.Header>
|
|
112
|
+
<Table.Body>
|
|
113
|
+
{#each projectRows as row (row.name)}
|
|
114
|
+
<Table.Row>
|
|
115
|
+
<Table.Cell class="font-medium">{row.name}</Table.Cell>
|
|
116
|
+
<Table.Cell>
|
|
117
|
+
<div class="flex items-center gap-2">
|
|
118
|
+
<Progress value={row.progress} class="h-1.5 flex-1" />
|
|
119
|
+
<span class="w-9 text-right text-xs text-muted-foreground">{row.progress}%</span>
|
|
120
|
+
</div>
|
|
121
|
+
</Table.Cell>
|
|
122
|
+
<Table.Cell>
|
|
123
|
+
<div class="flex -space-x-1.5">
|
|
124
|
+
{#each row.people as person (person)}
|
|
125
|
+
<div class="flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[9px] font-semibold text-muted-foreground ring-2 ring-card">{person}</div>
|
|
126
|
+
{/each}
|
|
127
|
+
</div>
|
|
128
|
+
</Table.Cell>
|
|
129
|
+
<Table.Cell class="text-right text-muted-foreground">{row.due}</Table.Cell>
|
|
130
|
+
</Table.Row>
|
|
131
|
+
{/each}
|
|
132
|
+
</Table.Body>
|
|
133
|
+
</Table.Root>
|
|
134
|
+
</Card.CardContent>
|
|
135
|
+
</Card.Card>
|
|
136
|
+
{/snippet}
|
|
137
|
+
|
|
138
|
+
{#snippet badgesCard()}
|
|
139
|
+
<Card.Card class="border-border/60">
|
|
140
|
+
<Card.CardHeader class="pb-3">
|
|
141
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.communityBadges')}</Card.CardTitle>
|
|
142
|
+
</Card.CardHeader>
|
|
143
|
+
<Card.CardContent>
|
|
144
|
+
<div class="grid grid-cols-2 gap-2">
|
|
145
|
+
{#each badges as badge (badge.name)}
|
|
146
|
+
<div class="flex items-center gap-2 rounded-lg border border-border/60 p-2.5">
|
|
147
|
+
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg {badge.tone}">
|
|
148
|
+
<badge.Icon class="h-4 w-4" />
|
|
149
|
+
</div>
|
|
150
|
+
<span class="text-xs font-medium text-foreground">{badge.name}</span>
|
|
151
|
+
</div>
|
|
152
|
+
{/each}
|
|
153
|
+
</div>
|
|
154
|
+
</Card.CardContent>
|
|
155
|
+
</Card.Card>
|
|
156
|
+
{/snippet}
|
|
157
|
+
|
|
158
|
+
{#if variant === 'default'}
|
|
159
|
+
<div class="grid gap-4 lg:grid-cols-3">
|
|
160
|
+
<div class="space-y-4">
|
|
161
|
+
<Card.Card class="border-border/60">
|
|
162
|
+
<Card.CardHeader class="pb-3">
|
|
163
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.about')}</Card.CardTitle>
|
|
164
|
+
</Card.CardHeader>
|
|
165
|
+
<Card.CardContent class="space-y-2.5 text-sm">
|
|
166
|
+
<p class="text-muted-foreground">Full-stack developer focused on design systems and developer tooling. Building in the open.</p>
|
|
167
|
+
<div class="flex items-center gap-2 text-muted-foreground"><Briefcase class="h-3.5 w-3.5" />Nebula Labs</div>
|
|
168
|
+
<div class="flex items-center gap-2 text-muted-foreground"><MapPin class="h-3.5 w-3.5" />Shanghai, CN</div>
|
|
169
|
+
<div class="flex items-center gap-2 text-muted-foreground"><Globe class="h-3.5 w-3.5" />alexchen.dev</div>
|
|
170
|
+
</Card.CardContent>
|
|
171
|
+
</Card.Card>
|
|
172
|
+
|
|
173
|
+
<Card.Card class="border-border/60">
|
|
174
|
+
<Card.CardHeader class="pb-3">
|
|
175
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.workExperience')}</Card.CardTitle>
|
|
176
|
+
</Card.CardHeader>
|
|
177
|
+
<Card.CardContent class="space-y-3">
|
|
178
|
+
{#each workExperience as job (job.company)}
|
|
179
|
+
<div class="flex items-start justify-between gap-2">
|
|
180
|
+
<div>
|
|
181
|
+
<p class="text-sm font-medium text-foreground">{job.role}</p>
|
|
182
|
+
<p class="text-xs text-muted-foreground">{job.company}</p>
|
|
183
|
+
</div>
|
|
184
|
+
<span class="shrink-0 text-xs text-muted-foreground">{job.period}{#if job.current}{i18n.t('profileSections.present')}{/if}</span>
|
|
185
|
+
</div>
|
|
186
|
+
{/each}
|
|
187
|
+
</Card.CardContent>
|
|
188
|
+
</Card.Card>
|
|
189
|
+
|
|
190
|
+
<Card.Card class="border-border/60">
|
|
191
|
+
<Card.CardHeader class="pb-3">
|
|
192
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.skills')}</Card.CardTitle>
|
|
193
|
+
</Card.CardHeader>
|
|
194
|
+
<Card.CardContent>
|
|
195
|
+
<div class="flex flex-wrap gap-1.5">
|
|
196
|
+
{#each skills as skill (skill)}
|
|
197
|
+
<Badge variant="secondary" class="text-xs">{skill}</Badge>
|
|
198
|
+
{/each}
|
|
199
|
+
</div>
|
|
200
|
+
</Card.CardContent>
|
|
201
|
+
</Card.Card>
|
|
202
|
+
|
|
203
|
+
{@render badgesCard()}
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div class="space-y-4 lg:col-span-2">
|
|
207
|
+
{@render projectsTable()}
|
|
208
|
+
|
|
209
|
+
<div class="grid gap-4 sm:grid-cols-2">
|
|
210
|
+
<Card.Card class="border-border/60">
|
|
211
|
+
<Card.CardHeader class="pb-3">
|
|
212
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.recentUploads')}</Card.CardTitle>
|
|
213
|
+
</Card.CardHeader>
|
|
214
|
+
<Card.CardContent class="space-y-2.5">
|
|
215
|
+
{#each recentUploads as file (file.name)}
|
|
216
|
+
<div class="flex items-center gap-3">
|
|
217
|
+
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg {file.tone}">
|
|
218
|
+
<file.Icon class="h-4 w-4" />
|
|
219
|
+
</div>
|
|
220
|
+
<div class="min-w-0 flex-1">
|
|
221
|
+
<p class="truncate text-sm font-medium text-foreground">{file.name}</p>
|
|
222
|
+
<p class="text-xs text-muted-foreground">{file.size}</p>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
{/each}
|
|
226
|
+
</Card.CardContent>
|
|
227
|
+
</Card.Card>
|
|
228
|
+
|
|
229
|
+
<Card.Card class="border-border/60">
|
|
230
|
+
<Card.CardHeader class="pb-3">
|
|
231
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.contributors')}</Card.CardTitle>
|
|
232
|
+
</Card.CardHeader>
|
|
233
|
+
<Card.CardContent>
|
|
234
|
+
<div class="flex flex-wrap gap-1.5">
|
|
235
|
+
{#each contributors as person (person)}
|
|
236
|
+
<div class="flex h-9 w-9 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground ring-2 ring-card" title={person}>
|
|
237
|
+
{person.startsWith('+') ? person : initials(person)}
|
|
238
|
+
</div>
|
|
239
|
+
{/each}
|
|
240
|
+
</div>
|
|
241
|
+
</Card.CardContent>
|
|
242
|
+
</Card.Card>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
{:else if variant === 'company'}
|
|
247
|
+
<div class="space-y-4">
|
|
248
|
+
<div class="grid gap-4 sm:grid-cols-3">
|
|
249
|
+
{#each highlights as item (item.label)}
|
|
250
|
+
<Card.Card class="border-border/60">
|
|
251
|
+
<Card.CardContent class="p-4">
|
|
252
|
+
<p class="text-xs text-muted-foreground">{item.label}</p>
|
|
253
|
+
<div class="mt-1 flex items-baseline gap-2">
|
|
254
|
+
<span class="text-xl font-bold text-foreground">{item.value}</span>
|
|
255
|
+
<span class="flex items-center gap-0.5 text-xs font-medium text-green-600"><TrendingUp class="h-3 w-3" />{item.delta}</span>
|
|
256
|
+
</div>
|
|
257
|
+
</Card.CardContent>
|
|
258
|
+
</Card.Card>
|
|
259
|
+
{/each}
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<div class="grid gap-4 lg:grid-cols-3">
|
|
263
|
+
<div class="lg:col-span-2">
|
|
264
|
+
{@render projectsTable()}
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<div class="space-y-4">
|
|
268
|
+
<Card.Card class="border-border/60">
|
|
269
|
+
<Card.CardHeader class="pb-3">
|
|
270
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.openJobs')}</Card.CardTitle>
|
|
271
|
+
</Card.CardHeader>
|
|
272
|
+
<Card.CardContent class="space-y-3">
|
|
273
|
+
{#each openJobs as job (job.title)}
|
|
274
|
+
<div>
|
|
275
|
+
<p class="text-sm font-medium text-foreground">{job.title}</p>
|
|
276
|
+
<p class="text-xs text-muted-foreground">{job.location} · {job.type}</p>
|
|
277
|
+
</div>
|
|
278
|
+
{/each}
|
|
279
|
+
</Card.CardContent>
|
|
280
|
+
</Card.Card>
|
|
281
|
+
|
|
282
|
+
<Card.Card class="border-border/60">
|
|
283
|
+
<Card.CardHeader class="pb-3">
|
|
284
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.locations')}</Card.CardTitle>
|
|
285
|
+
</Card.CardHeader>
|
|
286
|
+
<Card.CardContent class="space-y-2">
|
|
287
|
+
{#each locations as loc (loc)}
|
|
288
|
+
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
|
289
|
+
<Building2 class="h-3.5 w-3.5" />{loc}
|
|
290
|
+
</div>
|
|
291
|
+
{/each}
|
|
292
|
+
</Card.CardContent>
|
|
293
|
+
</Card.Card>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<div class="grid gap-4 sm:grid-cols-2">
|
|
298
|
+
<Card.Card class="border-border/60">
|
|
299
|
+
<Card.CardHeader class="pb-3">
|
|
300
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.members')}</Card.CardTitle>
|
|
301
|
+
</Card.CardHeader>
|
|
302
|
+
<Card.CardContent>
|
|
303
|
+
<div class="flex flex-wrap gap-1.5">
|
|
304
|
+
{#each companyMembers as person (person)}
|
|
305
|
+
<div class="flex h-9 w-9 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground ring-2 ring-card" title={person}>
|
|
306
|
+
{person.startsWith('+') ? person : initials(person)}
|
|
307
|
+
</div>
|
|
308
|
+
{/each}
|
|
309
|
+
</div>
|
|
310
|
+
</Card.CardContent>
|
|
311
|
+
</Card.Card>
|
|
312
|
+
|
|
313
|
+
<Card.Card class="border-border/60">
|
|
314
|
+
<Card.CardHeader class="pb-3">
|
|
315
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.investments')}</Card.CardTitle>
|
|
316
|
+
</Card.CardHeader>
|
|
317
|
+
<Card.CardContent class="space-y-2.5">
|
|
318
|
+
{#each investments as inv (inv.round)}
|
|
319
|
+
<div class="flex items-center justify-between text-sm">
|
|
320
|
+
<span class="font-medium text-foreground">{inv.round}</span>
|
|
321
|
+
<span class="text-muted-foreground">{inv.amount} · {inv.date}</span>
|
|
322
|
+
</div>
|
|
323
|
+
{/each}
|
|
324
|
+
</Card.CardContent>
|
|
325
|
+
</Card.Card>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
{:else}
|
|
329
|
+
<div class="grid gap-4 lg:grid-cols-3">
|
|
330
|
+
<div class="space-y-4">
|
|
331
|
+
<Card.Card class="border-border/60">
|
|
332
|
+
<Card.CardHeader class="pb-3">
|
|
333
|
+
<Card.CardTitle class="text-base">{i18n.t('profileSections.about')}</Card.CardTitle>
|
|
334
|
+
</Card.CardHeader>
|
|
335
|
+
<Card.CardContent class="space-y-2.5 text-sm">
|
|
336
|
+
<p class="text-muted-foreground">Pro gamer & content creator. Competing in FPS tournaments and streaming daily.</p>
|
|
337
|
+
<div class="flex items-center gap-2 text-muted-foreground"><Gamepad2 class="h-3.5 w-3.5" />ShadowFox#7742</div>
|
|
338
|
+
<div class="flex items-center gap-2 text-muted-foreground"><MapPin class="h-3.5 w-3.5" />Seoul, KR</div>
|
|
339
|
+
</Card.CardContent>
|
|
340
|
+
</Card.Card>
|
|
341
|
+
|
|
342
|
+
<Card.Card class="border-border/60">
|
|
343
|
+
<Card.CardHeader class="pb-3">
|
|
344
|
+
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
345
|
+
<Gamepad2 class="h-4 w-4 text-muted-foreground" />{i18n.t('profileSections.favoriteGames')}
|
|
346
|
+
</Card.CardTitle>
|
|
347
|
+
</Card.CardHeader>
|
|
348
|
+
<Card.CardContent class="space-y-3">
|
|
349
|
+
{#each favoriteGames as game (game.name)}
|
|
350
|
+
<div class="flex items-center justify-between gap-2">
|
|
351
|
+
<div>
|
|
352
|
+
<p class="text-sm font-medium text-foreground">{game.name}</p>
|
|
353
|
+
<p class="text-xs text-muted-foreground">{game.hours}</p>
|
|
354
|
+
</div>
|
|
355
|
+
<Badge variant="secondary" class="text-[10px]">{game.rank}</Badge>
|
|
356
|
+
</div>
|
|
357
|
+
{/each}
|
|
358
|
+
</Card.CardContent>
|
|
359
|
+
</Card.Card>
|
|
360
|
+
|
|
361
|
+
<Card.Card class="border-primary/30 bg-primary/5">
|
|
362
|
+
<Card.CardHeader class="pb-3">
|
|
363
|
+
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
364
|
+
<Play class="h-4 w-4 text-primary" />{i18n.t('profileSections.nowPlaying')}
|
|
365
|
+
</Card.CardTitle>
|
|
366
|
+
</Card.CardHeader>
|
|
367
|
+
<Card.CardContent>
|
|
368
|
+
<p class="text-sm font-medium text-foreground">Starfall Odyssey — Ranked</p>
|
|
369
|
+
<p class="mt-1 text-xs text-muted-foreground">Streaming live for 2.4K viewers</p>
|
|
370
|
+
<div class="mt-3 h-1.5 overflow-hidden rounded-full bg-primary/15">
|
|
371
|
+
<div class="h-full w-2/3 rounded-full bg-primary"></div>
|
|
372
|
+
</div>
|
|
373
|
+
</Card.CardContent>
|
|
374
|
+
</Card.Card>
|
|
375
|
+
|
|
376
|
+
{@render badgesCard()}
|
|
377
|
+
|
|
378
|
+
<Card.Card class="border-border/60">
|
|
379
|
+
<Card.CardHeader class="pb-3">
|
|
380
|
+
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
381
|
+
<Users class="h-4 w-4 text-muted-foreground" />{i18n.t('profileSections.team')}
|
|
382
|
+
</Card.CardTitle>
|
|
383
|
+
</Card.CardHeader>
|
|
384
|
+
<Card.CardContent class="space-y-2.5">
|
|
385
|
+
{#each [{ name: 'VortexQueen', role: 'IGL' }, { name: 'NoScopeNina', role: 'AWPer' }, { name: 'PixelPunk', role: 'Support' }] as mate (mate.name)}
|
|
386
|
+
<div class="flex items-center gap-2.5">
|
|
387
|
+
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground">
|
|
388
|
+
{initials(mate.name)}
|
|
389
|
+
</div>
|
|
390
|
+
<div class="min-w-0">
|
|
391
|
+
<p class="truncate text-sm font-medium text-foreground">{mate.name}</p>
|
|
392
|
+
<p class="text-xs text-muted-foreground">{mate.role}</p>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
{/each}
|
|
396
|
+
</Card.CardContent>
|
|
397
|
+
</Card.Card>
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<div class="space-y-4 lg:col-span-2">
|
|
401
|
+
<Card.Card class="border-border/60">
|
|
402
|
+
<Card.CardHeader class="pb-3">
|
|
403
|
+
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
404
|
+
<Trophy class="h-4 w-4 text-muted-foreground" />{i18n.t('profileSections.tournaments')}
|
|
405
|
+
</Card.CardTitle>
|
|
406
|
+
</Card.CardHeader>
|
|
407
|
+
<Card.CardContent class="p-0">
|
|
408
|
+
<Table.Root>
|
|
409
|
+
<Table.Header>
|
|
410
|
+
<Table.Row>
|
|
411
|
+
<Table.Head>{i18n.t('profileSections.tournaments')}</Table.Head>
|
|
412
|
+
<Table.Head>{i18n.t('profileSections.progress')}</Table.Head>
|
|
413
|
+
<Table.Head class="text-right">{i18n.t('account.amount')}</Table.Head>
|
|
414
|
+
</Table.Row>
|
|
415
|
+
</Table.Header>
|
|
416
|
+
<Table.Body>
|
|
417
|
+
{#each tournaments as t (t.name)}
|
|
418
|
+
<Table.Row>
|
|
419
|
+
<Table.Cell class="font-medium">{t.name}</Table.Cell>
|
|
420
|
+
<Table.Cell><Badge variant="secondary" class="text-[10px]">{t.result}</Badge></Table.Cell>
|
|
421
|
+
<Table.Cell class="text-right text-muted-foreground">{t.prize}</Table.Cell>
|
|
422
|
+
</Table.Row>
|
|
423
|
+
{/each}
|
|
424
|
+
</Table.Body>
|
|
425
|
+
</Table.Root>
|
|
426
|
+
</Card.CardContent>
|
|
427
|
+
</Card.Card>
|
|
428
|
+
|
|
429
|
+
<Card.Card class="border-border/60">
|
|
430
|
+
<Card.CardHeader class="pb-3">
|
|
431
|
+
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
432
|
+
<Users class="h-4 w-4 text-muted-foreground" />{i18n.t('profileSections.network')}
|
|
433
|
+
</Card.CardTitle>
|
|
434
|
+
</Card.CardHeader>
|
|
435
|
+
<Card.CardContent>
|
|
436
|
+
<div class="flex flex-wrap gap-1.5">
|
|
437
|
+
{#each gamerNetwork as person (person)}
|
|
438
|
+
<div class="flex h-9 w-9 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground ring-2 ring-card" title={person}>
|
|
439
|
+
{person.startsWith('+') ? person : initials(person)}
|
|
440
|
+
</div>
|
|
441
|
+
{/each}
|
|
442
|
+
</div>
|
|
443
|
+
</Card.CardContent>
|
|
444
|
+
</Card.Card>
|
|
445
|
+
|
|
446
|
+
<div>
|
|
447
|
+
<h4 class="mb-3 text-base font-semibold text-foreground">{i18n.t('profileSections.recentActivity')}</h4>
|
|
448
|
+
<ActivityTimeline activities={gamerActivities} showAutoRefresh={false} />
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
{/if}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
type ProfileVariant = 'default' | 'company' | 'gamer';
|
|
3
|
+
interface Props {
|
|
4
|
+
variant?: ProfileVariant;
|
|
5
|
+
}
|
|
6
|
+
declare const ProfileVariantSections: Component<Props, {}, "">;
|
|
7
|
+
type ProfileVariantSections = ReturnType<typeof ProfileVariantSections>;
|
|
8
|
+
export default ProfileVariantSections;
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
import * as Card from '../ui/card/index.js';
|
|
4
4
|
import { Badge } from '../ui/badge/index.js';
|
|
5
5
|
import { Button } from '../ui/button/index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { Input } from '../ui/input/index.js';
|
|
7
|
+
import { Users, CheckCircle2, ArrowRight, Search, Plus, FolderKanban } from '@lucide/svelte';
|
|
8
|
+
import Progress from '../ui/progress/progress.svelte';
|
|
7
9
|
|
|
8
10
|
const i18n = useTranslation();
|
|
9
11
|
|
|
@@ -16,6 +18,7 @@
|
|
|
16
18
|
status: 'active' | 'completed' | 'on-hold';
|
|
17
19
|
tags?: string[];
|
|
18
20
|
image?: string;
|
|
21
|
+
progress?: number;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
interface Props {
|
|
@@ -28,6 +31,13 @@
|
|
|
28
31
|
projects = [],
|
|
29
32
|
}: Props = $props();
|
|
30
33
|
|
|
34
|
+
let searchQuery = $state('');
|
|
35
|
+
const filtered = $derived(
|
|
36
|
+
searchQuery
|
|
37
|
+
? projects.filter(p => p.name.toLowerCase().includes(searchQuery.toLowerCase()) || p.description.toLowerCase().includes(searchQuery.toLowerCase()) || p.tags?.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())))
|
|
38
|
+
: projects
|
|
39
|
+
);
|
|
40
|
+
|
|
31
41
|
const statusConfig = {
|
|
32
42
|
'active': { color: 'bg-green-500/10 text-green-600', label: 'Active' },
|
|
33
43
|
'completed': { color: 'bg-blue-500/10 text-blue-600', label: 'Completed' },
|
|
@@ -38,20 +48,27 @@
|
|
|
38
48
|
</script>
|
|
39
49
|
|
|
40
50
|
<div class="space-y-4">
|
|
41
|
-
<div class="flex items-center justify-between">
|
|
51
|
+
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
42
52
|
<h3 class="text-lg font-semibold text-foreground">
|
|
43
|
-
{
|
|
53
|
+
{i18n.t('publicProfile.projectsCount', { count: filtered.length })}
|
|
44
54
|
</h3>
|
|
45
|
-
<
|
|
55
|
+
<div class="flex w-full items-center gap-2 sm:w-auto">
|
|
56
|
+
<div class="relative min-w-0 flex-1 sm:flex-none">
|
|
57
|
+
<Search class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
58
|
+
<Input placeholder={i18n.t('publicProfile.searchProjects')} bind:value={searchQuery} class="w-full pl-9 sm:w-48" />
|
|
59
|
+
</div>
|
|
60
|
+
<Button size="sm"><Plus class="h-3.5 w-3.5 mr-1" />{i18n.t('common.create')}</Button>
|
|
61
|
+
</div>
|
|
46
62
|
</div>
|
|
47
63
|
|
|
48
|
-
{#if
|
|
64
|
+
{#if filtered.length === 0}
|
|
49
65
|
<div class="rounded-xl border border-dashed border-border/60 p-8 text-center">
|
|
50
66
|
<p class="text-sm text-muted-foreground">{i18n.t('publicProfile.noProjects')}</p>
|
|
51
67
|
</div>
|
|
52
68
|
{:else}
|
|
53
69
|
<div class="grid gap-4 sm:grid-cols-1 md:grid-cols-2 {gridCols}">
|
|
54
|
-
{#each
|
|
70
|
+
{#each filtered as project (project.id)}
|
|
71
|
+
{@const progress = project.progress ?? (project.status === 'completed' ? 100 : project.status === 'active' ? 72 : 38)}
|
|
55
72
|
<Card.Card class="group overflow-hidden border-border/60 hover:border-primary/30 transition-colors">
|
|
56
73
|
{#if project.image}
|
|
57
74
|
<div class="h-32 overflow-hidden">
|
|
@@ -60,7 +77,12 @@
|
|
|
60
77
|
{/if}
|
|
61
78
|
<Card.CardContent class="p-4 space-y-3">
|
|
62
79
|
<div class="flex items-start justify-between gap-2">
|
|
63
|
-
<
|
|
80
|
+
<div class="flex min-w-0 items-center gap-3">
|
|
81
|
+
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-md border bg-muted/40 text-muted-foreground">
|
|
82
|
+
<FolderKanban class="h-4 w-4" />
|
|
83
|
+
</span>
|
|
84
|
+
<h4 class="font-semibold text-foreground">{project.name}</h4>
|
|
85
|
+
</div>
|
|
64
86
|
<span class="shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold {statusConfig[project.status].color}">
|
|
65
87
|
{statusConfig[project.status].label}
|
|
66
88
|
</span>
|
|
@@ -76,12 +98,20 @@
|
|
|
76
98
|
</div>
|
|
77
99
|
{/if}
|
|
78
100
|
|
|
101
|
+
<div class="space-y-1.5">
|
|
102
|
+
<div class="flex items-center justify-between text-[11px] text-muted-foreground">
|
|
103
|
+
<span>{i18n.t('common.progress')}</span>
|
|
104
|
+
<span class="font-medium text-foreground">{progress}%</span>
|
|
105
|
+
</div>
|
|
106
|
+
<Progress value={progress} class="h-1.5" />
|
|
107
|
+
</div>
|
|
108
|
+
|
|
79
109
|
<div class="flex items-center justify-between pt-2 border-t">
|
|
80
110
|
<div class="flex items-center gap-3 text-xs text-muted-foreground">
|
|
81
111
|
<span class="flex items-center gap-1"><Users class="h-3 w-3" />{i18n.t('publicProfile.projectMembers', { count: project.members })}</span>
|
|
82
112
|
<span class="flex items-center gap-1"><CheckCircle2 class="h-3 w-3" />{i18n.t('publicProfile.projectTasks', { count: project.tasks })}</span>
|
|
83
113
|
</div>
|
|
84
|
-
<Button variant="ghost" size="icon-sm">
|
|
114
|
+
<Button variant="ghost" size="icon-sm" aria-label={project.name} title={project.name}>
|
|
85
115
|
<ArrowRight class="h-3.5 w-3.5" />
|
|
86
116
|
</Button>
|
|
87
117
|
</div>
|