@svadmin/ui 0.34.9 → 0.35.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 +372 -0
- package/dist/component-registry.svelte.d.ts +1 -0
- package/dist/component-registry.svelte.js +1 -0
- package/dist/components/AICommandBar.svelte +3 -3
- package/dist/components/AboutSettings.svelte +2 -2
- package/dist/components/AdminApp.svelte +96 -18
- package/dist/components/AdminApp.svelte.d.ts +32 -2
- package/dist/components/AnomalyBadge.svelte +1 -1
- package/dist/components/ApiSettings.svelte +191 -0
- package/dist/components/ApiSettings.svelte.d.ts +3 -0
- package/dist/components/AppearanceSettings.svelte +5 -6
- package/dist/components/AuditLogDrawer.svelte +1 -1
- package/dist/components/AuditLogViewer.svelte +1 -1
- package/dist/components/AutoForm.svelte +50 -44
- package/dist/components/AutoTable.svelte +44 -39
- package/dist/components/Breadcrumbs.svelte +1 -0
- package/dist/components/ChatDialog.svelte +5 -4
- package/dist/components/ComboboxField.svelte +3 -3
- package/dist/components/CommandPalette.svelte +3 -3
- package/dist/components/CopilotPanel.svelte +2 -2
- package/dist/components/CreatePage.svelte +0 -1
- package/dist/components/DevTools.svelte +4 -3
- package/dist/components/DraggableHeader.svelte +2 -1
- package/dist/components/EditPage.svelte +1 -1
- package/dist/components/EmptyState.svelte +1 -1
- package/dist/components/ErrorPage.svelte +58 -0
- package/dist/components/ErrorPage.svelte.d.ts +8 -0
- package/dist/components/FieldRenderer.svelte +9 -8
- package/dist/components/ForgotPasswordPage.svelte +111 -68
- package/dist/components/Header.svelte +7 -7
- package/dist/components/InferencerPanel.svelte +4 -3
- package/dist/components/InfiniteList.svelte +3 -4
- package/dist/components/IntegrationsSettings.svelte +70 -0
- package/dist/components/IntegrationsSettings.svelte.d.ts +4 -0
- package/dist/components/KeyboardShortcuts.svelte +3 -3
- package/dist/components/Layout.svelte +13 -13
- package/dist/components/LoginPage.svelte +207 -105
- package/dist/components/LoginPage.svelte.d.ts +3 -15
- package/dist/components/MarkdownRenderer.svelte +3 -5
- package/dist/components/ModalForm.svelte +1 -3
- package/dist/components/NotificationsSettings.svelte +115 -0
- package/dist/components/NotificationsSettings.svelte.d.ts +3 -0
- package/dist/components/PageHeader.svelte +9 -7
- package/dist/components/PageSkeleton.svelte +4 -4
- package/dist/components/PasswordInput.svelte +1 -2
- package/dist/components/PermissionMatrix.svelte +5 -5
- package/dist/components/ProfilePage.svelte +1 -1
- package/dist/components/RegisterPage.svelte +103 -65
- package/dist/components/ResourceOperationsPage.svelte +153 -0
- package/dist/components/ResourceOperationsPage.svelte.d.ts +37 -0
- package/dist/components/RolesSettings.svelte +1 -2
- package/dist/components/SecuritySettings.svelte +179 -0
- package/dist/components/SecuritySettings.svelte.d.ts +4 -0
- package/dist/components/SettingsPage.svelte +121 -55
- package/dist/components/SettingsPage.svelte.d.ts +25 -1
- package/dist/components/ShowPage.svelte +13 -15
- package/dist/components/Sidebar.svelte +107 -58
- package/dist/components/SidebarItem.svelte +38 -14
- package/dist/components/StepsForm.svelte +1 -1
- package/dist/components/TaskList.svelte +1 -1
- package/dist/components/TaskQueueDrawer.svelte +2 -1
- package/dist/components/TenantSwitcher.svelte +1 -2
- package/dist/components/Toast.svelte +1 -0
- package/dist/components/TooltipButton.svelte +1 -0
- package/dist/components/TwoFactorAuthPage.svelte +247 -0
- package/dist/components/TwoFactorAuthPage.svelte.d.ts +3 -0
- package/dist/components/UpdatePasswordPage.svelte +86 -48
- package/dist/components/VirtualTable.svelte +1 -1
- package/dist/components/VoiceInput.svelte +1 -0
- package/dist/components/account/CompanyProfilePage.svelte +99 -0
- package/dist/components/account/CompanyProfilePage.svelte.d.ts +3 -0
- package/dist/components/account/GetStartedPage.svelte +123 -0
- package/dist/components/account/GetStartedPage.svelte.d.ts +4 -0
- package/dist/components/account/ImportMembersPage.svelte +143 -0
- package/dist/components/account/ImportMembersPage.svelte.d.ts +3 -0
- package/dist/components/account/MembersStarterPage.svelte +104 -0
- package/dist/components/account/MembersStarterPage.svelte.d.ts +3 -0
- package/dist/components/account/SecurityLogPage.svelte +108 -0
- package/dist/components/account/SecurityLogPage.svelte.d.ts +3 -0
- package/dist/components/account/SettingsEnterprisePage.svelte +131 -0
- package/dist/components/account/SettingsEnterprisePage.svelte.d.ts +3 -0
- package/dist/components/account/SettingsPlainPage.svelte +81 -0
- package/dist/components/account/SettingsPlainPage.svelte.d.ts +3 -0
- package/dist/components/account/SettingsSidebarPage.svelte +98 -0
- package/dist/components/account/SettingsSidebarPage.svelte.d.ts +3 -0
- package/dist/components/account/TeamMembersPage.svelte +98 -0
- package/dist/components/account/TeamMembersPage.svelte.d.ts +3 -0
- package/dist/components/buttons/ImportButton.svelte +1 -1
- package/dist/components/buttons/ListButton.svelte +1 -1
- package/dist/components/charts/BarChart.svelte +1 -1
- package/dist/components/charts/LineChart.svelte +3 -3
- package/dist/components/charts/PieChart.svelte +2 -2
- package/dist/components/fieldComponentMap.js +2 -1
- package/dist/components/fields/ArrayField.svelte +1 -1
- package/dist/components/fields/MarkdownField.svelte +1 -0
- package/dist/components/fields/MultiSelectField.svelte +1 -1
- package/dist/components/fields/TagField.svelte +1 -1
- package/dist/components/network/TeamCrewTablePage.svelte +122 -0
- package/dist/components/network/TeamCrewTablePage.svelte.d.ts +3 -0
- package/dist/components/network/UserCardsNFTPage.svelte +134 -0
- package/dist/components/network/UserCardsNFTPage.svelte.d.ts +3 -0
- package/dist/components/profile/ActivityTimeline.svelte +77 -0
- package/dist/components/profile/ActivityTimeline.svelte.d.ts +16 -0
- package/dist/components/profile/ProfileCard.svelte +216 -0
- package/dist/components/profile/ProfileCard.svelte.d.ts +27 -0
- package/dist/components/profile/ProjectsGrid.svelte +91 -0
- package/dist/components/profile/ProjectsGrid.svelte.d.ts +17 -0
- package/dist/components/profile/PublicProfilePage.svelte +179 -0
- package/dist/components/profile/PublicProfilePage.svelte.d.ts +10 -0
- package/dist/components/profile/TeamsShowcase.svelte +98 -0
- package/dist/components/profile/TeamsShowcase.svelte.d.ts +19 -0
- package/dist/components/ui/alert/index.js +1 -0
- package/dist/components/ui/badge/index.js +1 -0
- package/dist/components/ui/button/button.svelte +1 -0
- package/dist/components/ui/button/index.js +1 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +1 -1
- package/dist/components/ui/sheet/sheet.svelte +1 -0
- package/dist/components/ui/tabs/tabs.svelte +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +27 -0
- package/dist/router-state.svelte.js +17 -0
- package/package.json +13 -13
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import * as Card from '../ui/card/index.js';
|
|
4
|
+
import { Avatar } from '../ui/avatar/index.js';
|
|
5
|
+
import { MessageSquare, Plus, UserPlus, Star } from '@lucide/svelte';
|
|
6
|
+
import type { Component } from 'svelte';
|
|
7
|
+
|
|
8
|
+
interface ActivityItem {
|
|
9
|
+
id: string;
|
|
10
|
+
type: 'posted' | 'commented' | 'joined' | 'created' | 'starred';
|
|
11
|
+
user: string;
|
|
12
|
+
avatar?: string;
|
|
13
|
+
content?: string;
|
|
14
|
+
target?: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
activities?: ActivityItem[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { activities = [] }: Props = $props();
|
|
23
|
+
|
|
24
|
+
const typeConfig: Record<string, { icon: Component; color: string; action: string }> = {
|
|
25
|
+
'posted': { icon: MessageSquare, color: 'bg-blue-500/10 text-blue-500', action: t('publicProfile.activityPosted') },
|
|
26
|
+
'commented': { icon: MessageSquare, color: 'bg-green-500/10 text-green-500', action: t('publicProfile.activityCommented') },
|
|
27
|
+
'joined': { icon: UserPlus, color: 'bg-purple-500/10 text-purple-500', action: t('publicProfile.activityJoined') },
|
|
28
|
+
'created': { icon: Plus, color: 'bg-amber-500/10 text-amber-500', action: t('publicProfile.activityCreated') },
|
|
29
|
+
'starred': { icon: Star, color: 'bg-yellow-500/10 text-yellow-500', action: t('publicProfile.activityCreated') },
|
|
30
|
+
};
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div class="space-y-4">
|
|
34
|
+
<h3 class="text-lg font-semibold text-foreground">{t('publicProfile.activity')}</h3>
|
|
35
|
+
|
|
36
|
+
{#if activities.length === 0}
|
|
37
|
+
<div class="rounded-xl border border-dashed border-border/60 p-8 text-center">
|
|
38
|
+
<p class="text-sm text-muted-foreground">{t('publicProfile.noActivity')}</p>
|
|
39
|
+
</div>
|
|
40
|
+
{:else}
|
|
41
|
+
<Card.Card class="border-border/60">
|
|
42
|
+
<Card.CardContent class="p-0">
|
|
43
|
+
<div class="divide-y">
|
|
44
|
+
{#each activities as activity (activity.id)}
|
|
45
|
+
{@const config = typeConfig[activity.type] ?? typeConfig['posted']}
|
|
46
|
+
<div class="flex items-start gap-3 p-4">
|
|
47
|
+
<div class="shrink-0 mt-0.5">
|
|
48
|
+
{#if activity.avatar}
|
|
49
|
+
<Avatar class="h-8 w-8">
|
|
50
|
+
<img src={activity.avatar} alt={activity.user} />
|
|
51
|
+
</Avatar>
|
|
52
|
+
{:else}
|
|
53
|
+
<div class="flex h-8 w-8 items-center justify-center rounded-full {config.color}">
|
|
54
|
+
<config.icon class="h-4 w-4" />
|
|
55
|
+
</div>
|
|
56
|
+
{/if}
|
|
57
|
+
</div>
|
|
58
|
+
<div class="flex-1 min-w-0">
|
|
59
|
+
<p class="text-sm">
|
|
60
|
+
<strong class="text-foreground">{activity.user}</strong>
|
|
61
|
+
<span class="text-muted-foreground"> {config.action} </span>
|
|
62
|
+
{#if activity.target}
|
|
63
|
+
<strong class="text-foreground">{activity.target}</strong>
|
|
64
|
+
{/if}
|
|
65
|
+
</p>
|
|
66
|
+
{#if activity.content}
|
|
67
|
+
<p class="mt-1 text-sm text-muted-foreground line-clamp-2">{activity.content}</p>
|
|
68
|
+
{/if}
|
|
69
|
+
<p class="mt-1 text-xs text-muted-foreground/60">{activity.timestamp}</p>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
{/each}
|
|
73
|
+
</div>
|
|
74
|
+
</Card.CardContent>
|
|
75
|
+
</Card.Card>
|
|
76
|
+
{/if}
|
|
77
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
interface ActivityItem {
|
|
3
|
+
id: string;
|
|
4
|
+
type: 'posted' | 'commented' | 'joined' | 'created' | 'starred';
|
|
5
|
+
user: string;
|
|
6
|
+
avatar?: string;
|
|
7
|
+
content?: string;
|
|
8
|
+
target?: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
}
|
|
11
|
+
interface Props {
|
|
12
|
+
activities?: ActivityItem[];
|
|
13
|
+
}
|
|
14
|
+
declare const ActivityTimeline: Component<Props, {}, "">;
|
|
15
|
+
type ActivityTimeline = ReturnType<typeof ActivityTimeline>;
|
|
16
|
+
export default ActivityTimeline;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { Badge } from '../ui/badge/index.js';
|
|
5
|
+
import * as Card from '../ui/card/index.js';
|
|
6
|
+
import { Avatar } from '../ui/avatar/index.js';
|
|
7
|
+
import { MapPin, Link, Calendar, Users, Star, Trophy, Building2, Gamepad2 } from '@lucide/svelte';
|
|
8
|
+
|
|
9
|
+
interface ProfileStats {
|
|
10
|
+
label: string;
|
|
11
|
+
value: string | number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
variant?: 'default' | 'company' | 'gamer';
|
|
16
|
+
name: string;
|
|
17
|
+
tagline?: string;
|
|
18
|
+
avatar?: string;
|
|
19
|
+
coverImage?: string;
|
|
20
|
+
location?: string;
|
|
21
|
+
website?: string;
|
|
22
|
+
joinedDate?: string;
|
|
23
|
+
stats?: ProfileStats[];
|
|
24
|
+
tags?: string[];
|
|
25
|
+
followers?: number;
|
|
26
|
+
following?: number;
|
|
27
|
+
// Company-specific
|
|
28
|
+
industry?: string;
|
|
29
|
+
employees?: number;
|
|
30
|
+
founded?: string;
|
|
31
|
+
// Gamer-specific
|
|
32
|
+
gamerTag?: string;
|
|
33
|
+
level?: number;
|
|
34
|
+
rank?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let {
|
|
38
|
+
variant = 'default',
|
|
39
|
+
name,
|
|
40
|
+
tagline = '',
|
|
41
|
+
avatar,
|
|
42
|
+
coverImage,
|
|
43
|
+
location,
|
|
44
|
+
website,
|
|
45
|
+
joinedDate,
|
|
46
|
+
stats = [],
|
|
47
|
+
tags = [],
|
|
48
|
+
followers = 0,
|
|
49
|
+
following = 0,
|
|
50
|
+
industry,
|
|
51
|
+
employees,
|
|
52
|
+
founded,
|
|
53
|
+
gamerTag,
|
|
54
|
+
level,
|
|
55
|
+
rank,
|
|
56
|
+
}: Props = $props();
|
|
57
|
+
|
|
58
|
+
const initials = $derived(name ? name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2) : '?');
|
|
59
|
+
const statsGridClass = $derived(stats.length > 3 ? 'grid-cols-4' : stats.length === 3 ? 'grid-cols-3' : stats.length === 2 ? 'grid-cols-2' : 'grid-cols-1');
|
|
60
|
+
|
|
61
|
+
let isConnected = $state(false);
|
|
62
|
+
|
|
63
|
+
function toggleConnect() {
|
|
64
|
+
isConnected = !isConnected;
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<Card.Card class="overflow-hidden border-border/60">
|
|
69
|
+
{#if coverImage}
|
|
70
|
+
<div class="h-32 bg-gradient-to-r from-primary/20 via-primary/10 to-accent/20 relative">
|
|
71
|
+
<img src={coverImage} alt="" class="h-full w-full object-cover" />
|
|
72
|
+
</div>
|
|
73
|
+
{:else}
|
|
74
|
+
<div class="h-32 bg-gradient-to-r from-primary/20 via-primary/10 to-accent/20 relative">
|
|
75
|
+
<div class="absolute inset-0 opacity-[0.06]" style="background-image: radial-gradient(circle, currentColor 1px, transparent 1px); background-size: 20px 20px;"></div>
|
|
76
|
+
</div>
|
|
77
|
+
{/if}
|
|
78
|
+
|
|
79
|
+
<Card.CardContent class="relative px-5 pb-5">
|
|
80
|
+
<div class="-mt-10 mb-3 flex items-end gap-4">
|
|
81
|
+
<div class="ring-4 ring-card rounded-xl overflow-hidden shrink-0">
|
|
82
|
+
{#if avatar}
|
|
83
|
+
<Avatar class="h-20 w-20">
|
|
84
|
+
<img src={avatar} alt={name} />
|
|
85
|
+
</Avatar>
|
|
86
|
+
{:else}
|
|
87
|
+
<div class="flex h-20 w-20 items-center justify-center rounded-xl bg-primary/10 text-primary text-2xl font-bold">
|
|
88
|
+
{initials}
|
|
89
|
+
</div>
|
|
90
|
+
{/if}
|
|
91
|
+
</div>
|
|
92
|
+
<div class="flex-1 min-w-0 pb-1">
|
|
93
|
+
<div class="flex items-center gap-2">
|
|
94
|
+
<h3 class="text-lg font-semibold text-foreground truncate">{name}</h3>
|
|
95
|
+
{#if variant === 'company'}
|
|
96
|
+
<Badge variant="secondary" class="shrink-0"><Building2 class="h-3 w-3 mr-1" />{t('profile.company')}</Badge>
|
|
97
|
+
{:else if variant === 'gamer'}
|
|
98
|
+
<Badge variant="secondary" class="shrink-0"><Gamepad2 class="h-3 w-3 mr-1" />{t('profile.gamer')}</Badge>
|
|
99
|
+
{/if}
|
|
100
|
+
</div>
|
|
101
|
+
{#if tagline}
|
|
102
|
+
<p class="text-sm text-muted-foreground truncate">{tagline}</p>
|
|
103
|
+
{/if}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Variant-specific info -->
|
|
108
|
+
{#if variant === 'company' && (industry || employees || founded)}
|
|
109
|
+
<div class="mb-3 grid grid-cols-2 gap-2 text-sm">
|
|
110
|
+
{#if industry}
|
|
111
|
+
<div class="flex items-center gap-2 text-muted-foreground">
|
|
112
|
+
<Building2 class="h-3.5 w-3.5" />
|
|
113
|
+
<span>{industry}</span>
|
|
114
|
+
</div>
|
|
115
|
+
{/if}
|
|
116
|
+
{#if employees}
|
|
117
|
+
<div class="flex items-center gap-2 text-muted-foreground">
|
|
118
|
+
<Users class="h-3.5 w-3.5" />
|
|
119
|
+
<span>{employees} {t('profile.employees')}</span>
|
|
120
|
+
</div>
|
|
121
|
+
{/if}
|
|
122
|
+
{#if founded}
|
|
123
|
+
<div class="flex items-center gap-2 text-muted-foreground">
|
|
124
|
+
<Calendar class="h-3.5 w-3.5" />
|
|
125
|
+
<span>{t('profile.founded')} {founded}</span>
|
|
126
|
+
</div>
|
|
127
|
+
{/if}
|
|
128
|
+
{#if website}
|
|
129
|
+
<div class="flex items-center gap-2 text-muted-foreground">
|
|
130
|
+
<Link class="h-3.5 w-3.5" />
|
|
131
|
+
<span class="truncate">{website}</span>
|
|
132
|
+
</div>
|
|
133
|
+
{/if}
|
|
134
|
+
</div>
|
|
135
|
+
{:else if variant === 'gamer' && (gamerTag || level || rank)}
|
|
136
|
+
<div class="mb-3 grid grid-cols-2 gap-2 text-sm">
|
|
137
|
+
{#if gamerTag}
|
|
138
|
+
<div class="flex items-center gap-2 text-muted-foreground">
|
|
139
|
+
<Gamepad2 class="h-3.5 w-3.5" />
|
|
140
|
+
<span>{gamerTag}</span>
|
|
141
|
+
</div>
|
|
142
|
+
{/if}
|
|
143
|
+
{#if level}
|
|
144
|
+
<div class="flex items-center gap-2 text-muted-foreground">
|
|
145
|
+
<Star class="h-3.5 w-3.5" />
|
|
146
|
+
<span>{t('profile.level')} {level}</span>
|
|
147
|
+
</div>
|
|
148
|
+
{/if}
|
|
149
|
+
{#if rank}
|
|
150
|
+
<div class="flex items-center gap-2 text-muted-foreground">
|
|
151
|
+
<Trophy class="h-3.5 w-3.5" />
|
|
152
|
+
<span>{rank}</span>
|
|
153
|
+
</div>
|
|
154
|
+
{/if}
|
|
155
|
+
</div>
|
|
156
|
+
{:else}
|
|
157
|
+
<!-- Default profile info -->
|
|
158
|
+
<div class="mb-3 flex flex-wrap gap-3 text-sm text-muted-foreground">
|
|
159
|
+
{#if location}
|
|
160
|
+
<div class="flex items-center gap-1.5"><MapPin class="h-3.5 w-3.5" /><span>{location}</span></div>
|
|
161
|
+
{/if}
|
|
162
|
+
{#if website}
|
|
163
|
+
<div class="flex items-center gap-1.5"><Link class="h-3.5 w-3.5" /><span class="truncate">{website}</span></div>
|
|
164
|
+
{/if}
|
|
165
|
+
{#if joinedDate}
|
|
166
|
+
<div class="flex items-center gap-1.5"><Calendar class="h-3.5 w-3.5" /><span>{t('publicProfile.joinedDate', { date: joinedDate })}</span></div>
|
|
167
|
+
{/if}
|
|
168
|
+
</div>
|
|
169
|
+
{/if}
|
|
170
|
+
|
|
171
|
+
<!-- Tags -->
|
|
172
|
+
{#if tags.length > 0}
|
|
173
|
+
<div class="mb-3 flex flex-wrap gap-1.5">
|
|
174
|
+
{#each tags as tag (tag)}
|
|
175
|
+
<Badge variant="outline" class="text-xs">{tag}</Badge>
|
|
176
|
+
{/each}
|
|
177
|
+
</div>
|
|
178
|
+
{/if}
|
|
179
|
+
|
|
180
|
+
<!-- Stats -->
|
|
181
|
+
{#if stats.length > 0}
|
|
182
|
+
<div class="mb-3 grid {statsGridClass} gap-3 border-t pt-3">
|
|
183
|
+
{#each stats as stat (stat.label)}
|
|
184
|
+
<div class="text-center">
|
|
185
|
+
<div class="text-lg font-semibold text-foreground">{stat.value}</div>
|
|
186
|
+
<div class="text-xs text-muted-foreground">{stat.label}</div>
|
|
187
|
+
</div>
|
|
188
|
+
{/each}
|
|
189
|
+
</div>
|
|
190
|
+
{/if}
|
|
191
|
+
|
|
192
|
+
<!-- Followers / Following -->
|
|
193
|
+
{#if followers > 0 || following > 0}
|
|
194
|
+
<div class="flex gap-4 text-sm border-t pt-3">
|
|
195
|
+
{#if followers > 0}
|
|
196
|
+
<span><strong class="text-foreground">{followers}</strong> <span class="text-muted-foreground">{t('publicProfile.followers')}</span></span>
|
|
197
|
+
{/if}
|
|
198
|
+
{#if following > 0}
|
|
199
|
+
<span><strong class="text-foreground">{following}</strong> <span class="text-muted-foreground">{t('publicProfile.following')}</span></span>
|
|
200
|
+
{/if}
|
|
201
|
+
</div>
|
|
202
|
+
{/if}
|
|
203
|
+
|
|
204
|
+
<!-- Actions -->
|
|
205
|
+
<div class="mt-3 flex gap-2">
|
|
206
|
+
<Button
|
|
207
|
+
size="sm"
|
|
208
|
+
variant={isConnected ? 'outline' : 'default'}
|
|
209
|
+
onclick={toggleConnect}
|
|
210
|
+
>
|
|
211
|
+
{isConnected ? t('network.connected') : t('network.connect')}
|
|
212
|
+
</Button>
|
|
213
|
+
<Button size="sm" variant="outline">{t('publicProfile.sendMessage')}</Button>
|
|
214
|
+
</div>
|
|
215
|
+
</Card.CardContent>
|
|
216
|
+
</Card.Card>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface ProfileStats {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string | number;
|
|
4
|
+
}
|
|
5
|
+
interface Props {
|
|
6
|
+
variant?: 'default' | 'company' | 'gamer';
|
|
7
|
+
name: string;
|
|
8
|
+
tagline?: string;
|
|
9
|
+
avatar?: string;
|
|
10
|
+
coverImage?: string;
|
|
11
|
+
location?: string;
|
|
12
|
+
website?: string;
|
|
13
|
+
joinedDate?: string;
|
|
14
|
+
stats?: ProfileStats[];
|
|
15
|
+
tags?: string[];
|
|
16
|
+
followers?: number;
|
|
17
|
+
following?: number;
|
|
18
|
+
industry?: string;
|
|
19
|
+
employees?: number;
|
|
20
|
+
founded?: string;
|
|
21
|
+
gamerTag?: string;
|
|
22
|
+
level?: number;
|
|
23
|
+
rank?: string;
|
|
24
|
+
}
|
|
25
|
+
declare const ProfileCard: import("svelte").Component<Props, {}, "">;
|
|
26
|
+
type ProfileCard = ReturnType<typeof ProfileCard>;
|
|
27
|
+
export default ProfileCard;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import * as Card from '../ui/card/index.js';
|
|
4
|
+
import { Badge } from '../ui/badge/index.js';
|
|
5
|
+
import { Button } from '../ui/button/index.js';
|
|
6
|
+
import { Users, CheckCircle2, ArrowRight } from '@lucide/svelte';
|
|
7
|
+
|
|
8
|
+
interface Project {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
members: number;
|
|
13
|
+
tasks: number;
|
|
14
|
+
status: 'active' | 'completed' | 'on-hold';
|
|
15
|
+
tags?: string[];
|
|
16
|
+
image?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
columns?: 2 | 3;
|
|
21
|
+
projects?: Project[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
columns = 2,
|
|
26
|
+
projects = [],
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
|
|
29
|
+
const statusConfig = {
|
|
30
|
+
'active': { color: 'bg-green-500/10 text-green-600', label: 'Active' },
|
|
31
|
+
'completed': { color: 'bg-blue-500/10 text-blue-600', label: 'Completed' },
|
|
32
|
+
'on-hold': { color: 'bg-amber-500/10 text-amber-600', label: 'On Hold' },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const gridCols = $derived(columns === 3 ? 'lg:grid-cols-3' : 'lg:grid-cols-2');
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<div class="space-y-4">
|
|
39
|
+
<div class="flex items-center justify-between">
|
|
40
|
+
<h3 class="text-lg font-semibold text-foreground">
|
|
41
|
+
{columns === 2 ? t('publicProfile.projects2Col') : t('publicProfile.projects3Col')}
|
|
42
|
+
</h3>
|
|
43
|
+
<Badge variant="secondary">{projects.length}</Badge>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
{#if projects.length === 0}
|
|
47
|
+
<div class="rounded-xl border border-dashed border-border/60 p-8 text-center">
|
|
48
|
+
<p class="text-sm text-muted-foreground">{t('publicProfile.noProjects')}</p>
|
|
49
|
+
</div>
|
|
50
|
+
{:else}
|
|
51
|
+
<div class="grid gap-4 sm:grid-cols-1 md:grid-cols-2 {gridCols}">
|
|
52
|
+
{#each projects as project (project.id)}
|
|
53
|
+
<Card.Card class="group overflow-hidden border-border/60 hover:border-primary/30 transition-colors">
|
|
54
|
+
{#if project.image}
|
|
55
|
+
<div class="h-32 overflow-hidden">
|
|
56
|
+
<img src={project.image} alt={project.name} class="h-full w-full object-cover group-hover:scale-105 transition-transform duration-300" />
|
|
57
|
+
</div>
|
|
58
|
+
{/if}
|
|
59
|
+
<Card.CardContent class="p-4 space-y-3">
|
|
60
|
+
<div class="flex items-start justify-between gap-2">
|
|
61
|
+
<h4 class="font-semibold text-foreground">{project.name}</h4>
|
|
62
|
+
<span class="shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold {statusConfig[project.status].color}">
|
|
63
|
+
{statusConfig[project.status].label}
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<p class="text-sm text-muted-foreground line-clamp-2">{project.description}</p>
|
|
68
|
+
|
|
69
|
+
{#if project.tags && project.tags.length > 0}
|
|
70
|
+
<div class="flex flex-wrap gap-1">
|
|
71
|
+
{#each project.tags as tag (tag)}
|
|
72
|
+
<Badge variant="outline" class="text-[10px]">{tag}</Badge>
|
|
73
|
+
{/each}
|
|
74
|
+
</div>
|
|
75
|
+
{/if}
|
|
76
|
+
|
|
77
|
+
<div class="flex items-center justify-between pt-2 border-t">
|
|
78
|
+
<div class="flex items-center gap-3 text-xs text-muted-foreground">
|
|
79
|
+
<span class="flex items-center gap-1"><Users class="h-3 w-3" />{t('publicProfile.projectMembers', { count: project.members })}</span>
|
|
80
|
+
<span class="flex items-center gap-1"><CheckCircle2 class="h-3 w-3" />{t('publicProfile.projectTasks', { count: project.tasks })}</span>
|
|
81
|
+
</div>
|
|
82
|
+
<Button variant="ghost" size="icon-sm">
|
|
83
|
+
<ArrowRight class="h-3.5 w-3.5" />
|
|
84
|
+
</Button>
|
|
85
|
+
</div>
|
|
86
|
+
</Card.CardContent>
|
|
87
|
+
</Card.Card>
|
|
88
|
+
{/each}
|
|
89
|
+
</div>
|
|
90
|
+
{/if}
|
|
91
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface Project {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
members: number;
|
|
6
|
+
tasks: number;
|
|
7
|
+
status: 'active' | 'completed' | 'on-hold';
|
|
8
|
+
tags?: string[];
|
|
9
|
+
image?: string;
|
|
10
|
+
}
|
|
11
|
+
interface Props {
|
|
12
|
+
columns?: 2 | 3;
|
|
13
|
+
projects?: Project[];
|
|
14
|
+
}
|
|
15
|
+
declare const ProjectsGrid: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type ProjectsGrid = ReturnType<typeof ProjectsGrid>;
|
|
17
|
+
export default ProjectsGrid;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core/i18n';
|
|
3
|
+
import * as Card from '../ui/card/index.js';
|
|
4
|
+
import { Button } from '../ui/button/index.js';
|
|
5
|
+
import { Badge } from '../ui/badge/index.js';
|
|
6
|
+
import * as Tabs from '../ui/tabs/index.js';
|
|
7
|
+
import ProjectsGrid from './ProjectsGrid.svelte';
|
|
8
|
+
import ActivityTimeline from './ActivityTimeline.svelte';
|
|
9
|
+
import TeamsShowcase from './TeamsShowcase.svelte';
|
|
10
|
+
import { MapPin, Link, Calendar, MoreHorizontal } from '@lucide/svelte';
|
|
11
|
+
|
|
12
|
+
type ProfileVariant = 'default' | 'company' | 'gamer';
|
|
13
|
+
type ProfileTab = 'projects' | 'activity' | 'teams';
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
variant?: ProfileVariant;
|
|
17
|
+
initialTab?: ProfileTab;
|
|
18
|
+
columns?: 2 | 3;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let { variant = 'default', initialTab = 'projects', columns = 2 }: Props = $props();
|
|
22
|
+
let activeTab = $derived<ProfileTab>(initialTab);
|
|
23
|
+
|
|
24
|
+
// Demo profile data based on variant
|
|
25
|
+
const profileData = $derived.by(() => {
|
|
26
|
+
if (variant === 'company') {
|
|
27
|
+
return {
|
|
28
|
+
name: 'Acme Corporation',
|
|
29
|
+
tagline: 'Building the future of enterprise software',
|
|
30
|
+
industry: 'Technology',
|
|
31
|
+
employees: 1250,
|
|
32
|
+
founded: '2015',
|
|
33
|
+
website: 'acme.com',
|
|
34
|
+
location: 'San Francisco, CA',
|
|
35
|
+
followers: 8420,
|
|
36
|
+
following: 312,
|
|
37
|
+
stats: [
|
|
38
|
+
{ label: t('profile.employees'), value: '1,250' },
|
|
39
|
+
{ label: t('publicProfile.projects'), value: '48' },
|
|
40
|
+
{ label: t('publicProfile.teams'), value: '12' },
|
|
41
|
+
],
|
|
42
|
+
tags: ['SaaS', 'Enterprise', 'Cloud', 'AI'],
|
|
43
|
+
};
|
|
44
|
+
} else if (variant === 'gamer') {
|
|
45
|
+
return {
|
|
46
|
+
name: 'ShadowFox',
|
|
47
|
+
tagline: 'Pro gamer & content creator',
|
|
48
|
+
gamerTag: 'ShadowFox#7742',
|
|
49
|
+
level: 42,
|
|
50
|
+
rank: 'Diamond II',
|
|
51
|
+
location: 'Seoul, KR',
|
|
52
|
+
followers: 24600,
|
|
53
|
+
following: 180,
|
|
54
|
+
stats: [
|
|
55
|
+
{ label: t('profile.gamesPlayed'), value: '1,284' },
|
|
56
|
+
{ label: t('profile.winRate'), value: '68%' },
|
|
57
|
+
{ label: t('profile.rank'), value: 'Diamond II' },
|
|
58
|
+
],
|
|
59
|
+
tags: ['FPS', 'RPG', 'Streaming', 'Esports'],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
name: 'Alex Chen',
|
|
64
|
+
tagline: 'Full-stack developer & open source enthusiast',
|
|
65
|
+
location: 'Shanghai, CN',
|
|
66
|
+
website: 'alexchen.dev',
|
|
67
|
+
joinedDate: '2023',
|
|
68
|
+
followers: 1234,
|
|
69
|
+
following: 567,
|
|
70
|
+
stats: [
|
|
71
|
+
{ label: t('publicProfile.projects'), value: '24' },
|
|
72
|
+
{ label: t('publicProfile.followers'), value: '1.2K' },
|
|
73
|
+
{ label: t('publicProfile.following'), value: '567' },
|
|
74
|
+
],
|
|
75
|
+
tags: ['TypeScript', 'Svelte', 'Node.js', 'Open Source'],
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Demo data
|
|
80
|
+
const demoProjects = [
|
|
81
|
+
{ id: '1', name: 'Dashboard Redesign', description: 'Modern admin dashboard with real-time analytics and customizable widgets.', members: 8, tasks: 34, status: 'active' as const, tags: ['UI', 'Analytics'] },
|
|
82
|
+
{ id: '2', name: 'API Gateway v2', description: 'High-performance API gateway with rate limiting and circuit breaker patterns.', members: 5, tasks: 21, status: 'active' as const, tags: ['Backend', 'Infra'] },
|
|
83
|
+
{ id: '3', name: 'Mobile App', description: 'Cross-platform mobile application built with React Native.', members: 12, tasks: 56, status: 'completed' as const, tags: ['Mobile', 'React'] },
|
|
84
|
+
{ id: '4', name: 'Data Pipeline', description: 'Real-time data processing pipeline for event streaming.', members: 3, tasks: 18, status: 'on-hold' as const, tags: ['Data', 'Streaming'] },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const demoActivities = [
|
|
88
|
+
{ id: '1', type: 'posted' as const, user: 'Alex Chen', content: 'Just shipped the new dashboard redesign with real-time analytics support.', timestamp: '2 hours ago' },
|
|
89
|
+
{ id: '2', type: 'commented' as const, user: 'Alex Chen', target: 'API Gateway PR #42', content: 'Looks great, just a few minor suggestions on error handling.', timestamp: '5 hours ago' },
|
|
90
|
+
{ id: '3', type: 'created' as const, user: 'Alex Chen', target: 'Mobile App project', timestamp: '1 day ago' },
|
|
91
|
+
{ id: '4', type: 'joined' as const, user: 'Alex Chen', target: 'Design Systems Guild', timestamp: '3 days ago' },
|
|
92
|
+
{ id: '5', type: 'starred' as const, user: 'Alex Chen', target: 'svadmin/svadmin', timestamp: '1 week ago' },
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const demoTeams = [
|
|
96
|
+
{ id: '1', name: 'Frontend Platform', description: 'Building the core UI component library and design system.', members: [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Carol' }], totalMembers: 8, color: '#3b82f6' },
|
|
97
|
+
{ id: '2', name: 'Backend Services', description: 'API development, microservices architecture, and infrastructure.', members: [{ name: 'Dave' }, { name: 'Eve' }], totalMembers: 5, color: '#10b981' },
|
|
98
|
+
{ id: '3', name: 'Design', description: 'User experience research and visual design.', members: [{ name: 'Frank' }, { name: 'Grace' }, { name: 'Hank' }, { name: 'Ivy' }], totalMembers: 6, color: '#f59e0b' },
|
|
99
|
+
];
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<div class="space-y-6">
|
|
103
|
+
<!-- Cover + Profile Header -->
|
|
104
|
+
<Card.Card class="overflow-hidden border-border/60">
|
|
105
|
+
<div class="h-40 bg-gradient-to-r from-primary/25 via-primary/15 to-accent/20 relative">
|
|
106
|
+
<div class="absolute inset-0 opacity-[0.06]" style="background-image: radial-gradient(circle, currentColor 1px, transparent 1px); background-size: 24px 24px;"></div>
|
|
107
|
+
</div>
|
|
108
|
+
<Card.CardContent class="relative px-6 pb-5">
|
|
109
|
+
<div class="-mt-12 flex flex-col sm:flex-row sm:items-end gap-4">
|
|
110
|
+
<div class="ring-4 ring-card rounded-2xl overflow-hidden shrink-0">
|
|
111
|
+
<div class="flex h-24 w-24 items-center justify-center bg-primary/10 text-primary text-3xl font-bold">
|
|
112
|
+
{profileData.name.split(' ').map(n => n[0]).join('').slice(0, 2)}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="flex-1 min-w-0">
|
|
116
|
+
<h2 class="text-2xl font-bold text-foreground">{profileData.name}</h2>
|
|
117
|
+
{#if profileData.tagline}
|
|
118
|
+
<p class="text-muted-foreground">{profileData.tagline}</p>
|
|
119
|
+
{/if}
|
|
120
|
+
<div class="mt-2 flex flex-wrap gap-3 text-sm text-muted-foreground">
|
|
121
|
+
{#if profileData.location}
|
|
122
|
+
<span class="flex items-center gap-1"><MapPin class="h-3.5 w-3.5" />{profileData.location}</span>
|
|
123
|
+
{/if}
|
|
124
|
+
{#if profileData.website}
|
|
125
|
+
<span class="flex items-center gap-1"><Link class="h-3.5 w-3.5" />{profileData.website}</span>
|
|
126
|
+
{/if}
|
|
127
|
+
{#if 'joinedDate' in profileData && profileData.joinedDate}
|
|
128
|
+
<span class="flex items-center gap-1"><Calendar class="h-3.5 w-3.5" />{t('publicProfile.joinedDate', { date: profileData.joinedDate })}</span>
|
|
129
|
+
{/if}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="flex gap-2 shrink-0">
|
|
133
|
+
<Button size="sm">{t('publicProfile.editProfile')}</Button>
|
|
134
|
+
<Button size="sm" variant="outline">{t('publicProfile.sendMessage')}</Button>
|
|
135
|
+
<Button size="sm" variant="ghost"><MoreHorizontal class="h-4 w-4" /></Button>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<!-- Stats -->
|
|
140
|
+
{#if profileData.stats.length > 0}
|
|
141
|
+
<div class="mt-4 grid grid-cols-3 gap-4 border-t pt-4">
|
|
142
|
+
{#each profileData.stats as stat (stat.label)}
|
|
143
|
+
<div class="text-center">
|
|
144
|
+
<div class="text-xl font-bold text-foreground">{stat.value}</div>
|
|
145
|
+
<div class="text-xs text-muted-foreground">{stat.label}</div>
|
|
146
|
+
</div>
|
|
147
|
+
{/each}
|
|
148
|
+
</div>
|
|
149
|
+
{/if}
|
|
150
|
+
|
|
151
|
+
<!-- Tags -->
|
|
152
|
+
{#if profileData.tags.length > 0}
|
|
153
|
+
<div class="mt-3 flex flex-wrap gap-1.5">
|
|
154
|
+
{#each profileData.tags as tag (tag)}
|
|
155
|
+
<Badge variant="outline" class="text-xs">{tag}</Badge>
|
|
156
|
+
{/each}
|
|
157
|
+
</div>
|
|
158
|
+
{/if}
|
|
159
|
+
</Card.CardContent>
|
|
160
|
+
</Card.Card>
|
|
161
|
+
|
|
162
|
+
<!-- Tabs: Projects / Activity / Teams -->
|
|
163
|
+
<Tabs.Root value={activeTab}>
|
|
164
|
+
<Tabs.List class="w-full justify-start border-b">
|
|
165
|
+
<Tabs.Trigger value="projects" active={activeTab === 'projects'} onclick={() => activeTab = 'projects'} class="gap-1.5">{t('publicProfile.projects')}</Tabs.Trigger>
|
|
166
|
+
<Tabs.Trigger value="activity" active={activeTab === 'activity'} onclick={() => activeTab = 'activity'} class="gap-1.5">{t('publicProfile.activity')}</Tabs.Trigger>
|
|
167
|
+
<Tabs.Trigger value="teams" active={activeTab === 'teams'} onclick={() => activeTab = 'teams'} class="gap-1.5">{t('publicProfile.teams')}</Tabs.Trigger>
|
|
168
|
+
</Tabs.List>
|
|
169
|
+
<Tabs.Content value="projects" active={activeTab === 'projects'} class="mt-4">
|
|
170
|
+
<ProjectsGrid {columns} projects={demoProjects} />
|
|
171
|
+
</Tabs.Content>
|
|
172
|
+
<Tabs.Content value="activity" active={activeTab === 'activity'} class="mt-4">
|
|
173
|
+
<ActivityTimeline activities={demoActivities} />
|
|
174
|
+
</Tabs.Content>
|
|
175
|
+
<Tabs.Content value="teams" active={activeTab === 'teams'} class="mt-4">
|
|
176
|
+
<TeamsShowcase teams={demoTeams} />
|
|
177
|
+
</Tabs.Content>
|
|
178
|
+
</Tabs.Root>
|
|
179
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type ProfileVariant = 'default' | 'company' | 'gamer';
|
|
2
|
+
type ProfileTab = 'projects' | 'activity' | 'teams';
|
|
3
|
+
interface Props {
|
|
4
|
+
variant?: ProfileVariant;
|
|
5
|
+
initialTab?: ProfileTab;
|
|
6
|
+
columns?: 2 | 3;
|
|
7
|
+
}
|
|
8
|
+
declare const PublicProfilePage: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type PublicProfilePage = ReturnType<typeof PublicProfilePage>;
|
|
10
|
+
export default PublicProfilePage;
|