Skip to content

Implement edit profile screen, and fix username routing for public routes - #2

Open
sherucon wants to merge 4 commits into
mainfrom
experimental
Open

Implement edit profile screen, and fix username routing for public routes#2
sherucon wants to merge 4 commits into
mainfrom
experimental

Conversation

@sherucon

@sherucon sherucon commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added profile editing with account settings and customizable section ordering.
    • Added drag-and-drop reordering for profile sections.
    • Added improved profile loading states and refreshed profile data after updates.
    • Added platform-specific sign-out options and clearer profile empty states.
    • Added support for image and video uploads.
  • Bug Fixes

    • Removed an unnecessary hidden tab from navigation.
    • Improved text input styling and outline button visibility.
  • Style

    • Refined welcome screen typography and input font styling.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds profile editing with draggable dossier sections, profile skeleton loading, authenticated user refresh, updated profile schema fields, shared UI changes, upload type support, database diagnostic scripts, and related navigation and configuration updates.

Changes

Profile management

Layer / File(s) Summary
Profile data contracts
prisma/schema.prisma, src/context/AuthContext.tsx
Profile.plan now uses a boolean. sectionOrder no longer includes "writing" by default. Auth state loads both fields and exposes refreshUser.
Edit profile flow
package.json, src/app/edit-profile.tsx
The new edit screen supports account settings, draggable dossier sections, Supabase persistence, deferred list mounting, save states, and auth synchronization.
Profile loading and actions
src/app/[username].tsx, src/app/(tabs)/_layout.tsx, src/app/_layout.tsx
The profile screen adds skeleton loading, concurrent refresh behavior, platform-specific sign-out prompts, edit navigation, layered content styling, and consolidated empty states.
Shared UI and upload contracts
src/components/button.tsx, src/components/editorial-input.tsx, src/components/text-input.tsx, src/hooks/useImageUpload.ts
Shared controls update colors, fonts, and container styling. Uploads now accept images and videos.
Presentation and database diagnostics
src/app/(auth)/welcome.tsx, check_schema.js, check_schema.ts, test-*.js, tsconfig.json
The welcome title typography changes. New scripts query Supabase or Prisma records. TypeScript excludes node_modules and supabase/functions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProfileScreen
  participant AuthContext
  participant Supabase
  ProfileScreen->>Supabase: fetch profile
  ProfileScreen->>AuthContext: refresh own authenticated user
  AuthContext->>Supabase: fetch authenticated profile
  Supabase-->>AuthContext: return profile fields
  Supabase-->>ProfileScreen: return profile data
Loading

Possibly related PRs

  • sherucon/toll#1: Shares profile skeletons, upload types, schema defaults, and input styling changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the two main changes: the edit profile screen and username routing updates for public routes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch experimental

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (2)
src/components/text-input.tsx (1)

8-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use StyleProp<ViewStyle> for containerStyle.

containerStyle is used as a React Native View prop, which accepts plain style objects, arrays, StyleSheet styles, or undefined. Typing it as ViewStyle rejects valid array or StyleSheet callers at compile time.

Proposed type fix
-import { StyleSheet, TextInput as RNTextInput, View, type TextInputProps, type ViewStyle } from 'react-native';
+import { StyleSheet, TextInput as RNTextInput, View, type StyleProp, type TextInputProps, type ViewStyle } from 'react-native';

 export type CustomTextInputProps = TextInputProps & {
   error?: boolean;
-  containerStyle?: ViewStyle;
+  containerStyle?: StyleProp<ViewStyle>;
 };
🤖 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/text-input.tsx` around lines 8 - 10, Update the
CustomTextInputProps.containerStyle type to use React Native’s
StyleProp<ViewStyle> instead of ViewStyle, preserving support for plain styles,
arrays, StyleSheet styles, and undefined wherever containerStyle is passed to
the View.
test-db.js (1)

5-6: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Bound and redact the full-table diagnostics.

Both scripts materialize and print every profile. Query and log size grow with the database, and shared logs retain username and plan metadata.

  • test-db.js#L5-L6: use findFirst/take: 1 or explicit pagination, and avoid logging raw profiles.
  • test-supabase.js#L7-L9: add limit/pagination and log only a redacted sample or aggregate count.
🤖 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 `@test-db.js` around lines 5 - 6, Bound both diagnostic queries and prevent
sensitive profile metadata from being written to logs: in test-db.js lines 5-6,
update the Prisma profile query near profiles to fetch at most one record or use
explicit pagination, then log only a redacted sample or aggregate count; in
test-supabase.js lines 7-9, add an equivalent limit or pagination and replace
raw profile logging with a redacted sample or aggregate count.
🤖 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 `@check_schema.js`:
- Around line 1-4: The diagnostics scripts cannot run through the documented
project environment because the JavaScript entrypoint directly requires
TypeScript and neither entrypoint has a documented invocation. Update
check_schema.js and check_schema.ts to use the repository’s supported
Node/TypeScript loader setup, add package.json scripts for both diagnostics, and
document those commands alongside the existing Expo usage in README; apply the
corresponding loader-compatible change at check_schema.js and check_schema.ts,
preserving the schema query behavior.

In `@prisma/schema.prisma`:
- Line 23: Establish one canonical persisted section-order contract across all
affected sites: in prisma/schema.prisma:23-23, explicitly decide whether contact
and retired writing are persisted, adding the required migration or retirement
handling; in src/app/edit-profile.tsx:37-62, include every section allowed by
that contract in the editor data; and in src/app/edit-profile.tsx:116-128,
preserve fixed or unknown IDs when saving reordered sections instead of dropping
them.
- Line 14: Update the migration history for the Profile.plan schema change by
adding a data migration that converts existing persisted plan string values,
mapping the prior free/default value to false and the applicable paid or enabled
values to true before enforcing the Boolean column type. Keep the schema
declaration and existing migration conventions consistent.

In `@src/app/`[username].tsx:
- Around line 480-498: The Feed/Content Container currently renders only the
empty state, leaving populated tabs blank. Update the JSX around the
activeTab/isDossierEmpty, isPostsEmpty, and isArticlesEmpty condition to render
the corresponding dossier, post, or article content when that tab is non-empty,
while preserving the existing empty-state branch for empty tabs.

In `@src/app/edit-profile.tsx`:
- Around line 134-136: Implement the General option in handleOptionPress by
navigating to its intended sub-screen using the existing navigation pattern, or
remove/hide the General control until that destination is available. Ensure
tapping General no longer invokes a no-op handler.
- Around line 116-131: Update handleSaveOrder so the Supabase update is wrapped
in try/catch/finally, with setIsSavingOrder(false) executed in finally even when
the promise rejects. Keep setIsReordered(false) and updateUser within the
successful no-error path, and preserve error logging for failures.

In `@src/components/text-input.tsx`:
- Line 25: Update the shared TextInput usage in EditorialInput to forward its
error prop as error={error}, while preserving the existing style and value
handling so the shared input controls the error border state.

In `@src/context/AuthContext.tsx`:
- Around line 117-121: Update fetchProfileAndUpdateUser, used by refreshUser, to
handle profile-query errors separately without replacing the existing user
state. Only call mapUser and setUser when the profile is confirmed absent;
preserve the current user on transient query failures while retaining the
existing successful-profile behavior.

In `@test-db.js`:
- Line 8: Update the main() promise chain so a rejected query preserves a
failing process exit status by setting process.exitCode to 1 or rethrowing after
logging, and ensure prisma.$disconnect() is awaited during final cleanup.

In `@test-simulate.js`:
- Around line 12-18: Update the diagnostic logging inside the users iteration to
avoid emitting sessionUser.id, sessionUser.email, the full profile, or raw error
details. Replace per-account output with aggregate counts and sanitized error
codes, and only retain per-user tracing behind a local-only flag with redacted
identifiers.
- Around line 4-7: Update the client setup used by main so
auth.admin.listUsers() runs through a separate server-only Supabase client
initialized with the non-public service-role credential, rather than
EXPO_PUBLIC_SUPABASE_ANON_KEY. Keep the public client separate and ensure the
admin call uses the server client.
- Around line 7-12: Update the user-loading flow around
supabase.auth.admin.listUsers() to paginate through every available page before
processing users. Use the returned lastPage or nextPage metadata to continue
fetching and aggregate users across pages, while preserving the existing
authError/no-users handling and subsequent sessionUser processing.

In `@tsconfig.json`:
- Around line 14-17: Add CI coverage for the Supabase Edge Function sources
currently excluded by tsconfig.json: add a step that runs Deno’s type check
against supabase/functions, using the repository’s existing CI configuration and
Deno setup. If that directory contains generated artifacts instead, reorganize
the configuration so only generated files remain excluded and the source is
type-checked.

---

Nitpick comments:
In `@src/components/text-input.tsx`:
- Around line 8-10: Update the CustomTextInputProps.containerStyle type to use
React Native’s StyleProp<ViewStyle> instead of ViewStyle, preserving support for
plain styles, arrays, StyleSheet styles, and undefined wherever containerStyle
is passed to the View.

In `@test-db.js`:
- Around line 5-6: Bound both diagnostic queries and prevent sensitive profile
metadata from being written to logs: in test-db.js lines 5-6, update the Prisma
profile query near profiles to fetch at most one record or use explicit
pagination, then log only a redacted sample or aggregate count; in
test-supabase.js lines 7-9, add an equivalent limit or pagination and replace
raw profile logging with a redacted sample or aggregate count.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bccd7774-a55f-40cf-b31e-af53bb2eaea9

📥 Commits

Reviewing files that changed from the base of the PR and between 854ee21 and 74dd6ed.

⛔ Files ignored due to path filters (2)
  • assets/images/support.png is excluded by !**/*.png
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • check_schema.js
  • check_schema.ts
  • package.json
  • prisma/schema.prisma
  • src/app/(auth)/welcome.tsx
  • src/app/(tabs)/_layout.tsx
  • src/app/[username].tsx
  • src/app/_layout.tsx
  • src/app/edit-profile.tsx
  • src/components/button.tsx
  • src/components/editorial-input.tsx
  • src/components/text-input.tsx
  • src/context/AuthContext.tsx
  • src/hooks/useImageUpload.ts
  • test-col.js
  • test-db.js
  • test-simulate.js
  • test-supabase.js
  • tsconfig.json
💤 Files with no reviewable changes (1)
  • src/app/(tabs)/_layout.tsx

Comment thread check_schema.js
Comment on lines +1 to +4
const { supabase } = require('./src/lib/supabase.ts');

async function checkSchema() {
const { data, error } = await supabase.from('Project').select('*').limit(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

for file in package.json .nvmrc .node-version .tool-versions tsconfig.json; do
  if [ -f "$file" ]; then
    echo "== $file =="
    rg -n 'tsx|ts-node|node --import|module|type|dotenv|check_schema' "$file" || true
  fi
done

rg -n -C3 'ExpoSecureStoreAdapter|createClient|EXPO_PUBLIC_SUPABASE_(URL|ANON_KEY)' \
  src/lib/supabase.ts check_schema.js check_schema.ts

Repository: sherucon/toll

Length of output: 1385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a '^(check_schema\.(js|ts)|package\.json|tsconfig\.json|readme|README|docs|scripts)$|schema|script' . | head -100 || true

echo "== root files =="
for f in package.json tsconfig.json README.md readmi docs scripts check_schema.js check_schema.ts; do
  [ -e "$f" ] && echo "--- $f" && wc -l "$f"
done

echo "== scripts and imports =="
python3 - <<'PY'
from pathlib import Path
for p in ['package.json','tsconfig.json','check_schema.js','check_schema.ts','README.md']:
    if Path(p).exists():
        print(f'-- {p} --')
        print(Path(p).read_text()[:2000])
PY

echo "== relevant searches =="
rg -n -S --glob '!node_modules' --glob '!dist' --glob '!build' 'check_schema|EXPO_PUBLIC_SUPERBASE|dotenv|tsx|ts-node|node -r|react-native-url-polyfill|createClient|supabaseUrl|supabaseAnonKey' . || true

echo "== package scripts =="
python3 - <<'PY'
import json, pathlib
p=pathlib.Path('package.json')
if p.exists():
    data=json.loads(p.read_text())
    for key in ['scripts','dependencies','devDependencies']:
        print(f'[{key}]')
        for k,v in data.get(key,{}).items():
            if 'supabase' in k.lower() or k in ['typescript','tsx','ts-node','expo','expo-secure-store','react-native-url-polyfill','dotenv','vite','react-native'] or 'check_schema' in str(v):
                print(k, v)
PY

Repository: sherucon/toll

Length of output: 538


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== script files =="
for f in check_schema.js check_schema.ts; do
  if [ -f "$f" ]; then
    echo "--- $f ($(wc -l < "$f") lines) ---"
    cat -n "$f"
  fi
done

echo "== package and config =="
printf '\n--- package.json ---\n'
cat -n package.json
printf '\n--- tsconfig.json ---\n'
cat -n tsconfig.json
printf '\n--- README.md ---\n'
cat -n README.md

echo "== scripts directory =="
find scripts -maxdepth 2 -type f -print -exec sh -c 'echo "--- $1 ---"; wc -l "$1"; sed -n "1,120p" "$1"' _ {} \;

echo "== relevant search =="
rg -n -S --glob '!node_modules' --glob '!dist' --glob '!build' --glob '!*.lock' \
  'check_schema|EXPO_PUBLIC_SUPABASE|dotenv|tsx|ts-node|node -r|react-native-url-polyfill|createClient|supabaseUrl|supabaseAnonKey|package:.*check_schema|scripts' . || true

echo "== package metadata =="
python3 - <<'PY'
import json, pathlib
data=json.loads(pathlib.Path("package.json").read_text())
for key in ["scripts","dependencies","devDependencies"]:
    print(f"[{key}]")
    for k,v in data.get(key,{}).items():
        if k.lower() in ["tsx","ts-node","typescript","expo","expo-secure-store","react-native-url-polyfill","dotenv"] \
        or k.lower().startswith("`@supabase`") \
        or any(s in str(v).lower() for s in ["check_schema","supabase","dotenv","node --import","ts-node","tsx"]):
            print(k, v)
PY

Repository: sherucon/toll

Length of output: 16244


Make these diagnostics scripts runnable in the documented project environment.

package.json has no check_schema script, and the README only mentions npx expo start. check_schema.js also requires .ts directly from CommonJS. Add a documented script/Invocation path for both .js and .ts, or align the diagnostics with the repo’s Node loader setup so the scripts do not fail before the schema query.

📍 Affects 2 files
  • check_schema.js#L1-L4 (this comment)
  • check_schema.ts#L1-L4
🤖 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 `@check_schema.js` around lines 1 - 4, The diagnostics scripts cannot run
through the documented project environment because the JavaScript entrypoint
directly requires TypeScript and neither entrypoint has a documented invocation.
Update check_schema.js and check_schema.ts to use the repository’s supported
Node/TypeScript loader setup, add package.json scripts for both diagnostics, and
document those commands alongside the existing Expo usage in README; apply the
corresponding loader-compatible change at check_schema.js and check_schema.ts,
preserving the schema query behavior.

Source: MCP tools

Comment thread prisma/schema.prisma
name String
username String @unique
plan String @default("free") // "free", "pro", "studio"
plan Boolean @default(false)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -H -t f . prisma | sort
rg -n -C 5 'ALTER TABLE.*Profile|ALTER COLUMN.*plan|sectionOrder|plan' prisma

Repository: sherucon/toll

Length of output: 1504


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== git tracked prisma files =="
git ls-files prisma | sort

echo "== schema around Profile =="
sed -n '1,45p' prisma/schema.prisma

echo "== references to Profile.plan and plan assignments/reads =="
rg -n -C 3 '\bplan\b|Profile\.plan' . --glob '!node_modules' --glob '!dist' --glob '!build' --glob '!coverage'

echo "== migrations files =="
fd -H -t f . prisma | sort

Repository: sherucon/toll

Length of output: 8717


Add a data migration for Profile.plan.

Profile.plan changed from a persisted string default such as "free" to a Boolean, and there is no migration that maps existing profile plan values. Add a migration that explicitly converts existing values before deploying this schema.

🤖 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 `@prisma/schema.prisma` at line 14, Update the migration history for the
Profile.plan schema change by adding a data migration that converts existing
persisted plan string values, mapping the prior free/default value to false and
the applicable paid or enabled values to true before enforcing the Boolean
column type. Keep the schema declaration and existing migration conventions
consistent.

Comment thread prisma/schema.prisma
socialLinks Json? // Stores an array or object of links
setupComplete Boolean @default(false) // Tracks onboarding completion
sectionOrder String[] @default(["projects", "sideProjects", "writing", "awards", "workExperience", "volunteering", "education", "certifications", "contact"])
sectionOrder String[] @default(["projects", "sideProjects", "awards", "workExperience", "volunteering", "education", "certifications", "contact"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Use one canonical section-order contract.

The schema defaults include contact, but the editor cannot display or preserve it. Saving any reordered profile writes only the draggable IDs and removes contact from persisted sectionOrder. Existing writing IDs also need an explicit migration or retirement path.

  • prisma/schema.prisma#L23-L23: define whether contact and retired writing sections belong in persisted ordering.
  • src/app/edit-profile.tsx#L37-L62: include every reorderable persisted section in the editor data.
  • src/app/edit-profile.tsx#L116-L128: preserve fixed or unknown section IDs when writing an edited order.
📍 Affects 2 files
  • prisma/schema.prisma#L23-L23 (this comment)
  • src/app/edit-profile.tsx#L37-L62
  • src/app/edit-profile.tsx#L116-L128
🤖 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 `@prisma/schema.prisma` at line 23, Establish one canonical persisted
section-order contract across all affected sites: in prisma/schema.prisma:23-23,
explicitly decide whether contact and retired writing are persisted, adding the
required migration or retirement handling; in src/app/edit-profile.tsx:37-62,
include every section allowed by that contract in the editor data; and in
src/app/edit-profile.tsx:116-128, preserve fixed or unknown IDs when saving
reordered sections instead of dropping them.

Comment thread src/app/[username].tsx
Comment on lines +480 to +498
{/* Feed/Content Container */}
<View style={styles.feedContainer}>
{((activeTab === "Dossier" && isDossierEmpty) ||
(activeTab === "Posts" && isPostsEmpty) ||
(activeTab === "Articles" && isArticlesEmpty)) && (
<View style={styles.emptyStateContainer}>
<Image
source={require("@/assets/images/not-found.png")}
style={styles.emptyStateImage}
resizeMode="contain"
/>
<ThemedText style={styles.emptyStateText}>
{isOwnProfile
? `Add sections to your ${activeTab.toLowerCase()}`
: "Quacks!"}
</ThemedText>
</View>
)}
</View>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Render populated tab content.

When the active tab contains data, the condition at Lines 482-484 is false. The feed container then renders no dossiers, posts, or articles.

Add the tab-specific content renderer alongside this empty-state branch. Otherwise every non-empty profile tab appears blank.

🤖 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].tsx around lines 480 - 498, The Feed/Content Container
currently renders only the empty state, leaving populated tabs blank. Update the
JSX around the activeTab/isDossierEmpty, isPostsEmpty, and isArticlesEmpty
condition to render the corresponding dossier, post, or article content when
that tab is non-empty, while preserving the existing empty-state branch for
empty tabs.

Comment thread src/app/edit-profile.tsx
Comment on lines +116 to +131
const handleSaveOrder = async () => {
if (!user?.id) return;
setIsSavingOrder(true);
const newOrder = dossierData.map((item) => item.id);
const { error } = await supabase
.from("Profile")
.update({ sectionOrder: newOrder })
.eq("id", user.id);

setIsSavingOrder(false);
if (!error) {
setIsReordered(false);
updateUser({ sectionOrder: newOrder });
} else {
console.error(error);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Always clear isSavingOrder.

If the update promise rejects, Line 125 does not run. The Save Order control then remains disabled and displays Saving... until the screen is remounted.

Move setIsSavingOrder(false) into a finally block. Keep the success-only state updates inside the successful path.

Proposed fix
   setIsSavingOrder(true);
-  const newOrder = dossierData.map((item) => item.id);
-  const { error } = await supabase
-    .from("Profile")
-    .update({ sectionOrder: newOrder })
-    .eq("id", user.id);
-
-  setIsSavingOrder(false);
-  if (!error) {
-    setIsReordered(false);
-    updateUser({ sectionOrder: newOrder });
-  } else {
-    console.error(error);
+  try {
+    const newOrder = dossierData.map((item) => item.id);
+    const { error } = await supabase
+      .from("Profile")
+      .update({ sectionOrder: newOrder })
+      .eq("id", user.id);
+
+    if (error) {
+      console.error(error);
+      return;
+    }
+
+    setIsReordered(false);
+    updateUser({ sectionOrder: newOrder });
+  } catch (error) {
+    console.error(error);
+  } finally {
+    setIsSavingOrder(false);
   }
📝 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.

Suggested change
const handleSaveOrder = async () => {
if (!user?.id) return;
setIsSavingOrder(true);
const newOrder = dossierData.map((item) => item.id);
const { error } = await supabase
.from("Profile")
.update({ sectionOrder: newOrder })
.eq("id", user.id);
setIsSavingOrder(false);
if (!error) {
setIsReordered(false);
updateUser({ sectionOrder: newOrder });
} else {
console.error(error);
}
const handleSaveOrder = async () => {
if (!user?.id) return;
setIsSavingOrder(true);
try {
const newOrder = dossierData.map((item) => item.id);
const { error } = await supabase
.from("Profile")
.update({ sectionOrder: newOrder })
.eq("id", user.id);
if (error) {
console.error(error);
return;
}
setIsReordered(false);
updateUser({ sectionOrder: newOrder });
} catch (error) {
console.error(error);
} finally {
setIsSavingOrder(false);
}
🧰 Tools
🪛 React Doctor (0.9.3)

[error] 125-125: This resets a loading/busy flag only on the success path: if the awaited call rejects the reset never runs and the flag stays stuck truthy (a spinner that never stops, a button disabled forever). Move the reset into a finally block, or mirror it on every catch, so it clears on rejection too.

A trailing setLoading(false) after an await never runs if the awaited call rejects, so the flag stays stuck truthy; reset it in a finally block (or mirror the reset on every catch) so it clears on both paths.

(no-loading-flag-reset-outside-finally)

🤖 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/edit-profile.tsx` around lines 116 - 131, Update handleSaveOrder so
the Supabase update is wrapped in try/catch/finally, with
setIsSavingOrder(false) executed in finally even when the promise rejects. Keep
setIsReordered(false) and updateUser within the successful no-error path, and
preserve error logging for failures.

Source: Linters/SAST tools

Comment thread test-db.js
const profiles = await prisma.profile.findMany({ select: { username: true, plan: true } });
console.log(profiles);
}
main().catch(console.error).finally(() => prisma.$disconnect());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve a failing exit status.

catch(console.error) logs a rejected query and then resolves successfully. CI or shell callers can receive exit code 0 for a database failure. Set process.exitCode = 1 or rethrow after logging, and await $disconnect() during cleanup.

Proposed fix
-main().catch(console.error).finally(() => prisma.$disconnect());
+main()
+  .catch((error) => {
+    console.error(error);
+    process.exitCode = 1;
+  })
+  .finally(async () => {
+    await prisma.$disconnect();
+  });
📝 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.

Suggested change
main().catch(console.error).finally(() => prisma.$disconnect());
main()
.catch((error) => {
console.error(error);
process.exitCode = 1;
})
.finally(async () => {
await prisma.$disconnect();
});
🤖 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 `@test-db.js` at line 8, Update the main() promise chain so a rejected query
preserves a failing process exit status by setting process.exitCode to 1 or
rethrowing after logging, and ensure prisma.$disconnect() is awaited during
final cleanup.

Comment thread test-simulate.js
Comment on lines +4 to +7
const supabase = createClient(process.env.EXPO_PUBLIC_SUPABASE_URL, process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY);

async function main() {
const { data: { users }, error: authError } = await supabase.auth.admin.listUsers();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | sed -n '1,120p'

echo "== test-simulate.js =="
if [ -f test-simulate.js ]; then
  cat -n test-simulate.js
else
  fd -i '^test-simulate\.js$' -x sh -c 'echo "--- $1"; cat -n "$1"' sh {}
fi

echo "== env references =="
rg -n "EXPO_PUBLIC_SUPABASE_(URL|ANON_KEY)|SUPABASE_(SECRET|SERVICE_ROLE)_KEY|auth\.admin\.listUsers|createClient" . --glob '!node_modules' --glob '!dist' --glob '!build' || true

Repository: sherucon/toll

Length of output: 7438


🌐 Web query:

Supabase JavaScript Auth Admin listUsers serverOnly service_role key documentation

💡 Result:

To list users in Supabase using the JavaScript client, you should use the supabase.auth.admin.listUsers() method [1][2]. Critical Security Considerations: 1. Server-Side Only: This method is intended exclusively for server-side environments [1][2][3]. 2. Service Role Key: You must initialize the Supabase client using your project's service_role API key to access administrative functions [4][5][6]. 3. Never Expose in Browser: The service_role key bypasses Row Level Security (RLS) and provides full administrative access to your project [4]. You must never expose this key in client-side code (e.g., frontend browser applications), as doing so will compromise your database [1][2][3]. Implementation Example: You should create a separate, dedicated Supabase client on your server using the service_role key, distinct from any client used for standard frontend operations [4]. // Server-side code example import { createClient } from '@supabase/supabase-js' const supabaseAdmin = createClient( process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_ROLE_KEY // Use server-side environment variables) const { data: { users }, error } = await supabaseAdmin.auth.admin.listUsers({ page: 1, perPage: 100 }) if (error) { console.error('Error fetching users:', error) } else { console.log(users) } Method Signature: The listUsers method accepts an optional parameters object for pagination [1][2]: - page (number): The page number to retrieve [1][5]. - perPage (number): The number of users to return per page [1][5]. For further details, refer to the official Supabase documentation for the JavaScript Auth Admin API [1][7].

Citations:


Use a server-only credential for Auth Admin.

auth.admin.listUsers() requires a Supabase service_role key, but this client uses the public EXPO_PUBLIC_SUPABASE_ANON_KEY. Create a separate server client with a non-public Supabase key before calling supabase.auth.admin.listUsers().

🤖 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 `@test-simulate.js` around lines 4 - 7, Update the client setup used by main so
auth.admin.listUsers() runs through a separate server-only Supabase client
initialized with the non-public service-role credential, rather than
EXPO_PUBLIC_SUPABASE_ANON_KEY. Keep the public client separate and ensure the
admin call uses the server client.

Source: MCP tools

Comment thread test-simulate.js
Comment on lines +7 to +12
const { data: { users }, error: authError } = await supabase.auth.admin.listUsers();
if (authError || !users) {
console.log('No users found or error:', authError);
return;
}
for (const sessionUser of users) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | sed -n '1,120p'

echo "== target file =="
if [ -f test-simulate.js ]; then
  nl -ba test-simulate.js | sed -n '1,120p'
else
  fd -a 'test-simulate\.js' .
fi

echo "== listUsers mentions =="
rg -n "listUsers|perPage|page|users\]" . || true

Repository: sherucon/toll

Length of output: 5237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== test-simulate.js =="
awk '{printf "%6d\t%s\n", NR, $0}' test-simulate.js | sed -n '1,180p'

echo "== listUsers/user profile code =="
rg -n "listUsers|profile|profiles|supabase=|createClient|users\]" . -g '*.js' -g '*.ts' -g '*.tsx' || true

Repository: sherucon/toll

Length of output: 6354


🌐 Web query:

Supabase JavaScript Auth Admin listUsers page perPage pagination docs

💡 Result:

In the Supabase JavaScript client, the auth.admin.listUsers method supports server-side pagination using an optional params object [1][2]. Usage and Parameters: The method accepts a params object with the following optional properties [1]: - page (number): The page number to retrieve [1][2]. - perPage (number): The number of results to return per page [1][2]. By default, listUsers returns 50 users per page if no pagination parameters are provided [2][3]. Example Implementation: const { data: { users }, error } = await supabase.auth.admin.listUsers({ page: 1, perPage: 1000 }) Security Note: This function is intended for server-side use only. Never expose your service_role key in client-side code (e.g., browser environments), as it bypasses Row Level Security (RLS) [1][2]. Metadata: The listUsers method also returns pagination metadata in the response object, including [4][3]: - total: The total number of records [4][3]. - nextPage: The next page index, or null if no further results exist [4][3]. - lastPage: The index of the final page [4][3]. These fields are derived from the API response headers (x-total-count and Link headers) [4][3]. When using this method, ensure your version of the Supabase client is up-to-date to avoid potential bugs associated with parsing multi-digit page numbers from the Link header [5].

Citations:


Paginate the user list.

supabase.auth.admin.listUsers() returns total, nextPage, and lastPage, but test-simulate.js only calls it once. If the project has more than one page of users, later users are skipped. Loop through pages using lastPage or nextPage before querying profiles.

🤖 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 `@test-simulate.js` around lines 7 - 12, Update the user-loading flow around
supabase.auth.admin.listUsers() to paginate through every available page before
processing users. Use the returned lastPage or nextPage metadata to continue
fetching and aggregate users across pages, while preserving the existing
authError/no-users handling and subsequent sessionUser processing.

Source: MCP tools

Comment thread test-simulate.js
Comment on lines +12 to +18
for (const sessionUser of users) {
const { data: profile, error } = await supabase
.from('Profile')
.select('username, name, avatar, setupComplete, plan')
.eq('id', sessionUser.id)
.single();
console.log(`User ${sessionUser.id} (${sessionUser.email}):`, { profile, error });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove user data from diagnostic logs.

Line 18 writes sessionUser.id, sessionUser.email, the full profile, and error for every account. Shared CI logs can retain these values. Log aggregate counts and sanitized error codes. If per-user tracing is required, gate it behind a local-only flag and redact identifiers.

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 17-17: Avoid logging sensitive data
Context: console.log(User ${sessionUser.id} (${sessionUser.email}):, { profile, error })
Note: [CWE-532] Insertion of Sensitive Information into Log File.

(log-sensitive-data)

🤖 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 `@test-simulate.js` around lines 12 - 18, Update the diagnostic logging inside
the users iteration to avoid emitting sessionUser.id, sessionUser.email, the
full profile, or raw error details. Replace per-account output with aggregate
counts and sanitized error codes, and only retain per-user tracing behind a
local-only flag with redacted identifiers.

Source: Linters/SAST tools

Comment thread tsconfig.json
Comment on lines +14 to +17
"exclude": [
"node_modules",
"supabase/functions"
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

if [ -d supabase/functions ]; then
  fd -t f . supabase/functions
fi

rg -n 'supabase/functions|typecheck|tsc|deno' \
  package.json .github tsconfig.json 2>/dev/null || true

Repository: sherucon/toll

Length of output: 337


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- package.json relevant scripts ---\n'
if [ -f package.json ]; then
  jq '.scripts // {}' package.json
fi

printf '\n--- tsconfig.json ---\n'
cat -n tsconfig.json

printf '\n--- Supabase function files ---\n'
for f in $(fd -t f . supabase/functions); do
  echo "--- $f ---"
  sed -n '1,120p' "$f"
done

Repository: sherucon/toll

Length of output: 242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- package.json relevant scripts ---"
if [ -f package.json ]; then
  jq '.scripts // {}' package.json
fi

echo
echo "--- tsconfig.json ---"
cat -n tsconfig.json

echo
echo "--- Supabase function files ---"
while IFS= read -r f; do
  echo "--- $f ---"
  sed -n '1,120p' "$f"
done < <(fd -t f . supabase/functions)

Repository: sherucon/toll

Length of output: 1021


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- Supabase function files ---"
fd -t f . supabase/functions | while IFS= read -r f; do
  echo "--- $f ---"
  sed -n '1,120p' "$f"
done

echo
echo "--- package.json dev scripts and deps ---"
jq '{scripts, devDependencies, dependencies}' package.json

echo
echo "--- type-check/deno-related files ---"
fd -i '^(deno\.json|deno\.jsonc|tsconfig.*\.json)$|package\.json$' . | sort

Repository: sherucon/toll

Length of output: 6187


Add a Deno type check for Supabase Edge Functions.

supabase/functions is deployed Edge Function source, and this config excludes it. package.json has no scripts for Deno/type-checking that directory. Add a CI step using Deno’s type check for supabase/functions, or reorganize if those files are generated artifacts.

🤖 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 `@tsconfig.json` around lines 14 - 17, Add CI coverage for the Supabase Edge
Function sources currently excluded by tsconfig.json: add a step that runs
Deno’s type check against supabase/functions, using the repository’s existing CI
configuration and Deno setup. If that directory contains generated artifacts
instead, reorganize the configuration so only generated files remain excluded
and the source is type-checked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant