@svadmin/ui 0.34.10 → 0.36.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 +746 -22
- package/dist/component-registry.svelte.d.ts +1 -0
- package/dist/components/AdminApp.svelte +94 -15
- package/dist/components/AdminApp.svelte.d.ts +32 -2
- package/dist/components/ApiSettings.svelte +191 -0
- package/dist/components/ApiSettings.svelte.d.ts +3 -0
- package/dist/components/AutoForm.svelte +47 -42
- package/dist/components/AutoTable.svelte +29 -24
- package/dist/components/ErrorPage.svelte +58 -0
- package/dist/components/ErrorPage.svelte.d.ts +8 -0
- package/dist/components/FieldRenderer.svelte +2 -2
- package/dist/components/ForgotPasswordPage.svelte +111 -68
- package/dist/components/Header.svelte +6 -7
- package/dist/components/IntegrationsSettings.svelte +70 -0
- package/dist/components/IntegrationsSettings.svelte.d.ts +4 -0
- package/dist/components/Layout.svelte +7 -7
- package/dist/components/LoginPage.svelte +207 -105
- package/dist/components/LoginPage.svelte.d.ts +3 -15
- 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/RegisterPage.svelte +103 -65
- package/dist/components/ResourceOperationsPage.svelte +151 -0
- package/dist/components/ResourceOperationsPage.svelte.d.ts +37 -0
- package/dist/components/SecuritySettings.svelte +179 -0
- package/dist/components/SecuritySettings.svelte.d.ts +4 -0
- package/dist/components/SettingsPage.svelte +120 -87
- package/dist/components/SettingsPage.svelte.d.ts +25 -1
- package/dist/components/ShowPage.svelte +9 -9
- package/dist/components/Sidebar.svelte +278 -189
- package/dist/components/SidebarItem.svelte +77 -18
- 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/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 +135 -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/network/TeamCrewTablePage.svelte +155 -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/pagination/pagination-ellipsis.svelte +1 -1
- package/dist/index.d.ts +23 -0
- package/dist/index.js +27 -0
- package/dist/router-state.svelte.js +17 -0
- package/package.json +2 -2
|
@@ -0,0 +1,134 @@
|
|
|
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 { Input } from '../ui/input/index.js';
|
|
7
|
+
import { Search, ExternalLink, Check, Plus } from '@lucide/svelte';
|
|
8
|
+
|
|
9
|
+
interface NFTUser {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
username: string;
|
|
13
|
+
avatar?: string;
|
|
14
|
+
bio: string;
|
|
15
|
+
collectionCount: number;
|
|
16
|
+
floorPrice: string;
|
|
17
|
+
volume: string;
|
|
18
|
+
verified: boolean;
|
|
19
|
+
tags: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let searchQuery = $state('');
|
|
23
|
+
|
|
24
|
+
const users: NFTUser[] = [
|
|
25
|
+
{ id: '1', name: 'CryptoArt Studio', username: '@cryptoart', bio: 'Digital art collection featuring generative abstracts', collectionCount: 42, floorPrice: '0.5 ETH', volume: '128.3 ETH', verified: true, tags: ['Art', 'Generative'] },
|
|
26
|
+
{ id: '2', name: 'PixelPunk Collective', username: '@pixelpunk', bio: 'On-chain pixel art characters with unique traits', collectionCount: 18, floorPrice: '1.2 ETH', volume: '342.7 ETH', verified: true, tags: ['PFP', 'Pixel'] },
|
|
27
|
+
{ id: '3', name: 'MetaVerse Builders', username: '@metabuild', bio: '3D architectural models for virtual worlds', collectionCount: 7, floorPrice: '0.8 ETH', volume: '56.1 ETH', verified: false, tags: ['3D', 'Virtual'] },
|
|
28
|
+
{ id: '4', name: 'SoundWave NFT', username: '@soundwave', bio: 'Music-backed NFTs with on-chain audio', collectionCount: 23, floorPrice: '0.3 ETH', volume: '89.4 ETH', verified: true, tags: ['Music', 'Audio'] },
|
|
29
|
+
{ id: '5', name: 'GameAssets DAO', username: '@gameassets', bio: 'In-game items and collectibles for Web3 gaming', collectionCount: 156, floorPrice: '0.05 ETH', volume: '412.8 ETH', verified: true, tags: ['Gaming', 'Items'] },
|
|
30
|
+
{ id: '6', name: 'PhotoVerse', username: '@photoverse', bio: 'Curated photography NFTs from world artists', collectionCount: 31, floorPrice: '0.15 ETH', volume: '67.2 ETH', verified: false, tags: ['Photo', 'Curated'] },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const filtered = $derived(
|
|
34
|
+
searchQuery
|
|
35
|
+
? users.filter(u => u.name.toLowerCase().includes(searchQuery.toLowerCase()) || u.username.toLowerCase().includes(searchQuery.toLowerCase()) || u.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())))
|
|
36
|
+
: users
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const initials = (name: string) => name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
|
|
40
|
+
|
|
41
|
+
let connectedIds = $state<string[]>([]);
|
|
42
|
+
|
|
43
|
+
function toggleConnect(id: string) {
|
|
44
|
+
connectedIds = connectedIds.includes(id)
|
|
45
|
+
? connectedIds.filter(connectedId => connectedId !== id)
|
|
46
|
+
: [...connectedIds, id];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isConnected(id: string): boolean {
|
|
50
|
+
return connectedIds.includes(id);
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<div class="space-y-6" data-svadmin-content-page="network">
|
|
55
|
+
<div>
|
|
56
|
+
<h2 class="text-xl font-semibold text-foreground">{t('network.userCards')}</h2>
|
|
57
|
+
<p class="mt-1 text-sm text-muted-foreground">{t('network.userCardsDescription')}</p>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="relative">
|
|
61
|
+
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
62
|
+
<Input placeholder={t('common.search')} bind:value={searchQuery} class="pl-9" />
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="grid gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
|
66
|
+
{#each filtered as user (user.id)}
|
|
67
|
+
<Card.Card class="overflow-hidden border-border/60 hover:border-primary/30 transition-colors">
|
|
68
|
+
<div class="h-20 bg-gradient-to-r from-violet-500/20 via-purple-500/15 to-fuchsia-500/20 relative">
|
|
69
|
+
<div class="absolute inset-0 opacity-[0.08]" style="background-image: radial-gradient(circle, currentColor 1px, transparent 1px); background-size: 16px 16px;"></div>
|
|
70
|
+
</div>
|
|
71
|
+
<Card.CardContent class="relative px-4 pb-4">
|
|
72
|
+
<div class="-mt-8 mb-2 flex items-end gap-3">
|
|
73
|
+
<div class="ring-3 ring-card rounded-xl overflow-hidden shrink-0">
|
|
74
|
+
<div class="flex h-14 w-14 items-center justify-center bg-primary/10 text-primary font-bold">
|
|
75
|
+
{initials(user.name)}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="flex-1 min-w-0 pb-0.5">
|
|
79
|
+
<div class="flex items-center gap-1.5">
|
|
80
|
+
<h4 class="text-sm font-semibold text-foreground truncate">{user.name}</h4>
|
|
81
|
+
{#if user.verified}
|
|
82
|
+
<Check class="h-3.5 w-3.5 text-blue-500" />
|
|
83
|
+
{/if}
|
|
84
|
+
</div>
|
|
85
|
+
<p class="text-xs text-muted-foreground">{user.username}</p>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<p class="text-xs text-muted-foreground line-clamp-2 mb-3">{user.bio}</p>
|
|
90
|
+
|
|
91
|
+
<div class="flex flex-wrap gap-1 mb-3">
|
|
92
|
+
{#each user.tags as tag (tag)}
|
|
93
|
+
<Badge variant="outline" class="text-[10px]">{tag}</Badge>
|
|
94
|
+
{/each}
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- NFT stats -->
|
|
98
|
+
<div class="grid grid-cols-3 gap-2 border-t pt-3">
|
|
99
|
+
<div class="text-center">
|
|
100
|
+
<div class="text-sm font-semibold text-foreground">{user.collectionCount}</div>
|
|
101
|
+
<div class="text-[10px] text-muted-foreground">{t('network.nftCollection')}</div>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="text-center">
|
|
104
|
+
<div class="text-sm font-semibold text-foreground">{user.floorPrice}</div>
|
|
105
|
+
<div class="text-[10px] text-muted-foreground">{t('network.floorPrice')}</div>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="text-center">
|
|
108
|
+
<div class="text-xs font-semibold text-foreground">{user.volume}</div>
|
|
109
|
+
<div class="text-[10px] text-muted-foreground">{t('network.volume')}</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div class="mt-3 flex gap-2">
|
|
114
|
+
<Button
|
|
115
|
+
size="sm"
|
|
116
|
+
variant={isConnected(user.id) ? 'outline' : 'default'}
|
|
117
|
+
class="flex-1"
|
|
118
|
+
onclick={() => toggleConnect(user.id)}
|
|
119
|
+
>
|
|
120
|
+
{#if isConnected(user.id)}
|
|
121
|
+
<Check class="h-3.5 w-3.5 mr-1" />{t('network.connected')}
|
|
122
|
+
{:else}
|
|
123
|
+
<Plus class="h-3.5 w-3.5 mr-1" />{t('network.connect')}
|
|
124
|
+
{/if}
|
|
125
|
+
</Button>
|
|
126
|
+
<Button size="sm" variant="ghost">
|
|
127
|
+
<ExternalLink class="h-3.5 w-3.5" />
|
|
128
|
+
</Button>
|
|
129
|
+
</div>
|
|
130
|
+
</Card.CardContent>
|
|
131
|
+
</Card.Card>
|
|
132
|
+
{/each}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
@@ -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;
|