Skip to main content
The Profile page allows users to view their account information, update their public profile settings, and manage authentication actions like password resets.

Accessing Your Profile

Navigate to /profile or /app/profile while signed in. The profile page displays your account identity, connected authentication providers, and profile settings. Route: /profile or /app/profile Component: ProfilePage.tsx

Account Identity

The Account Identity card displays your core account details.

Displayed Information

FieldDescription
Display NameYour current public display name
EmailThe email address associated with your account (or “No email address”)
AvatarProfile image preview (shows initials if no avatar URL is set)
Auth ProvidersConnected authentication methods (e.g., “email”, “google”)
Last Sign InTimestamp of your most recent sign-in
JoinedAccount creation timestamp
Account identity section (ProfilePage.tsx:447-476):
<div className="flex items-center gap-4">
  <div className="relative inline-flex h-20 w-20 items-center justify-center overflow-hidden rounded-2xl border border-[#2B2B37] bg-[#141420] text-lg font-bold text-[#D8D8DE]">
    {avatarSource ? (
      <img
        src={avatarSource}
        alt="Profile avatar"
        className="h-full w-full object-cover"
        onError={() => setAvatarPreviewFailed(true)}
      />
    ) : (
      getInitial(form.displayName || profile.email || 'U')
    )}
  </div>
  <div className="min-w-0 space-y-1 text-sm">
    <div className="text-[#F5F5F0] truncate">{profile.providers.length > 0 ? profile.providers.join(' • ') : 'Email account'}</div>
    <div className="text-xs text-[#8C8C99] truncate">Last sign in: {formatDate(profile.lastSignInAt)}</div>
    <div className="text-xs text-[#8C8C99] truncate">Joined: {formatDate(profile.createdAt)}</div>
  </div>
</div>

Provider Defaults

If you signed in via Google OAuth, the system caches your Google profile defaults (name and avatar). Click Use profile defaults to restore these values. Provider defaults button (ProfilePage.tsx:498-507):
{profile.providerDefaults ? (
  <button
    type="button"
    onClick={handleUseProviderDefaults}
    className="inline-flex h-9 w-full items-center justify-center gap-2 rounded-xl border border-[#1A1A22] bg-[#151519] px-3 text-sm text-[#8C8C99] transition-colors hover:border-[#34D399]/30 hover:text-[#6EE7B7]"
  >
    <RefreshCw size={14} />
    Use {profile.providerDefaults.provider} profile defaults
  </button>
) : null}

Public Profile Settings

The Public profile settings section allows you to customize how your account appears throughout AdRecon.

Editable Fields

1

Display Name

Your public display name (max 100 characters). This is shown in the app UI and any future collaboration features.
2

Profile Image URL

A direct URL to your profile avatar image. The preview updates as you type. If the image fails to load, your initials are shown instead.
3

Bio

A short biography for your account (max 240 characters). The character counter updates as you type.
4

Website

Your website URL (e.g., https://your-site.com). Displayed with a globe icon.

Saving Changes

Click Save changes to update your profile. The button is disabled until you make an edit. Save handler (ProfilePage.tsx:269-294):
const handleSave = async () => {
  if (!profile) {
    return;
  }

  setSaving(true);
  setError(null);

  try {
    const updated = await updateCurrentUserProfile({
      displayName: form.displayName,
      avatarUrl: form.avatarUrl,
      bio: form.bio,
      websiteUrl: form.websiteUrl,
    });
    setProfile(updated);
    setForm(buildInitialForm(updated));
    toast.success('Profile updated successfully.');
  } catch (err) {
    const message = toUserFacingError(err, 'Failed to update profile.');
    setError(message);
    toast.error(message);
  } finally {
    setSaving(false);
  }
};
Display name and profile image are stored in your Supabase user_metadata and are reflected throughout the app immediately after saving.

Email Confirmation Badge

The profile form footer displays your email confirmation status:
  • Email confirmed (green badge): Your email address has been verified
  • Email unconfirmed (orange badge): Your email address has not been verified
Email confirmation is managed by Supabase Auth and controlled by the emailConfirmedAt timestamp.

Security Actions

Copy User ID

Click Copy User ID to copy your unique Supabase user UUID to the clipboard. This is useful for support tickets or debugging. Copy handler (ProfilePage.tsx:308-319):
const handleCopyUserId = async () => {
  if (!profile) {
    return;
  }

  try {
    await navigator.clipboard.writeText(profile.id);
    toast.success('User ID copied to clipboard.');
  } catch {
    toast.error('Unable to copy User ID in this browser.');
  }
};

Send Password Reset Email

Click Send password reset email to trigger a password reset flow. This button is disabled if your account does not have an email address. Reset handler (ProfilePage.tsx:296-306):
const handleSendPasswordReset = async () => {
  setSendingReset(true);
  try {
    await sendCurrentUserPasswordResetEmail();
    toast.success('Password reset email sent.');
  } catch (err) {
    toast.error(toUserFacingError(err, 'Failed to send password reset email.'));
  } finally {
    setSendingReset(false);
  }
};
If you are an admin user, an Open admin dashboard button appears in the Account Identity card. This navigates to /admin. Admin link (ProfilePage.tsx:509-517):
{isAdmin ? (
  <a
    href="/admin"
    className="inline-flex h-9 w-full items-center justify-center gap-2 rounded-xl border border-[#60A5FA]/30 bg-[#60A5FA]/10 px-3 text-sm text-[#BFDBFE] transition-colors hover:bg-[#60A5FA]/20"
  >
    <Shield size={14} />
    Open admin dashboard
  </a>
) : null}

Landing Ripper Jobs

The Landing Ripper Jobs section displays background page capture jobs you’ve initiated.

Job Status Filters

Filter jobs by status:
  • All statuses: Show all jobs
  • In progress: Jobs that are queued, preparing, capturing, or packaging
  • Completed: Jobs that finished successfully (full or partial)
  • Failed: Jobs that encountered errors
  • Canceled: Jobs you manually canceled

Job Actions

1

Download artifacts

Click ZIP, Desktop HTML, or Mobile HTML to download available artifacts. Buttons are disabled if artifacts are not available.
2

Retry failed jobs

Click Retry on failed, partial, or canceled jobs to re-queue the capture.
3

Cancel in-progress jobs

Click Cancel on queued or processing jobs to abort the capture.
Job action buttons (ProfilePage.tsx:686-770):
<button
  type="button"
  onClick={() => {
    if (job.artifacts.zip.downloadUrl) {
      window.open(job.artifacts.zip.downloadUrl, '_blank', 'noopener,noreferrer');
    }
  }}
  disabled={!job.artifacts.zip.available}
  className={cn(
    'inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs font-semibold transition-all',
    job.artifacts.zip.available
      ? 'bg-[#151519] border-[#1A1A22] text-[#F5F5F0] hover:bg-[#1B1B21]'
      : 'bg-[#151519] border-[#2A2A35] text-[#5F5F6A] cursor-not-allowed',
  )}
>
  <Download size={12} />
  ZIP
</button>

New Job Badge

If you have new completed jobs since your last visit, a new badge appears next to the section header. Click Mark seen to clear the badge. New job tracking (ProfilePage.tsx:360-370):
const handleMarkRipperSeen = () => {
  const latestCompletedAt = getLatestCompletedAt(ripperJobs);
  if (!latestCompletedAt) {
    setRipperNewCount(0);
    return;
  }

  setLandingRipperLastSeenCompletedAt(latestCompletedAt);
  setRipperNewCount(computeLandingRipperNewCompletionsCount(ripperJobs));
  toast.success('Ripper completion badge cleared.');
};

Refresh and Sign Out

Refresh Profile Data

Click the Refresh button in the header to reload your profile data and landing ripper jobs from the server. Refresh handler (ProfilePage.tsx:233-255):
const handleRefresh = async () => {
  const needsBlockingLoad = !profile;
  if (needsBlockingLoad) {
    setLoading(true);
  } else {
    setRefreshing(true);
  }
  setError(null);

  try {
    const data = await getCurrentUserProfile();
    setProfile(data);
    setForm(buildInitialForm(data));
    await loadRipperJobs(ripperStatusFilter);
  } catch (err) {
    setError(toUserFacingError(err, 'Failed to refresh profile.'));
  } finally {
    if (needsBlockingLoad) {
      setLoading(false);
    }
    setRefreshing(false);
  }
};

Sign Out

Click the Log out button in the header to sign out of your account. You’ll be redirected to the login page. Sign out handler (ProfilePage.tsx:321-332):
const handleSignOut = async () => {
  setSigningOut(true);

  try {
    await getSupabaseClient().auth.signOut();
    window.location.assign('/app');
  } catch (err) {
    toast.error(toUserFacingError(err, 'Sign out failed.'));
  } finally {
    setSigningOut(false);
  }
};
Signing out terminates your session immediately. You’ll need to sign in again to access your account.

Build docs developers (and LLMs) love