-
Notifications
You must be signed in to change notification settings - Fork 55
fix: reapir brand signup flow(frontend) #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds memoized object URLs and cleanup for profile and brand image previews; inserts pre-submit user upsert and refined upload flows in onboarding/brand submission; introduces a new Profile page component that loads, validates, previews, and saves username, bio, and avatar with optional image upload. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant OB as Onboarding Component
participant Auth as Auth Context
participant API as App API
participant Storage as Object Storage / DB
U->>OB: Submit onboarding / brand
OB->>Auth: get current user (id,email)
OB->>API: upsert user {id,email,username,role}
API->>Storage: upsert users table
Storage-->>API: upsert ok
API-->>OB: user exists
alt logo/profile is File
OB->>Storage: upload file (contentType, cacheControl, filename)
Storage-->>OB: file URL
end
OB->>API: submit onboarding data (including uploaded URLs)
API-->>OB: success
Note right of OB: ObjectURLs are memoized and revoked on change/unmount
sequenceDiagram
autonumber
actor U as User
participant P as ProfilePage
participant Auth as Auth Context
participant UA as userApi
participant Store as Image Storage
U->>P: Open profile page
P->>Auth: get current user
P->>UA: getProfile(userId)
UA-->>P: profile {username,bio,avatar_url,role}
U->>P: Edit fields / choose image
alt Image selected
P->>Store: uploadProfileImage(file, contentType)
Store-->>P: {avatar_url}
end
P->>UA: updateProfile({username,bio,avatar_url?})
UA-->>P: success
P-->>U: Save complete
Note right of P: previews use memoized ObjectURL and are revoked on change/unmount
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
Frontend/src/components/Onboarding.tsx (6)
446-451: Avoid empty/unsafe extensions and set contentType on uploadIf the file has no extension, you create a trailing dot. Also, set contentType for correct headers.
Apply:
- const fileExt = profilePic.name ? profilePic.name.split('.').pop() : ''; - const fileName = `${user?.id}_${Date.now()}.${fileExt}`; - const { data, error } = await supabase.storage.from('profile-pictures').upload(fileName, profilePic); + const ext = profilePic.name.includes('.') ? profilePic.name.split('.').pop()!.toLowerCase() : undefined; + const fileName = `${user?.id}_${Date.now()}${ext ? `.${ext}` : ''}`; + const { data, error } = await supabase + .storage.from('profile-pictures') + .upload(fileName, profilePic, { contentType: profilePic.type, cacheControl: '3600', upsert: false });
456-466: Guard auth and upsert the user (avoid eq('id', undefined) and missing row updates)If user is null/undefined, the update can be a no-op and miss creating the row. Upsert and coerce age to number.
- // 2. Update users table - const categoryToSave = personal.category === 'Other' ? personal.otherCategory : personal.category; - const { error: userError } = await supabase.from('users').update({ - username: personal.name, - age: personal.age, - gender: personal.gender, - country: personal.country, - category: categoryToSave, - profile_image: profile_image_url, - role, - }).eq('id', user?.id); + // 2. Ensure auth and upsert users row + if (!user?.id || !user?.email) throw new Error('You must be signed in to submit onboarding.'); + const categoryToSave = personal.category === 'Other' ? personal.otherCategory : personal.category; + const { error: userError } = await supabase.from('users').upsert({ + id: user.id, + email: user.email, + username: personal.name, + age: Number(personal.age), + gender: personal.gender, + country: personal.country, + category: categoryToSave, + profile_image: profile_image_url, + role, + }, { onConflict: 'id' });
418-421: Stop creating new ObjectURLs on every render; memoize and revokeRepeated URL.createObjectURL calls leak. Memoize once per file and revoke on change/unmount.
- src={profilePic ? URL.createObjectURL(profilePic) : user?.user_metadata?.avatar_url} + src={profilePicUrl ?? user?.user_metadata?.avatar_url}Add near the component state (after line 88):
+ const profilePicUrl = useMemo(() => (profilePic ? URL.createObjectURL(profilePic) : null), [profilePic]); + useEffect(() => () => { if (profilePicUrl) URL.revokeObjectURL(profilePicUrl); }, [profilePicUrl]);Also applies to: 544-545
957-964: Brand logo: ensure File type, sanitize extension, set contentTypeLocalStorage rehydration can make logo an object; gate with instanceof File. Also fix extension and headers.
- if (brandData.logo) { - const fileExt = brandData.logo.name ? brandData.logo.name.split('.').pop() : ''; - const fileName = `${user?.id}_${Date.now()}.${fileExt}`; - const { data, error } = await supabase.storage.from('brand-logos').upload(fileName, brandData.logo); + if (brandData.logo instanceof File) { + const ext = brandData.logo.name.includes('.') ? brandData.logo.name.split('.').pop()!.toLowerCase() : undefined; + const fileName = `${user!.id}_${Date.now()}${ext ? `.${ext}` : ''}`; + const { data, error } = await supabase + .storage.from('brand-logos') + .upload(fileName, brandData.logo, { contentType: brandData.logo.type, cacheControl: '3600', upsert: false });
940-956: Brand submit: enforce auth before proceedingFail fast if not signed in to avoid undefined user.id in filenames and DB rows.
const handleBrandSubmit = async () => { setBrandSubmitting(true); setBrandSubmitError(""); setBrandSubmitSuccess(""); let logo_url = null; try { + if (!user?.id || !user?.email) throw new Error("Please sign in to finish brand onboarding."); // 0. Ensure user exists in users table (upsert) if (user) {
1066-1075: Fix brandData rehydration: File isn’t serializableAfter JSON.parse, logo becomes a plain object. Normalize to null to avoid runtime upload errors.
- if (savedStep) setBrandStep(Number(savedStep)); - if (savedData) setBrandData(JSON.parse(savedData)); + if (savedStep) setBrandStep(Number(savedStep)); + if (savedData) { + const parsed = JSON.parse(savedData); + if (parsed.logo) parsed.logo = null; + setBrandData(parsed); + }
🧹 Nitpick comments (5)
Frontend/src/components/Onboarding.tsx (3)
965-989: Make brand insert idempotentUse upsert on user_id to prevent duplicate brand rows on retries.
- const { error: brandError } = await supabase.from('brands').insert({ + const { error: brandError } = await supabase.from('brands').upsert({ user_id: user?.id, brand_name: brandData.brand_name, logo_url, website_url: brandData.website_url, industry: brandData.industry, company_size: brandData.company_size, location: brandData.location, description: brandData.description, contact_person: brandData.contact_person, contact_email: brandData.contact_email, contact_phone: brandData.contact_phone, role: brandData.role, instagram_url: brandData.social_links.instagram_url || null, facebook_url: brandData.social_links.facebook_url || null, twitter_url: brandData.social_links.twitter_url || null, linkedin_url: brandData.social_links.linkedin_url || null, youtube_url: brandData.social_links.youtube_url || null, collaboration_types: brandData.collaboration_types, preferred_creator_categories: brandData.preferred_creator_categories, brand_values: brandData.brand_values, preferred_tone: brandData.preferred_tone, platforms: brandData.platforms, - }); + }, { onConflict: 'user_id' });
165-166: Fix asset path typoImage likely broken: “contnetcreator.png”.
- <img src="/contnetcreator.png" alt="Content Creator" className="h-20 w-20 mb-2" /> + <img src="/contentcreator.png" alt="Content Creator" className="h-20 w-20 mb-2" />
1066-1075: Optional: persist selected role across reloadsImproves UX when resuming brand onboarding.
useEffect(() => { const savedStep = localStorage.getItem("brandStep"); const savedData = localStorage.getItem("brandData"); + const savedRole = localStorage.getItem("onboardingRole"); if (savedStep) setBrandStep(Number(savedStep)); if (savedData) { const parsed = JSON.parse(savedData); if (parsed.logo) parsed.logo = null; setBrandData(parsed); } + if (savedRole) setRole(savedRole); }, []); useEffect(() => { localStorage.setItem("brandStep", String(brandStep)); localStorage.setItem("brandData", JSON.stringify(brandData)); + if (role) localStorage.setItem("onboardingRole", role); }, [brandStep, brandData, role]);Frontend/src/pages/Profile.tsx (2)
78-99: Save flow looks solid; minor: avoid sending role unless intendedYou currently send role back even though it’s not editable. Consider omitting to prevent accidental changes.
- const payload: Partial<UserProfile> = { + const payload: Partial<UserProfile> = { username: username.trim() || undefined, bio: bio.trim() || undefined, - // role is generally set during onboarding; do not allow arbitrary changes here unless present - role: role as any, + // role: keep server source of truth; omit here unless explicitly editable
64-71: Image validation LGTM; add early clear of prior error on new pickTiny UX nit: clear error when a new valid file is selected.
- const onSelectImage: React.ChangeEventHandler<HTMLInputElement> = (e) => { + const onSelectImage: React.ChangeEventHandler<HTMLInputElement> = (e) => { const f = e.target.files?.[0] || null; if (!f) return setImageFile(null); if (!f.type.startsWith("image/")) { setError("Please select an image file"); return; } if (f.size > 5 * 1024 * 1024) { setError("Max file size is 5MB"); return; } - setError(null); + setError(null); setImageFile(f); };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Frontend/src/components/Onboarding.tsx(3 hunks)Frontend/src/pages/Profile.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
Frontend/src/components/Onboarding.tsx (1)
Frontend/src/utils/supabase.tsx (1)
supabase(11-11)
Frontend/src/pages/Profile.tsx (1)
Frontend/src/context/AuthContext.tsx (1)
useAuth(216-222)
🔇 Additional comments (1)
Frontend/src/pages/Profile.tsx (1)
41-62: Verify userApi exports and return types used by Profile.tsx
- Confirm getProfile, uploadProfileImage, updateProfile and the UserProfile type are exported from Frontend/src/services/userApi.
- Ensure those functions return an object with the fields consumed in Frontend/src/pages/Profile.tsx (username, email, bio, role, profile_image_url).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
Frontend/src/components/Onboarding.tsx (5)
666-676: Validate logo file type/size and clear errors when validAdd basic guards to prevent huge/non-image uploads for brand logos.
Apply:
const handleBrandLogoChange = (e: React.ChangeEvent<HTMLInputElement>) => { if (e.target.files && e.target.files[0]) { const file = e.target.files[0]; + if (!file.type.startsWith('image/')) { + setBrandError('Please select a valid image file.'); + return; + } + if (file.size > 3 * 1024 * 1024) { + setBrandError('Logo must be less than 3MB.'); + return; + } // Revoke previous preview if present if (brandLogoPreview) { try { URL.revokeObjectURL(brandLogoPreview); } catch {} } const url = URL.createObjectURL(file); setBrandData({ ...brandData, logo: file }); setBrandLogoPreview(url); + setBrandError(''); } };
463-469: Storage path hygiene (optional)Consider namespacing uploads by user to avoid flat bucket clutter and improve traceability.
Apply:
- const { data, error } = await supabase + const filePath = `${user!.id}/${fileName}`; + const { data, error } = await supabase .storage.from('profile-pictures') - .upload(fileName, profilePic, { contentType: profilePic.type, cacheControl: '3600', upsert: false }); + .upload(filePath, profilePic, { contentType: profilePic.type, cacheControl: '3600', upsert: false }); - profile_image_url = `${supabase.storage.from('profile-pictures').getPublicUrl(fileName).data.publicUrl}`; + profile_image_url = supabase.storage.from('profile-pictures').getPublicUrl(filePath).data.publicUrl;
1037-1039: Simplify: rely solely on brandLogoPreviewThe ternaries are redundant; just gate on brandLogoPreview.
Apply:
- {(brandLogoPreview || (brandData.logo instanceof File ? brandLogoPreview : undefined)) ? ( - <img src={(brandLogoPreview || (brandData.logo instanceof File ? brandLogoPreview : undefined)) ?? ''} alt="Logo Preview" className="h-16 w-16 rounded-full object-cover border mb-2" /> + {brandLogoPreview ? ( + <img src={brandLogoPreview} alt="Logo Preview" className="h-16 w-16 rounded-full object-cover border mb-2" />
1101-1105: Clear preview on restored stateWhen restoring brandData (logo reset to null), also clear brandLogoPreview to avoid stale previews in long-lived mounts.
Apply:
if (savedData) { const parsed = JSON.parse(savedData); if (parsed.logo) parsed.logo = null; setBrandData(parsed); + setBrandLogoPreview(null); }
182-183: Fix asset filename typoImage likely won’t load.
Apply:
- <img src="/contnetcreator.png" alt="Content Creator" className="h-20 w-20 mb-2" /> + <img src="/contentcreator.png" alt="Content Creator" className="h-20 w-20 mb-2" />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Frontend/src/components/Onboarding.tsx(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
Frontend/src/components/Onboarding.tsx (1)
Frontend/src/utils/supabase.tsx (1)
supabase(11-11)
🔇 Additional comments (3)
Frontend/src/components/Onboarding.tsx (3)
89-96: Good: memoized ObjectURL + cleanupAvoids leaks and redundant URLs for the profile picture.
562-569: Stop calling URL.createObjectURL during render (profile preview in Review step)This recreates a blob URL every render and isn’t revoked. Reuse profilePicUrl like you did in the upload step.
Apply:
- {(profilePic || user?.user_metadata?.avatar_url) ? ( + {(profilePicUrl || user?.user_metadata?.avatar_url) ? ( <img - src={profilePic ? URL.createObjectURL(profilePic) : user?.user_metadata?.avatar_url} + src={profilePicUrl ?? user?.user_metadata?.avatar_url} alt="Profile Preview" className="h-20 w-20 rounded-full object-cover border-2 border-purple-500" />Run to ensure no other render-path usages remain:
#!/bin/bash rg -n -C2 -g 'Frontend/**' 'createObjectURL\(' --type tsx
973-985: Upserting users with role='brand' may overwrite an existing role — confirm business rule
If users can hold multiple roles (e.g., creator + brand) a single role string is lossy; use an array of roles or a join table.
Location: Frontend/src/components/Onboarding.tsx (lines 973–985).
Verification attempt failed (repo scan returned "No such file or directory"); re-run a repo-wide search for upsert/insert/update to the 'users' table or manually confirm whether this is the only place writing users.role before merging.
Closes #
📝 Description
Fixes several frontend issues in the brand signup/onboarding flow to make registration, login, password reset, and onboarding navigation reliable for new Brand users. This PR focuses on the client-side flow and UX (no backend schema or DB changes).
Context:
Users experienced broken or confusing flows during brand signup and onboarding.
Google OAuth and password-based signup had edge-case handling and redirect issues.
Onboarding redirects were inconsistent after successful sign-up or OAuth.
🔧 Changes Made
Refactored and fixed signup flow logic in [Signup.tsx]
Improved duplicate-account detection and error messages.
Fixed password validation and strength display behavior.
Ensured Supabase signUp/signInWithOAuth calls are handled consistently.
Fixed login flow in [Login.tsx]
Improved sign-in error handling and OAuth fallback behavior.
Improved password recovery flow:
[ForgotPassword.tsx] validates email existence and shows clear guidance when no account exists.
[ResetPassword.tsx] applies the password update reliably after reset.
Onboarding & role selection:
[RoleSelection.tsx] clarified navigation and ensured chosen role persists to onboarding.
[Onboarding.tsx] fixed onboarding submission, persistence and redirects to the correct dashboard after completion.
Profile page adjustments:
[Profile.tsx] made certain profile/role reads safe during onboarding and removed accidental edits to role.
Minor UX / error messaging improvements across auth flows:
Standardized messages for authentication failures and session expiry.
-->
✅ Checklist
Summary by CodeRabbit
New Features
Bug Fixes