Skip to content

Commit 85ed9a2

Browse files
committed
feat(web): enhance user profile sharing and navigation updates
- Integrated new components for Unified VCP, including sections for identity, insights, and methodology. - Updated navigation links to reflect changes from "My Vibe" to "My VCP" and "Vibes" to "Repo VCPs". - Added support for user ID in the AuthenticatedDashboard for personalized experiences. - Refactored API routes and components to improve data handling and user profile story generation. - Enhanced NotificationDropdown and ShareActions components for better user engagement. This update improves the overall user experience by streamlining navigation and enriching the profile sharing features.
1 parent 975239d commit 85ed9a2

10 files changed

Lines changed: 372 additions & 86 deletions

File tree

apps/web/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@radix-ui/react-toast": "^1.2.15",
1515
"@supabase/ssr": "^0.5.2",
1616
"@supabase/supabase-js": "^2.47.14",
17+
"@vercel/og": "^0.8.6",
1718
"@vibed/core": "*",
1819
"@vibed/db": "*",
1920
"class-variance-authority": "^0.7.1",
@@ -30,6 +31,7 @@
3031
"devDependencies": {
3132
"@tailwindcss/postcss": "^4",
3233
"@types/node": "^20",
34+
"@types/qrcode": "^1.5.6",
3335
"@types/react": "^19",
3436
"@types/react-dom": "^19",
3537
"eslint": "^9",

apps/web/src/app/api/share/story/[userId]/route.tsx

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { NextRequest, NextResponse } from "next/server";
1010

1111
export const runtime = "edge";
1212

13+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
14+
type SupabaseClientInstance = any;
15+
1316
const STORY_WIDTH = 1080;
1417
const STORY_HEIGHT = 1920;
1518

@@ -73,8 +76,19 @@ const buildMetricsFromAxes = (axes: VibeAxes): ShareCardMetric[] => {
7376
];
7477
};
7578

79+
interface UserProfileRow {
80+
axes_json?: unknown;
81+
cards_json?: unknown;
82+
persona_id?: string;
83+
persona_name?: string | null;
84+
persona_tagline?: string | null;
85+
persona_confidence?: string | null;
86+
total_commits?: number | null;
87+
total_repos?: number | null;
88+
}
89+
7690
async function buildProfileStory(
77-
supabase: ReturnType<typeof createSupabaseServerClient>,
91+
supabase: SupabaseClientInstance,
7892
userId: string,
7993
appUrl: string
8094
): Promise<StoryData | null> {
@@ -86,27 +100,30 @@ async function buildProfileStory(
86100
.eq("user_id", userId)
87101
.maybeSingle();
88102

89-
if (!profile || typeof profile !== "object") return null;
103+
const profileRow = (profile ?? null) as UserProfileRow | null;
104+
if (!profileRow) return null;
90105

91-
const axes = isVibeAxes(profile.axes_json) ? profile.axes_json : null;
106+
const axes = isVibeAxes(profileRow.axes_json) ? profileRow.axes_json : null;
92107
const metrics = axes ? buildMetricsFromAxes(axes) : [];
93-
const highlight = parseCardsHighlight(profile.cards_json);
94-
const colors = STORY_THEMES[profile.persona_id ?? ""] ?? DEFAULT_THEME;
108+
const highlight = parseCardsHighlight(profileRow.cards_json);
109+
const colors = STORY_THEMES[profileRow.persona_id ?? ""] ?? DEFAULT_THEME;
95110
const subhead =
96-
profile.persona_tagline?.trim().length ? profile.persona_tagline : `${profile.persona_confidence} confidence`;
111+
profileRow.persona_tagline?.trim().length
112+
? profileRow.persona_tagline
113+
: `${profileRow.persona_confidence ?? "medium"} confidence`;
97114
const stats = [
98-
`${profile.total_commits?.toLocaleString() ?? "0"} commits`,
99-
`${profile.total_repos ?? 0} repos included`,
115+
`${profileRow.total_commits?.toLocaleString() ?? "0"} commits`,
116+
`${profileRow.total_repos ?? 0} repos included`,
100117
];
101118
const topAxes = axes ? formatAxesList(axes) : [];
102119

103120
return {
104121
headline: "My Vibed Profile",
105122
subhead,
106-
personaName: profile.persona_name ?? "Vibe Coder",
123+
personaName: profileRow.persona_name ?? "Vibe Coder",
107124
personaTagline: subhead,
108-
personaId: profile.persona_id ?? "balanced_builder",
109-
personaConfidence: profile.persona_confidence ?? "medium",
125+
personaId: profileRow.persona_id ?? "balanced_builder",
126+
personaConfidence: profileRow.persona_confidence ?? "medium",
110127
metrics: metrics.length ? metrics : stats.slice(0, 4).map((value, idx) => ({ label: ["Commits", "Repos", "Profile", "Vibes"][idx] ?? `Stat ${idx + 1}`, value })),
111128
highlight,
112129
topAxes,
@@ -117,7 +134,7 @@ async function buildProfileStory(
117134
}
118135

119136
async function buildJobStory(
120-
supabase: ReturnType<typeof createSupabaseServerClient>,
137+
supabase: SupabaseClientInstance,
121138
userId: string,
122139
jobId: string,
123140
appUrl: string
@@ -414,15 +431,16 @@ const renderStoryImage = async (story: StoryData, qrDataUrl: string) => {
414431

415432
export async function GET(
416433
request: NextRequest,
417-
{ params }: { params: { userId: string } }
434+
{ params }: { params: Promise<{ userId: string }> }
418435
) {
419436
const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? "https://www.vibed.app";
420437
const supabase = await createSupabaseServerClient();
421438
const {
422439
data: { user },
423440
} = await supabase.auth.getUser();
424441

425-
if (!user || user.id !== params.userId) {
442+
const { userId } = await params;
443+
if (!user || user.id !== userId) {
426444
return NextResponse.json({ error: "unauthorized" }, { status: 401 });
427445
}
428446

apps/web/src/app/page.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import {
1414
type VibeAxes,
1515
type VibeCommitEvent,
1616
} from "@vibed/core";
17+
import {
18+
UnifiedIdentitySection,
19+
UnifiedInsightSection,
20+
UnifiedAxesSection,
21+
EvolutionSection,
22+
RepoBreakdownSection,
23+
UnifiedMethodologySection,
24+
} from "@/components/vcp/unified";
1725

1826
const heroFeatures = [
1927
"A Vibe Coding Profile (VCP) built from AI-assisted engineering signals in your commit history",
@@ -796,7 +804,7 @@ export default async function Home({
796804
}
797805
: null;
798806

799-
return <AuthenticatedDashboard stats={stats} debugInfo={debugInfo} />;
807+
return <AuthenticatedDashboard stats={stats} debugInfo={debugInfo} userId={user.id} />;
800808
}
801809

802810
function MarketingLanding() {
@@ -952,9 +960,11 @@ function MarketingLanding() {
952960
function AuthenticatedDashboard({
953961
stats,
954962
debugInfo,
963+
userId,
955964
}: {
956965
stats: AuthStats;
957966
debugInfo: Record<string, unknown> | null;
967+
userId: string;
958968
}) {
959969
const isAxisValue = (v: unknown): v is { score: number; level: string; why: string[] } => {
960970
if (typeof v !== "object" || v === null) return false;
@@ -1136,7 +1146,7 @@ function AuthenticatedDashboard({
11361146
topAxes={topAxes}
11371147
insight={crossRepoInsight}
11381148
axes={stats.userProfile.axes as unknown as VibeAxes}
1139-
userId={user?.id ?? ""}
1149+
userId={userId}
11401150
/>
11411151
) : null}
11421152

@@ -1159,21 +1169,21 @@ function AuthenticatedDashboard({
11591169
{stats.completedJobs > 0 ? (
11601170
<>
11611171
<Link
1162-
href="/repos"
1172+
href="/settings/repos"
11631173
className="rounded-full border border-black/10 bg-white/80 px-4 py-1.5 text-xs font-semibold text-zinc-700 shadow-sm transition hover:bg-white"
11641174
>
11651175
Add repo
11661176
</Link>
11671177
<Link
1168-
href="/analysis"
1178+
href="/vibes"
11691179
className="rounded-full bg-zinc-900 px-4 py-1.5 text-xs font-semibold text-white shadow-sm transition hover:bg-zinc-800"
11701180
>
11711181
View Repo VCPs
11721182
</Link>
11731183
</>
11741184
) : (
11751185
<Link
1176-
href="/repos"
1186+
href="/settings/repos"
11771187
className="rounded-full bg-zinc-900 px-4 py-1.5 text-xs font-semibold text-white shadow-sm transition hover:bg-zinc-800"
11781188
>
11791189
Pick a repo
@@ -1468,7 +1478,7 @@ function AuthenticatedDashboard({
14681478
<div className="flex flex-wrap gap-3">
14691479
{stats.completedJobs === 0 ? (
14701480
<>
1471-
<Link href="/repos" className={wrappedTheme.primaryButton}>
1481+
<Link href="/settings/repos" className={wrappedTheme.primaryButton}>
14721482
Pick a repo
14731483
</Link>
14741484
<Link href="/security" className={wrappedTheme.secondaryButton}>
@@ -1477,10 +1487,10 @@ function AuthenticatedDashboard({
14771487
</>
14781488
) : (
14791489
<>
1480-
<Link href="/repos" className={wrappedTheme.secondaryButton}>
1490+
<Link href="/settings/repos" className={wrappedTheme.secondaryButton}>
14811491
Add repo
14821492
</Link>
1483-
<Link href="/analysis" className={wrappedTheme.primaryButton}>
1493+
<Link href="/vibes" className={wrappedTheme.primaryButton}>
14841494
View Repo VCPs
14851495
</Link>
14861496
</>

apps/web/src/components/notifications/NotificationDropdown.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,11 @@ export function NotificationDropdown() {
226226
{recentJobs.length > 0 && (
227227
<div className="border-t border-zinc-200 px-4 py-2">
228228
<Link
229-
href="/analysis"
229+
href="/vibes"
230230
onClick={() => setIsOpen(false)}
231231
className="block text-center text-xs font-medium text-zinc-500 hover:text-zinc-700"
232232
>
233-
View all reports
233+
View Repo VCPs
234234
</Link>
235235
</div>
236236
)}

apps/web/src/components/share/ShareActions.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export function ShareActions({
4747
shareTemplate,
4848
entityId,
4949
disabled = false,
50+
storyEndpoint,
5051
}: ShareActionsProps) {
5152
const [copied, setCopied] = useState(false);
5253
const [copiedLink, setCopiedLink] = useState(false);

apps/web/src/components/vcp/unified/EvolutionSection.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { cn } from "@/lib/utils";
33
interface EvolutionSectionProps {
44
/** Number of completed repo VCPs */
55
repoVcpCount: number;
6-
/** Number of persona shifts detected */
7-
vibeShifts: number;
8-
/** Dominant persona name (short) */
6+
/** Vibe shifts value (can be number, "New", or "Steady") */
7+
vibeShifts: string | number;
8+
/** Dominant persona name (short, typically first word of persona) */
99
dominantVibe: string | null;
1010
/** Helper text explaining shift history */
1111
shiftHelper?: string;

apps/web/src/components/vcp/unified/UnifiedIdentitySection.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ interface UnifiedIdentitySectionProps {
1818
clarity?: number;
1919
/** Number of completed analyses */
2020
completedJobs: number;
21+
/** Fallback: analyzed repos count (for "profile forming" state) */
22+
analyzedRepos?: number;
23+
/** Fallback: analyzed commits count (for "profile forming" state) */
24+
analyzedCommits?: number;
2125
/** Fallback persona (from latest job, if no profile) */
2226
latestPersona?: {
2327
label: string | null;
@@ -41,10 +45,13 @@ export function UnifiedIdentitySection({
4145
totalCommits,
4246
clarity,
4347
completedJobs,
48+
analyzedRepos,
49+
analyzedCommits,
4450
latestPersona,
4551
className,
4652
}: UnifiedIdentitySectionProps) {
4753
const hasProfile = Boolean(confidence && totalRepos);
54+
const isForming = !hasProfile && (analyzedRepos ?? 0) > 0;
4855

4956
return (
5057
<div
@@ -105,6 +112,14 @@ export function UnifiedIdentitySection({
105112
<span>·</span>
106113
<span>{clarity}% clarity</span>
107114
</>
115+
) : isForming ? (
116+
<>
117+
<span>{analyzedRepos} repos</span>
118+
<span>·</span>
119+
<span>{analyzedCommits?.toLocaleString()} commits</span>
120+
<span>·</span>
121+
<span>Profile forming</span>
122+
</>
108123
) : latestPersona ? (
109124
<>
110125
<span className="rounded-full bg-white/80 px-3 py-1 font-medium text-zinc-800 shadow-sm">

0 commit comments

Comments
 (0)