Skip to content

Commit 62cefb5

Browse files
committed
chore: bump version to 0.5.1 in package.json and update profile handling
- Updated version number from 0.5.0 to 0.5.1 in package.json and Cargo files. - Introduced new profile management functions in profile.ts for better user profile handling. - Enhanced hooks for deep linking and profile status to ensure users have complete profiles. - Updated login and user pages to utilize new profile functions for improved user experience.
1 parent 37741c5 commit 62cefb5

File tree

9 files changed

+167
-36
lines changed

9 files changed

+167
-36
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "codexia",
33
"private": true,
4-
"version": "0.5.0",
4+
"version": "0.5.1",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "codexia"
3-
version = "0.5.0"
3+
version = "0.5.1"
44
description = "A powerful IDE/GUI and Toolkit for Codex CLI"
55
authors = ["milisp"]
66
edition = "2021"

src/hooks/useDeepLink.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import supabase from "@/lib/supabase";
2+
import { ensureProfileRecord, mapProfileRow } from "@/lib/profile";
23
import { onOpenUrl } from "@tauri-apps/plugin-deep-link";
34
import { useEffect, useState } from "react";
45
import { useNavigate } from "react-router-dom";
@@ -35,26 +36,23 @@ export const useDeepLink = () => {
3536
if (error) throw error;
3637

3738
if (data.session) {
38-
// Decide where to go based on profile completeness
3939
try {
40-
const userId = data.session.user.id;
41-
const { data: profile, error: profileError } = await supabase
40+
const user = data.session.user;
41+
const { data: profileRow } = await supabase
4242
.from("profiles")
43-
.select("id, bio, website, github_url, x_url")
44-
.eq("id", userId)
43+
.select("id, full_name, avatar_url, bio, website, github_url, x_url, updated_at")
44+
.eq("id", user.id)
4545
.maybeSingle();
4646

47-
if (profileError) throw profileError;
48-
49-
const hasProfileInfo = Boolean(
50-
profile && (profile.bio || profile.website || profile.github_url || profile.x_url)
51-
);
47+
const profile = mapProfileRow(profileRow);
48+
if (!profile) {
49+
await ensureProfileRecord(user);
50+
}
5251

5352
toast.success("User authenticated successfully");
54-
navigate(hasProfileInfo ? "/" : "/profile?onboarding=1", { replace: true });
53+
navigate("/", { replace: true });
5554
return;
5655
} catch {
57-
// On any profile lookup error, fall back to home
5856
toast.success("User authenticated successfully");
5957
navigate("/", { replace: true });
6058
return;

src/hooks/useProfileStatus.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState } from 'react'
22
import supabase, { isSupabaseConfigured } from '@/lib/supabase'
33
import { useAuth } from '@/hooks/useAuth'
4+
import { ensureProfileRecord, mapProfileRow, type ProfileRecord } from '@/lib/profile'
45

56
// Returns whether the current (authenticated) user has a completed profile.
67
// If Supabase is not configured or there is no user, the check is not applicable
@@ -30,8 +31,14 @@ export function useProfileStatus() {
3031
.eq('id', userId!)
3132
.maybeSingle()
3233
if (error) throw error
34+
let profile: ProfileRecord | null = mapProfileRow(data)
35+
36+
if (!profile && user) {
37+
profile = await ensureProfileRecord(user)
38+
}
39+
3340
const ok = Boolean(
34-
data && (data.bio || data.website || data.github_url || data.x_url)
41+
profile && (profile.bio || profile.website || profile.github_url || profile.x_url)
3542
)
3643
if (!cancelled) setHasProfile(ok)
3744
} catch {
@@ -45,8 +52,7 @@ export function useProfileStatus() {
4552
return () => {
4653
cancelled = true
4754
}
48-
}, [requiresProfileCheck, userId])
55+
}, [requiresProfileCheck, userId, user])
4956

5057
return { hasProfile, loading, requiresProfileCheck }
5158
}
52-

src/lib/profile.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import type { User } from '@supabase/supabase-js'
2+
import supabase, { isSupabaseConfigured } from '@/lib/supabase'
3+
4+
export type ProfileRecord = {
5+
id: string
6+
full_name?: string | null
7+
avatar_url?: string | null
8+
bio: string | null
9+
website: string | null
10+
github_url: string | null
11+
x_url: string | null
12+
updated_at?: string | null
13+
}
14+
15+
function githubUrlFromMetadata(metadata: Record<string, unknown> | null): string | null {
16+
if (!metadata) return null
17+
const direct = metadata.html_url
18+
if (typeof direct === 'string' && direct.startsWith('http')) return direct
19+
const handleEntry = [metadata.user_name, metadata.preferred_username, metadata.nickname].find(
20+
(value) => typeof value === 'string' && value.length > 0,
21+
)
22+
if (typeof handleEntry === 'string' && handleEntry.length > 0) {
23+
return `https://github.com/${handleEntry}`
24+
}
25+
return null
26+
}
27+
28+
export function profileDefaultsFromMetadata(user: User) {
29+
const metadata = (user.user_metadata as Record<string, unknown> | null) ?? null
30+
const fullName = typeof metadata?.full_name === 'string' ? metadata.full_name : null
31+
const avatarUrl = typeof metadata?.avatar_url === 'string' ? metadata.avatar_url : null
32+
const githubUrl = githubUrlFromMetadata(metadata)
33+
return { full_name: fullName, avatar_url: avatarUrl, github_url: githubUrl }
34+
}
35+
36+
export function mapProfileRow(row: unknown): ProfileRecord | null {
37+
if (!row || typeof row !== 'object') return null
38+
const record = row as Record<string, unknown>
39+
const idRaw = record.id
40+
if (typeof idRaw !== 'string') return null
41+
return {
42+
id: idRaw,
43+
full_name: typeof record.full_name === 'string' ? record.full_name : null,
44+
avatar_url: typeof record.avatar_url === 'string' ? record.avatar_url : null,
45+
bio: (record.bio ?? null) as string | null,
46+
website: (record.website ?? null) as string | null,
47+
github_url: (record.github_url ?? null) as string | null,
48+
x_url: (record.x_url ?? null) as string | null,
49+
updated_at: (record.updated_at ?? null) as string | null,
50+
}
51+
}
52+
53+
/**
54+
* Ensure the current user has a profile row. Returns the created or existing profile.
55+
*/
56+
export async function ensureProfileRecord(user: User): Promise<ProfileRecord | null> {
57+
if (!user || !isSupabaseConfigured || !supabase) return null
58+
59+
const defaults = profileDefaultsFromMetadata(user)
60+
61+
const payload = {
62+
id: user.id,
63+
full_name: defaults.full_name,
64+
avatar_url: defaults.avatar_url,
65+
github_url: defaults.github_url,
66+
}
67+
68+
const selectColumns = 'id, full_name, avatar_url, bio, website, github_url, x_url, updated_at'
69+
70+
const insertResult = await supabase
71+
.from('profiles')
72+
.insert(payload)
73+
.select(selectColumns)
74+
.maybeSingle()
75+
76+
if (insertResult.error) {
77+
if (insertResult.error.code === '23505') {
78+
const existing = await supabase
79+
.from('profiles')
80+
.select(selectColumns)
81+
.eq('id', user.id)
82+
.maybeSingle()
83+
if (existing.error) throw existing.error
84+
return mapProfileRow(existing.data)
85+
}
86+
throw insertResult.error
87+
}
88+
89+
return mapProfileRow(insertResult.data)
90+
}

src/pages/login.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Input } from "@/components/ui/input";
33
import { Label } from "@/components/ui/label";
44
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
55
import supabase, { isSupabaseConfigured } from "@/lib/supabase";
6+
import { ensureProfileRecord, mapProfileRow, type ProfileRecord } from "@/lib/profile";
67
import { open } from "@tauri-apps/plugin-shell";
78
import { Github } from "lucide-react";
89
import { Navigate } from "react-router-dom";
@@ -35,11 +36,12 @@ export default function AuthPage() {
3536
.eq("id", user.id)
3637
.maybeSingle();
3738
if (error) throw error;
38-
if (!data || !(data.bio || data.website || data.github_url || data.x_url)) {
39-
setRedirect("/profile?onboarding=1");
40-
} else {
41-
setRedirect("/");
39+
const profile: ProfileRecord | null = mapProfileRow(data);
40+
if (!profile) {
41+
await ensureProfileRecord(user);
4242
}
43+
// Always send the user home; specific routes remain gated by useProfileStatus.
44+
setRedirect("/");
4345
} catch (_e) {
4446
setRedirect("/");
4547
}

src/pages/profile.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useMemo, useState } from "react";
22
import { useAuth } from "@/hooks/useAuth";
33
import supabase, { isSupabaseConfigured } from "@/lib/supabase";
4+
import { profileDefaultsFromMetadata } from "@/lib/profile";
45
import { Button } from "@/components/ui/button";
56
import { Input } from "@/components/ui/input";
67
import { Label } from "@/components/ui/label";
@@ -39,6 +40,9 @@ export default function ProfilePage() {
3940
const load = async () => {
4041
try {
4142
const client = supabase!;
43+
const defaults = profileDefaultsFromMetadata(user);
44+
const githubFromMetadata = defaults.github_url ?? "";
45+
4246
const { data, error } = await client
4347
.from("profiles")
4448
.select("id, full_name, avatar_url, bio, website, github_url, x_url, updated_at")
@@ -48,15 +52,20 @@ export default function ProfilePage() {
4852
if (error) throw error;
4953

5054
setProfile(
51-
data ?? {
52-
id: user.id,
53-
full_name: user.user_metadata?.full_name ?? null,
54-
avatar_url: user.user_metadata?.avatar_url ?? null,
55-
bio: "",
56-
website: "",
57-
github_url: "",
58-
x_url: "",
59-
}
55+
(data
56+
? {
57+
...data,
58+
github_url: data.github_url ?? (githubFromMetadata || ""),
59+
}
60+
: {
61+
id: user.id,
62+
full_name: defaults.full_name ?? user.user_metadata?.full_name ?? null,
63+
avatar_url: defaults.avatar_url ?? user.user_metadata?.avatar_url ?? null,
64+
bio: "",
65+
website: "",
66+
github_url: githubFromMetadata,
67+
x_url: "",
68+
}) as Profile
6069
);
6170
} catch (e: any) {
6271
setError(e?.message ?? "Failed to load profile");
@@ -105,7 +114,9 @@ export default function ProfilePage() {
105114
<CardHeader>
106115
<CardTitle>Profile</CardTitle>
107116
<CardDescription>
108-
{isOnboarding ? "Complete your profile to continue" : "Manage your public profile"}
117+
{isOnboarding
118+
? "Complete your profile to continue"
119+
: "Manage your public profile. Your profile will only be public if you share a project."}
109120
</CardDescription>
110121
</CardHeader>
111122
<CardContent className="space-y-4">

src/pages/user.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState } from "react";
22
import { useParams, Link } from "react-router-dom";
33
import supabase, { isSupabaseConfigured } from "@/lib/supabase";
4+
import { ensureProfileRecord, mapProfileRow, type ProfileRecord } from "@/lib/profile";
45
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
56
import { Badge } from "@/components/ui/badge";
67
import { Button } from "@/components/ui/button";
@@ -25,6 +26,19 @@ type Project = {
2526
url?: string | null;
2627
};
2728

29+
function profileFromRecord(record: ProfileRecord | null): Profile | null {
30+
if (!record) return null;
31+
return {
32+
id: record.id,
33+
bio: record.bio,
34+
website: record.website,
35+
github_url: record.github_url,
36+
x_url: record.x_url,
37+
full_name: record.full_name ?? null,
38+
avatar_url: record.avatar_url ?? null,
39+
};
40+
}
41+
2842
export default function PublicUserPage() {
2943
const params = useParams();
3044
const userId = params.id as string | undefined;
@@ -63,11 +77,21 @@ export default function PublicUserPage() {
6377
.limit(2),
6478
]);
6579
if (perr) throw perr;
66-
// If profile absent, fall back to a placeholder so projects can still show
67-
if (!p) {
80+
81+
const existing = mapProfileRow(p);
82+
83+
if (!existing && isOwner && me) {
84+
const ensured = await ensureProfileRecord(me);
85+
if (ensured) {
86+
setProfile(profileFromRecord(ensured));
87+
} else {
88+
setProfile({ id: userId } as Profile);
89+
}
90+
} else if (!existing) {
91+
// If profile absent, fall back to a placeholder so projects can still show
6892
setProfile({ id: userId } as Profile);
6993
} else {
70-
setProfile(p as Profile);
94+
setProfile(profileFromRecord(existing));
7195
}
7296
if (!jerr && Array.isArray(prj)) setProjects(prj as Project[]);
7397
} catch (e: any) {
@@ -77,7 +101,7 @@ export default function PublicUserPage() {
77101
}
78102
};
79103
load();
80-
}, [userId]);
104+
}, [userId, isOwner, me]);
81105

82106
return (
83107
<div className="p-4 mx-auto w-full max-w-3xl space-y-4">

0 commit comments

Comments
 (0)