Skip to content

Commit 1a8df74

Browse files
committed
feat: complete high-fidelity migration of LinkHub dashboard to Stitch Luminous Curator design system
1 parent 104aa18 commit 1a8df74

14 files changed

Lines changed: 1053 additions & 1968 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
2+
version = 1
3+
name = "Linkhub"
4+
5+
[setup]
6+
script = ""
7+
8+
[[actions]]
9+
name = "Run"
10+
icon = "run"
11+
command = "npm run dev"

link-sharing-frontend/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<link rel="preconnect" href="https://fonts.googleapis.com">
77
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
88
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
9+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet">
910
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
1011
<meta name="description" content="LinkHub — The next-generation link-in-bio platform for creators. Share all your links, connect socials, and track analytics with one beautiful page." />
1112
<meta name="theme-color" content="#0a0a0a" />

link-sharing-frontend/src/Components/dashboard/DashboardCard.jsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
11
import React from "react";
22
import { cx } from "./dashboardUtils";
33

4+
/**
5+
* DashboardCard — Glassmorphism surface card (Stitch Luminous Curator)
6+
* Uses tonal layering (surface-container hierarchy) over hard borders.
7+
* The "No-Line Rule": boundaries defined by bg shift + ghost border at 10% opacity.
8+
*/
49
const DashboardCard = React.memo(function DashboardCard({
510
children,
611
className,
12+
as: Tag = "section",
713
}) {
814
return (
9-
<section
15+
<Tag
1016
className={cx(
11-
"rounded-[32px] border border-[var(--saas-border)] bg-[var(--saas-card)] backdrop-blur-md p-6 sm:p-8 text-[var(--saas-text-primary)] transition-all duration-300 hover:border-[var(--saas-border-hover)] hover:bg-[var(--saas-card-hover)] shadow-sm",
17+
// Base glass surface
18+
"rounded-[28px]",
19+
"border border-[var(--saas-border)]",
20+
"bg-[var(--saas-bg-surface)]/80",
21+
"backdrop-blur-[20px]",
22+
"p-6 sm:p-8",
23+
"text-[var(--saas-text-primary)]",
24+
// Smooth transitions
25+
"transition-all duration-300",
26+
// Hover: ghost border reveals slightly + subtle bg shift
27+
"hover:border-[var(--saas-border-hover)]/60",
28+
"hover:bg-[var(--saas-bg-elevated)]/50",
29+
// Layered depth shadow (no pure black; uses accent-tinted shadow)
30+
"shadow-[0_4px_24px_rgba(0,0,0,0.18),0_1px_4px_rgba(0,0,0,0.12)]",
1231
className,
1332
)}
1433
>
1534
{children}
16-
</section>
35+
</Tag>
1736
);
1837
});
1938

Lines changed: 78 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,101 @@
1-
import React from "react";
2-
import { Globe2, Link2 } from "lucide-react";
3-
import { getAvatarUrl, getConnectedPlatforms, getVisibleLinks, formatCompactNumber } from "./dashboardUtils";
1+
import { useMemo } from "react";
2+
import { getAvatarUrl, getBannerUrl } from "./dashboardUtils";
43

5-
const MobilePreview = React.memo(function MobilePreview({
4+
export default function MobilePreview({
65
user,
76
links,
87
socialStats,
8+
analytics
99
}) {
1010
const avatarUrl = getAvatarUrl(user);
11-
const visibleLinks = getVisibleLinks(links).slice(0, 5);
12-
const connectedPlatforms = getConnectedPlatforms(user);
11+
const bannerUrl = getBannerUrl(user);
1312

1413
return (
15-
<div className="mx-auto w-full max-w-[320px] rounded-[48px] border border-[var(--saas-border)] bg-[#050505] p-2.5 shadow-[0_40px_100px_rgba(0,0,0,0.6)] ring-1 ring-white/10 ring-inset">
16-
<div className="mx-auto mb-3 h-6 w-28 rounded-full bg-black/80 flex items-center justify-center">
17-
<div className="w-10 h-1 rounded-full bg-white/10" />
18-
</div>
19-
20-
<div className="overflow-hidden rounded-[38px] border border-[var(--saas-border)] bg-gradient-to-b from-[#0e0e11] to-[#050505] p-6 relative">
21-
{/* Ambient background glow */}
22-
<div className="absolute -top-20 -left-20 w-40 h-40 bg-[var(--saas-accent-primary)] opacity-10 blur-[60px] rounded-full pointer-events-none" />
14+
<div className="flex flex-col items-center">
15+
{/* Mock Phone Frame */}
16+
<div className="relative w-[340px] aspect-[9/18.5] bg-[#0b0e14] rounded-[3.5rem] phone-frame overflow-hidden flex flex-col font-['Inter'] ring-1 ring-white/5 shadow-2xl">
17+
18+
{/* Phone Notch Shadow */}
19+
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-32 h-6 bg-surface-container-highest rounded-b-2xl z-20"></div>
2320

24-
<div className="flex flex-col items-center text-center relative z-10">
25-
<div className="h-22 w-22 p-1 overflow-hidden rounded-[32px] border border-[var(--saas-border)] bg-white/5 shadow-2xl relative group">
26-
<div className="absolute inset-0 bg-[var(--saas-accent-gradient)] opacity-20 blur-xl group-hover:opacity-40 transition-opacity" />
27-
{avatarUrl ? (
28-
<img
29-
src={avatarUrl}
30-
alt={user?.name || user?.username || "Creator avatar"}
31-
className="h-full w-full object-cover rounded-[28px] relative z-10 shadow-lg"
32-
/>
21+
{/* Screen Content Wrapper */}
22+
<div className="h-full w-full overflow-y-auto no-scrollbar relative flex flex-col">
23+
24+
{/* Cover / Banner Section */}
25+
<div className="h-44 w-full relative shrink-0">
26+
{bannerUrl ? (
27+
<img src={bannerUrl} className="w-full h-full object-cover" alt="Banner" />
3328
) : (
34-
<div className="flex h-full w-full items-center justify-center text-2xl font-black text-white bg-[var(--saas-accent-gradient)] relative z-10 rounded-[28px]">
35-
{(user?.username || user?.name || "L").charAt(0).toUpperCase()}
36-
</div>
29+
<div className="w-full h-full bg-surface-container-highest/50" />
3730
)}
31+
<div className="absolute inset-0 bg-gradient-to-t from-[#0b0e14] via-[#0b0e14]/40 to-transparent" />
3832
</div>
39-
40-
<div className="mt-5">
41-
<h4 className="text-xl font-black text-white tracking-tight leading-none">
42-
@{user?.username || "creator"}
43-
</h4>
44-
<div className="mt-2.5 mx-auto h-0.5 w-6 rounded-full bg-[var(--saas-accent-primary)]" />
45-
<p className="mt-4 text-[13px] leading-relaxed font-medium text-white/50 px-2 line-clamp-3">
46-
{user?.bio || "Crafting something extraordinary for the digital world."}
33+
34+
{/* Profile Identity Details */}
35+
<div className="px-6 -mt-16 relative z-10 flex flex-col items-center text-center">
36+
<div className="w-24 h-24 rounded-full p-0.5 bg-gradient-to-tr from-primary via-primary-container to-tertiary shadow-2xl shadow-primary/20">
37+
<div className="w-full h-full rounded-full border-4 border-[#0b0e14] overflow-hidden bg-surface-dim">
38+
{avatarUrl ? (
39+
<img src={avatarUrl} className="w-full h-full object-cover" alt="Avatar" />
40+
) : (
41+
<div className="w-full h-full bg-surface-container-high flex items-center justify-center text-primary/40">
42+
<span className="material-symbols-outlined text-4xl">person</span>
43+
</div>
44+
)}
45+
</div>
46+
</div>
47+
48+
<div className="mt-4 space-y-1">
49+
<h3 className="text-xl font-black text-white tracking-tight uppercase">
50+
{user?.full_name || "Julian Marcus"}
51+
</h3>
52+
<p className="text-sm font-bold text-primary tracking-widest uppercase opacity-80">
53+
@{user?.username || "creator_hub"}
54+
</p>
55+
</div>
56+
57+
<p className="mt-4 text-[11px] leading-relaxed text-on-surface-variant font-medium px-4">
58+
{user?.bio || "Digital nomad & minimalist designer. Sharing my journey through pixels and code."}
4759
</p>
4860
</div>
49-
</div>
50-
51-
<div className="mt-8 flex flex-wrap justify-center gap-2 relative z-10">
52-
{connectedPlatforms.length ? (
53-
connectedPlatforms.slice(0, 4).map((platform) => {
54-
const Icon = platform.icon;
55-
const stat = socialStats?.[platform.socialKey];
56-
const count =
57-
stat?.followers ??
58-
stat?.subscriberCount ??
59-
stat?.subscribers ??
60-
stat?.memberCount ??
61-
stat?.members;
6261

63-
return (
64-
<div
65-
key={platform.key}
66-
className="flex items-center gap-2 rounded-2xl border border-white/5 bg-white/5 px-4 py-2 text-white/90 backdrop-blur-sm transition-all hover:bg-white/10"
62+
{/* Dynamic Link Matrices */}
63+
<div className="mt-10 px-6 space-y-4 pb-20 w-full">
64+
{links && links.length > 0 ? (
65+
links.map((link) => (
66+
<div
67+
key={link.id}
68+
className="w-full py-4.5 px-6 glass-panel border border-white/5 rounded-2xl flex items-center justify-between group cursor-pointer transition-all hover:bg-white/5 active:scale-[0.98]"
6769
>
68-
<Icon className="h-3.5 w-3.5" style={{ color: platform.color }} />
69-
<span className="text-[10px] font-black uppercase tracking-wider">
70-
{count ? formatCompactNumber(count) : "Live"}
71-
</span>
72-
</div>
73-
);
74-
})
75-
) : (
76-
<div className="h-8 w-24 rounded-full bg-white/5 animate-pulse" />
77-
)}
78-
</div>
79-
80-
<div className="mt-8 space-y-3 relative z-10">
81-
{visibleLinks.length ? (
82-
visibleLinks.map((link) => (
83-
<div
84-
key={link.id}
85-
className="group flex items-center justify-between rounded-2xl border border-white/5 bg-white/5 px-4 py-4 transition-all hover:bg-white/10 hover:border-white/10 hover:-translate-y-0.5 shadow-sm"
86-
>
87-
<div className="min-w-0">
88-
<p className="truncate text-sm font-bold text-white tracking-tight">
89-
{link.title}
90-
</p>
91-
<p className="mt-1 truncate text-[10px] font-bold text-white/30 uppercase tracking-widest font-mono">
92-
{link.url.replace(/^https?:\/\/(www\.)?/, '')}
93-
</p>
94-
</div>
95-
<div className="h-9 w-9 shrink-0 flex items-center justify-center rounded-xl bg-white/5 text-white/40 group-hover:bg-[var(--saas-accent-primary)] group-hover:text-black transition-all">
96-
<Link2 className="h-4 w-4" />
70+
<div className="flex items-center gap-4">
71+
<div className="w-10 h-10 rounded-xl bg-surface-container-highest flex items-center justify-center text-primary shadow-inner">
72+
<span className="material-symbols-outlined text-xl">
73+
{link.platform === "General" ? "rocket_launch" : "alternate_email"}
74+
</span>
75+
</div>
76+
<span className="text-sm font-bold text-white tracking-wide uppercase">{link.title}</span>
77+
</div>
78+
<span className="material-symbols-outlined text-on-surface-variant text-[16px] opacity-40">chevron_right</span>
9779
</div>
80+
))
81+
) : (
82+
<div className="space-y-4">
83+
{[1, 2, 3].map(i => (
84+
<div key={i} className="w-full h-16 bg-white/5 rounded-2xl border border-white/5 opacity-40 animate-pulse" />
85+
))}
9886
</div>
99-
))
100-
) : (
101-
[1, 2, 3].map(i => (
102-
<div key={i} className="h-16 w-full rounded-2xl bg-white/5 animate-pulse" />
103-
))
104-
)}
105-
</div>
87+
)}
88+
</div>
10689

107-
<div className="mt-8 flex items-center justify-center gap-2.5 py-4 border-t border-white/5">
108-
<Globe2 className="h-3.5 w-3.5 text-white/20" />
109-
<span className="text-[10px] font-black uppercase tracking-[0.2em] text-white/40">
110-
linkhub.to/{user?.username || "identity"}
111-
</span>
90+
{/* Social Logo Watermark */}
91+
<div className="mt-auto py-8 flex justify-center opacity-10">
92+
<span className="text-[10px] font-black tracking-[0.5em] text-white">LINKHUB</span>
93+
</div>
11294
</div>
95+
96+
{/* Home Interaction Bar */}
97+
<div className="absolute bottom-1.5 left-1/2 -translate-x-1/2 w-32 h-1 bg-white/20 rounded-full z-20"></div>
11398
</div>
11499
</div>
115100
);
116-
});
117-
118-
export default MobilePreview;
101+
}

0 commit comments

Comments
 (0)