Implement custom links editing in ProfileView#137#182
Conversation
Added functionality to edit and save custom links for the profile view, including input validation and database update.Added the Supabase client import and implemented the handleSaveLinks function to update the profiles table. Wired the function to the Save Changes button within the Edit Links modal to successfully mutate the database and reload the view.
📝 WalkthroughWalkthrough
ChangesCustom Links on Profile
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/profile/ProfileView.tsx`:
- Around line 139-146: The newLinks variable in the ProfileView component is
hard-coded as a single-element array containing only the newly entered link,
which replaces all existing custom_links in the database causing data loss.
Instead of creating a one-element array for newLinks, first fetch the existing
custom_links array from the profiles table for the current user, then merge or
upsert the new link entry into that existing array before updating the database.
This way, previously saved links are preserved and the new/edited link is added
or modified within the complete list.
- Around line 295-337: The modal dialog in the isEditLinksOpen block lacks
essential accessibility attributes and keyboard handling. Add role="dialog" and
aria-modal="true" to the outer fixed-position div, provide an accessible name
using aria-labelledby pointing to the h3 "Edit Custom Links" heading, and add an
id to that heading. Implement focus management by using useEffect to move focus
into the modal (preferably to the first input with id "edit-link-label") when
isEditLinksOpen becomes true, and restore focus to the previously focused
element when the modal closes. Add an onKeyDown handler to the modal to detect
the Escape key and call setIsEditLinksOpen(false) to close the modal, enabling
keyboard users to escape without using the Cancel button.
- Around line 64-67: The ProfileExtras interface is closed on line 66 with a
closing brace, but line 67 contains a stray `updatedAt: string | null;`
declaration at module scope that is syntactically invalid and causes a compile
error. Remove the stray `updatedAt` declaration on line 67 since the field
already exists as part of the ProfileExtras interface definition above.
- Around line 245-268: The custom links anchor element in the customLinks.map
block is missing an aria-label attribute to indicate the link opens in a new
tab, which impacts screen reader accessibility, and uses an index-based key
(key={idx}) which is fragile if links are reordered or removed. Add an
aria-label attribute to the anchor tag that describes the action (e.g.,
mentioning it opens externally), and change the key from idx to a stable
identifier like link.url.
- Around line 130-156: The handleSaveLinks function references
setIsEditLinksOpen which lacks a corresponding useState declaration, causing a
compilation error. Add a useState hook near the existing state declarations
around line 118 to initialize the isEditLinksOpen state variable with a boolean
initial value, providing both the isEditLinksOpen variable and
setIsEditLinksOpen setter function used throughout the ProfileView component.
- Around line 130-156: In the handleSaveLinks function, replace the
document.getElementById calls for "edit-link-label" and "edit-link-url" with
state variables managed via useState hooks for controlled inputs. Replace the
window.location.reload() call with useRouter().refresh() imported from
next/navigation to refresh the page without a hard reload. Add URL scheme
validation before saving to ensure the URL only contains safe protocols like
http or https and reject unsafe schemes like javascript or data. Additionally,
ensure the parent component fetches customLinks from Supabase and passes them as
props to ProfileView, and consider wrapping the mutation in a Server Action that
uses revalidatePath() after a successful update to re-fetch the server data.
- Line 7: Replace the broken import statement from the non-existent module
`@/utils/supabase/client` with the correct import from the project's singleton:
change `import { createClient } from "`@/utils/supabase/client`";` to `import {
supabase } from "`@/lib/supabase`";`. Then locate the line in ProfileView.tsx
where `const supabase = createClient();` is called (around line 140) and remove
it, using the imported supabase singleton directly instead of trying to create a
new instance.
- Around line 94-96: In the ProfileView component invocation within the page
component, add logic to detect ownership by comparing the authenticated user's
identity with the profile username being viewed, and fetch the custom_links from
the Supabase profiles table. Then pass the derived isOwner boolean and fetched
customLinks array to the ProfileView component so that the ownership check on
line 271 (the `isOwner &&` condition for the edit pencil) and the custom links
rendering on line 245 become functional instead of remaining at their default
inert values.
- Around line 143-146: The update operation in ProfileView.tsx attempting to
update the custom_links column in the profiles table will fail because this
column does not exist in the database schema. Create a new database migration
that adds the custom_links column to the profiles table as a jsonb data type
using an ALTER TABLE statement with the if not exists clause to handle any edge
cases. Once the migration is applied to your database, the update operation
referencing custom_links will function correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 294e96ae-93e7-4772-b9c8-d44e9f57dc4d
📒 Files selected for processing (1)
src/components/profile/ProfileView.tsx
| import Image from "next/image"; | ||
| import Link from "next/link"; | ||
| import type { ContributorStats, Org, TechEntry, HeatmapWeek } from "@/types"; | ||
| import { createClient } from "@/utils/supabase/client"; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the imported module and its createClient export
fd -t f . src/utils/supabase 2>/dev/null
rg -nP 'export\s+(async\s+)?function\s+createClient|export\s+const\s+createClient' -g '**/utils/supabase/**'Repository: PRODHOSH/ossfolio
Length of output: 155
🏁 Script executed:
# Check what actually exists in src/
ls -la src/ 2>/dev/null | head -20Repository: PRODHOSH/ossfolio
Length of output: 481
🏁 Script executed:
# Search for supabase-related files anywhere in src/
find src -name "*supabase*" -type f 2>/dev/nullRepository: PRODHOSH/ossfolio
Length of output: 175
🏁 Script executed:
# Check the actual ProfileView.tsx file to see the import and line 140
cat -n src/components/profile/ProfileView.tsx | head -150Repository: PRODHOSH/ossfolio
Length of output: 5340
🏁 Script executed:
# Check for path alias configuration (tsconfig.json or similar)
cat tsconfig.json 2>/dev/null || cat tsconfig.base.json 2>/dev/null || cat vite.config.ts 2>/dev/nullRepository: PRODHOSH/ossfolio
Length of output: 857
🏁 Script executed:
# Read the existing supabase.ts to see what's exported
cat -n src/lib/supabase.tsRepository: PRODHOSH/ossfolio
Length of output: 811
🏁 Script executed:
# Double-check that src/utils doesn't exist at all
ls -la src/utils 2>/dev/null || echo "src/utils does not exist"Repository: PRODHOSH/ossfolio
Length of output: 180
🏁 Script executed:
# Verify the entire src/ directory structure
find src -type d | sortRepository: PRODHOSH/ossfolio
Length of output: 487
Remove broken import and use the established singleton from src/lib/supabase.ts.
The module @/utils/supabase/client does not exist. The import at line 7 will resolve to a non-existent path, and the createClient() call at line 140 will fail at runtime. Use the project's singleton instead: import { supabase } from "@/lib/supabase"; and replace const supabase = createClient(); with direct use of the imported singleton.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/profile/ProfileView.tsx` at line 7, Replace the broken import
statement from the non-existent module `@/utils/supabase/client` with the correct
import from the project's singleton: change `import { createClient } from
"`@/utils/supabase/client`";` to `import { supabase } from "`@/lib/supabase`";`.
Then locate the line in ProfileView.tsx where `const supabase = createClient();`
is called (around line 140) and remove it, using the imported supabase singleton
directly instead of trying to create a new instance.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| const newLinks = [{ label: labelInput.value, url: urlInput.value }]; | ||
| const supabase = createClient(); | ||
|
|
||
| // Update the Supabase database exactly as the issue requested | ||
| const { error } = await supabase | ||
| .from('profiles') | ||
| .update({ custom_links: newLinks }) | ||
| .eq('username', user.login); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Save overwrites all custom links with a single entry — data loss.
newLinks is hard-coded to a one-element array and replaces the entire custom_links column. A user with multiple existing links loses all but the one edited in the modal. The feature spec calls for add/change of multiple links. Persist a merged/edited array rather than a single-element replacement.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/profile/ProfileView.tsx` around lines 139 - 146, The newLinks
variable in the ProfileView component is hard-coded as a single-element array
containing only the newly entered link, which replaces all existing custom_links
in the database causing data loss. Instead of creating a one-element array for
newLinks, first fetch the existing custom_links array from the profiles table
for the current user, then merge or upsert the new link entry into that existing
array before updating the database. This way, previously saved links are
preserved and the new/edited link is added or modified within the complete list.
| const { error } = await supabase | ||
| .from('profiles') | ||
| .update({ custom_links: newLinks }) | ||
| .eq('username', user.login); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Look for RLS policy / migration governing UPDATE on profiles
rg -nP -C2 'profiles' -g '**/*.sql'
rg -nP -i 'policy|row level security|rls' -g '**/*.sql'Repository: PRODHOSH/ossfolio
Length of output: 155
🏁 Script executed:
#!/bin/bash
# Explore repo structure to understand database setup
git ls-files | head -30
echo "---"
# Look for common database/supabase config files
find . -maxdepth 3 -type f \( -name "*supabase*" -o -name "*migrations*" -o -name "*schema*" \) 2>/dev/null | head -20
echo "---"
# Check for TypeScript/JS migration or database definition files
fd -e ts -e js -e json | grep -i -E "(migration|schema|supabase|database|rls|policy)" | head -20Repository: PRODHOSH/ossfolio
Length of output: 1001
🏁 Script executed:
#!/bin/bash
# Read the schema and migration files to check for RLS policies
echo "=== supabase/schema.sql ==="
cat ./supabase/schema.sql
echo ""
echo "=== supabase/migrations/20260520000001_initial_schema.sql ==="
cat ./supabase/migrations/20260520000001_initial_schema.sqlRepository: PRODHOSH/ossfolio
Length of output: 3811
🏁 Script executed:
#!/bin/bash
# Read the ProfileView.tsx file to see the full context around lines 143-146
echo "=== ProfileView.tsx context ==="
sed -n '130,160p' ./src/components/profile/ProfileView.tsx
echo ""
# Check if custom_links column exists in schema
rg -i "custom_links" ./supabase/Repository: PRODHOSH/ossfolio
Length of output: 1318
🏁 Script executed:
#!/bin/bash
# Check for isOwner check and understand the user object/session context
echo "=== Looking for isOwner check in ProfileView.tsx ==="
rg -n "isOwner" ./src/components/profile/ProfileView.tsx -B2 -A2
echo ""
echo "=== Looking for user object definition and session context ==="
rg -n "user\." ./src/components/profile/ProfileView.tsx | head -20
echo ""
echo "=== Check how createClient is implemented ==="
cat ./src/lib/supabase.tsRepository: PRODHOSH/ossfolio
Length of output: 2593
🏁 Script executed:
#!/bin/bash
# Check if custom_links column is defined anywhere in the schema files
echo "=== Searching for custom_links in schema files ==="
rg -i "custom_links" ./supabase/
echo ""
# Check the full schema.sql for all columns
echo "=== Full profiles table definition from schema.sql ==="
sed -n '/^create table public.profiles/,/^);/p' ./supabase/schema.sql
echo ""
# Check if there are any other migration files
echo "=== All migration files ==="
find ./supabase/migrations -type f -name "*.sql" | sortRepository: PRODHOSH/ossfolio
Length of output: 911
🏁 Script executed:
#!/bin/bash
# Check the second migration file
echo "=== Migration 20260605000001_add_profile_score.sql ==="
cat ./supabase/migrations/20260605000001_add_profile_score.sql
echo ""
# Check if there are any more migration files
ls -la ./supabase/migrations/
echo ""
# Search the entire codebase for custom_links references
echo "=== All references to custom_links in the codebase ==="
rg "custom_links" . --type ts --type tsx --type sqlRepository: PRODHOSH/ossfolio
Length of output: 1240
🏁 Script executed:
#!/bin/bash
# Simple rg search for custom_links without type restrictions
echo "=== All references to custom_links ==="
rg "custom_links" .
echo ""
# Check the RLS policy more carefully
echo "=== RLS policies in schema ==="
rg -A2 "create policy.*profiles.*update" ./supabase/Repository: PRODHOSH/ossfolio
Length of output: 617
Add custom_links column to the profiles schema before this update will work.
The custom_links column referenced in the update at line 146 does not exist in the profiles table. The update will fail with a column-not-found error. Additionally, while the isOwner check is purely cosmetic (only gates the button), the existing RLS policy properly enforces authorization:
create policy "profiles_update" on public.profiles for update using (auth.uid() = id);This policy correctly restricts UPDATE to authenticated users whose Supabase ID matches the profile being updated, so unauthorized clients cannot bypass it. Add the custom_links column to your schema migration, e.g.:
alter table public.profiles
add column if not exists custom_links jsonb;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/profile/ProfileView.tsx` around lines 143 - 146, The update
operation in ProfileView.tsx attempting to update the custom_links column in the
profiles table will fail because this column does not exist in the database
schema. Create a new database migration that adds the custom_links column to the
profiles table as a jsonb data type using an ALTER TABLE statement with the if
not exists clause to handle any edge cases. Once the migration is applied to
your database, the update operation referencing custom_links will function
correctly.
| {isEditLinksOpen && ( | ||
| <div style={{ | ||
| position: "fixed", top: 0, left: 0, width: "100vw", height: "100vh", | ||
| backgroundColor: "rgba(0,0,0,0.5)", zIndex: 9999, | ||
| display: "flex", justifyContent: "center", alignItems: "center" | ||
| }}> | ||
| <div style={{ | ||
| background: "var(--color-canvas-default, #fff)", | ||
| padding: "24px", borderRadius: "12px", width: "400px", maxWidth: "90%", | ||
| border: "1px solid var(--color-hairline)", | ||
| boxShadow: "0 8px 24px rgba(0,0,0,0.12)" | ||
| }}> | ||
| <h3 style={{ margin: "0 0 8px 0", color: "var(--color-ink)" }}>Edit Custom Links</h3> | ||
| <p style={{ margin: "0 0 20px 0", fontSize: "13px", color: "var(--color-ink-mute)" }}>Add a custom link to display on your profile.</p> | ||
|
|
||
| <div style={{ display: "flex", flexDirection: "column", gap: "12px", marginBottom: "24px" }}> | ||
| <div> | ||
| <label style={{ display: "block", fontSize: "12px", marginBottom: "6px", color: "var(--color-ink-mute)" }}>Link Label</label> | ||
| <input id="edit-link-label" type="text" placeholder="e.g. My Portfolio" defaultValue={customLinks?.[0]?.label || ""} style={{ width: "95%", padding: "8px", borderRadius: "6px", border: "1px solid var(--color-hairline)", background: "var(--color-canvas-soft)", color: "var(--color-ink)" }} /> | ||
| </div> | ||
| <div> | ||
| <label style={{ display: "block", fontSize: "12px", marginBottom: "6px", color: "var(--color-ink-mute)" }}>Link URL</label> | ||
| <input id="edit-link-url" type="url" placeholder="https://..." defaultValue={customLinks?.[0]?.url || ""} style={{ width: "95%", padding: "8px", borderRadius: "6px", border: "1px solid var(--color-hairline)", background: "var(--color-canvas-soft)", color: "var(--color-ink)" }} /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div style={{ display: "flex", justifyContent: "flex-end", gap: "10px" }}> | ||
| <button | ||
| onClick={() => setIsEditLinksOpen(false)} | ||
| style={{ padding: "8px 16px", borderRadius: "6px", border: "1px solid var(--color-hairline-strong)", background: "transparent", color: "var(--color-ink)", cursor: "pointer", fontSize: "13px" }} | ||
| > | ||
| Cancel | ||
| </button> | ||
| <button | ||
| onClick={handleSaveLinks} | ||
| style={{ padding: "8px 16px", borderRadius: "6px", border: "none", background: "#3ecf8e", color: "#fff", cursor: "pointer", fontSize: "13px", fontWeight: 500 }} | ||
| > | ||
| Save Changes | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Edit modal lacks accessibility semantics.
The overlay/dialog has no role="dialog", aria-modal="true", accessible name, focus trap/initial focus, or ESC-to-close handling. Keyboard and screen-reader users can’t reliably operate or escape it, and focus leaks to the background content. Add the dialog roles, move focus into the modal on open, restore it on close, and handle the Escape key.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/profile/ProfileView.tsx` around lines 295 - 337, The modal
dialog in the isEditLinksOpen block lacks essential accessibility attributes and
keyboard handling. Add role="dialog" and aria-modal="true" to the outer
fixed-position div, provide an accessible name using aria-labelledby pointing to
the h3 "Edit Custom Links" heading, and add an id to that heading. Implement
focus management by using useEffect to move focus into the modal (preferably to
the first input with id "edit-link-label") when isEditLinksOpen becomes true,
and restore focus to the previously focused element when the modal closes. Add
an onKeyDown handler to the modal to detect the Escape key and call
setIsEditLinksOpen(false) to close the modal, enabling keyboard users to escape
without using the Cancel button.
|
@mspandey |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`[username]/page.tsx:
- Around line 127-137: The custom-links query and ownership check in the page
logic are still reading from params.username even though params has already been
resolved into username. Update the profile lookup and the isOwner comparison in
the page component to use the awaited username variable consistently so both
checks operate on the resolved value rather than the Promise-backed params
object.
- Around line 136-137: The ownership check in the page component is using the
shared Supabase instance, which does not have the current request’s
cookie/session context, so it may miss the signed-in user. Update the `page.tsx`
logic around `supabase.auth.getUser()` and `isOwner` to use a request-scoped
Supabase client created from the incoming request/session context, then compare
`authData.user.user_metadata.user_name` against `params.username` as before.
In `@src/components/profile/ProfileView.tsx`:
- Line 64: The ProfileExtras interface is missing props that ProfileView already
destructures and uses, so update the contract to include isOwner and
customLinks. Add these fields to ProfileExtras in the ProfileView component
module, making sure their types match how ProfileView consumes them and how the
page passes them in.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 668346c5-c184-4f83-a7c0-3058c1127285
📒 Files selected for processing (2)
src/app/[username]/page.tsxsrc/components/profile/ProfileView.tsx
| // Fetch custom links and check ownership | ||
| const { data: profile } = await supabase | ||
| .from('profiles') | ||
| .select('custom_links') | ||
| .eq('username', params.username) | ||
| .single(); | ||
|
|
||
| const customLinks = profile?.custom_links || []; | ||
|
|
||
| const { data: authData } = await supabase.auth.getUser(); | ||
| const isOwner = authData?.user?.user_metadata?.user_name === params.username; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify params is a Promise and the changed block no longer reads params.username.
sed -n '19,21p;80,82p;127,137p' src/app/\[username\]/page.tsxRepository: PRODHOSH/ossfolio
Length of output: 723
Use username here instead of params.username
params is a Promise<{ username: string }> and is already awaited above. Both the custom-links lookup and the ownership check should use the resolved username.
Proposed fix
const { data: profile } = await supabase
.from('profiles')
.select('custom_links')
- .eq('username', params.username)
+ .eq('username', username)
.single();
@@
const { data: authData } = await supabase.auth.getUser();
- const isOwner = authData?.user?.user_metadata?.user_name === params.username;
+ const isOwner = authData?.user?.user_metadata?.user_name === username;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Fetch custom links and check ownership | |
| const { data: profile } = await supabase | |
| .from('profiles') | |
| .select('custom_links') | |
| .eq('username', params.username) | |
| .single(); | |
| const customLinks = profile?.custom_links || []; | |
| const { data: authData } = await supabase.auth.getUser(); | |
| const isOwner = authData?.user?.user_metadata?.user_name === params.username; | |
| // Fetch custom links and check ownership | |
| const { data: profile } = await supabase | |
| .from('profiles') | |
| .select('custom_links') | |
| .eq('username', username) | |
| .single(); | |
| const customLinks = profile?.custom_links || []; | |
| const { data: authData } = await supabase.auth.getUser(); | |
| const isOwner = authData?.user?.user_metadata?.user_name === username; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`[username]/page.tsx around lines 127 - 137, The custom-links query
and ownership check in the page logic are still reading from params.username
even though params has already been resolved into username. Update the profile
lookup and the isOwner comparison in the page component to use the awaited
username variable consistently so both checks operate on the resolved value
rather than the Promise-backed params object.
| const { data: authData } = await supabase.auth.getUser(); | ||
| const isOwner = authData?.user?.user_metadata?.user_name === params.username; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether the repo has a request-scoped Supabase server client/helper.
cat -n src/lib/supabase.ts
rg -n "createServerClient|`@supabase/ssr`|cookies\\(|supabase.auth.getUser" src package.jsonRepository: PRODHOSH/ossfolio
Length of output: 903
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the page component and any Supabase server-client helpers.
wc -l src/app/[username]/page.tsx src/lib/supabase.ts package.json
printf '\n--- src/app/[username]/page.tsx (120-160) ---\n'
sed -n '120,160p' src/app/[username]/page.tsx
printf '\n--- package.json relevant deps ---\n'
cat package.json | python3 - <<'PY'
import json,sys
data=json.load(sys.stdin)
for k in ["dependencies","devDependencies"]:
print(f"[{k}]")
for name in sorted(data.get(k, {})):
if "supabase" in name.lower() or name in ("next","react","react-dom"):
print(f"{name}: {data[k][name]}")
PY
printf '\n--- Supabase auth/server helper search ---\n'
rg -n "createServerClient|cookies\\(|`@supabase/ssr`|createBrowserClient|createServerComponentClient|createMiddlewareClient|auth\\.getUser\\(" src package.jsonRepository: PRODHOSH/ossfolio
Length of output: 2260
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the page component and any Supabase server-client helpers.
wc -l src/app/[username]/page.tsx src/lib/supabase.ts package.json
printf '\n--- src/app/[username]/page.tsx (120-160) ---\n'
sed -n '120,160p' src/app/[username]/page.tsx
printf '\n--- package.json relevant deps ---\n'
python3 - <<'PY'
import json
with open("package.json") as f:
data=json.load(f)
for k in ["dependencies","devDependencies"]:
print(f"[{k}]")
for name in sorted(data.get(k, {})):
if "supabase" in name.lower() or name in ("next","react","react-dom"):
print(f"{name}: {data[k][name]}")
PY
printf '\n--- Supabase auth/server helper search ---\n'
rg -n "createServerClient|cookies\\(|`@supabase/ssr`|createBrowserClient|createServerComponentClient|createMiddlewareClient|auth\\.getUser\\(" src package.jsonRepository: PRODHOSH/ossfolio
Length of output: 1787
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n--- src/app/[username]/page.tsx (1-60) ---\n'
sed -n '1,60p' src/app/[username]/page.tsx
printf '\n--- src/lib/supabase.ts ---\n'
cat -n src/lib/supabase.ts
printf '\n--- any auth/session helpers in src ---\n'
rg -n "getSession\\(|getUser\\(|createServerClient|cookies\\(" srcRepository: PRODHOSH/ossfolio
Length of output: 3157
Use a request-scoped Supabase client for ownership checks. src/lib/supabase.ts creates a singleton without cookie/session context, so supabase.auth.getUser() here can’t reliably see the current request’s signed-in user. isOwner can stay false for the profile owner and hide the edit UI.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/`[username]/page.tsx around lines 136 - 137, The ownership check in
the page component is using the shared Supabase instance, which does not have
the current request’s cookie/session context, so it may miss the signed-in user.
Update the `page.tsx` logic around `supabase.auth.getUser()` and `isOwner` to
use a request-scoped Supabase client created from the incoming request/session
context, then compare `authData.user.user_metadata.user_name` against
`params.username` as before.
| score: number; | ||
| /** ISO 8601 timestamp from Supabase profiles.updated_at — null if the user has never synced. */ | ||
| updatedAt: string | null; | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the prop contract includes every prop destructured by ProfileView and passed by the page.
sed -n '54,94p' src/components/profile/ProfileView.tsx
sed -n '150,163p' src/app/\[username\]/page.tsxRepository: PRODHOSH/ossfolio
Length of output: 1735
Add isOwner and customLinks to ProfileExtras. ProfileView destructures and uses these props, and the page passes them in, but the interface stops at updatedAt, so the component contract is incomplete.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/profile/ProfileView.tsx` at line 64, The ProfileExtras
interface is missing props that ProfileView already destructures and uses, so
update the contract to include isOwner and customLinks. Add these fields to
ProfileExtras in the ProfileView component module, making sure their types match
how ProfileView consumes them and how the page passes them in.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/profile/ProfileView.tsx`:
- Around line 243-249: Guard the ProfileView custom link rendering so
`customLinks` is normalized before `customLinks.map(...)` is called. In
`ProfileView`, validate that `profile?.custom_links` is an array of link-like
objects before passing it through, and filter out malformed entries so the
anchor list only renders safe `{ url, label }` items. Use the existing
`customLinks` mapping block as the fix location and make the render path
resilient to JSON column values that are objects, strings, or partially invalid.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: cff42379-c43f-4f82-9052-0fa1a27fb6ec
📒 Files selected for processing (1)
src/components/profile/ProfileView.tsx
| {customLinks && customLinks.map((link) => ( | ||
| <a | ||
| key={link.url} | ||
| href={link.url} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| aria-label={`${link.label} (opens in a new tab)`} |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Guard JSON-backed customLinks before mapping.
custom_links is loaded from a JSON column and passed through as profile?.custom_links || []; if that value is an object/string or contains malformed entries, Line 243 can throw and break the profile page. Normalize before rendering.
Proposed guard
}: { user: GitHubUser; repos: GitHubRepo[] } & ProfileExtras) {
+ const safeCustomLinks = Array.isArray(customLinks)
+ ? customLinks.filter(
+ (link): link is { label: string; url: string } =>
+ !!link &&
+ typeof link === "object" &&
+ typeof (link as { label?: unknown }).label === "string" &&
+ typeof (link as { url?: unknown }).url === "string"
+ )
+ : [];
+
...
- {customLinks && customLinks.map((link) => (
+ {safeCustomLinks.map((link) => (📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {customLinks && customLinks.map((link) => ( | |
| <a | |
| key={link.url} | |
| href={link.url} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| aria-label={`${link.label} (opens in a new tab)`} | |
| const safeCustomLinks = Array.isArray(customLinks) | |
| ? customLinks.filter( | |
| (link): link is { label: string; url: string } => | |
| !!link && | |
| typeof link === "object" && | |
| typeof (link as { label?: unknown }).label === "string" && | |
| typeof (link as { url?: unknown }).url === "string" | |
| ) | |
| : []; | |
| {safeCustomLinks.map((link) => ( | |
| <a | |
| key={link.url} | |
| href={link.url} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| aria-label={`${link.label} (opens in a new tab)`} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/profile/ProfileView.tsx` around lines 243 - 249, Guard the
ProfileView custom link rendering so `customLinks` is normalized before
`customLinks.map(...)` is called. In `ProfileView`, validate that
`profile?.custom_links` is an array of link-like objects before passing it
through, and filter out malformed entries so the anchor list only renders safe
`{ url, label }` items. Use the existing `customLinks` mapping block as the fix
location and make the render path resilient to JSON column values that are
objects, strings, or partially invalid.

Added functionality to edit and save custom links for the profile view, including input validation and database update.Added the Supabase client import and implemented the handleSaveLinks function to update the profiles table. Wired the function to the Save Changes button within the Edit Links modal to successfully mutate the database and reload the view.
Summary by CodeRabbit