Skip to content

<seva-my-profile>

The <seva-my-profile> component renders a trigger button and a modal overlay for profile management. Users can view their login info, edit member directory details, and add or change their phone number with SMS OTP verification.

When opened, the modal fetches the user’s profile from the API and displays login info (email, phone with verified/unverified badge) and member directory info (if the user is a member). Members can edit their directory contact info and add or verify a phone number. If the user is not authenticated, the modal shows a sign-in prompt with an embedded <seva-auth> form.

This is a modal component — it self-manages authentication from localStorage and does not require a glue script to propagate auth tokens. See Inline vs. modal components for details.

<script type="module" src="YOUR_COMPONENTS_URL/seva-my-profile.js"></script>
<seva-my-profile
tenant-slug="my-club"
api-url="https://api.seva.tools">
</seva-my-profile>
<!-- The element above includes a built-in trigger button.
You can also open the modal from your own button: -->
<button onclick="document.dispatchEvent(new CustomEvent('seva:open-my-profile'))">
My Profile
</button>

The <seva-my-profile> element must be in the DOM — it listens for the seva:open-my-profile event. The element renders its own “My Profile” trigger button, but you can also open the modal from a custom button by dispatching the event on document.

AttributeTypeDefaultDescription
tenant-slugstring''Required. Your organization’s tenant slug.
api-urlstring''Base URL of the Seva API.
theme'light' | 'dark''light'Color theme. Set via JavaScript property.
auth-tokenstring''Session token. Modal components typically self-manage this from localStorage, so you rarely need to set it manually.
EventPayloadWhen it fires
seva:profile-updated{}Member directory info or phone number was saved successfully.

The component listens on document for:

EventEffect
seva:authenticatedStores the token, refreshes profile data if the modal is open.
seva:signed-outClears auth state and profile data.
seva:open-my-profileOpens the modal.
loading ──→ display ──→ edit-member ──→ display (on save)
└──→ phone-otp ──→ display (on verify success)
├── enter-phone (OTP not yet sent)
└── enter-code (OTP sent, awaiting code)
  • loading — Spinner while fetching profile from API.
  • display — Shows login info section (email, phone with Verified/Unverified badge) and member directory info section (email, phone, with Edit button for active members). Inactive members see an “inactive membership” notice.
  • edit-member — Form to edit directory contact info (phone, email). Validates with Zod on submit.
  • phone-otp — Two-step phone verification: enter phone number → receive SMS code → enter code → phone verified.

The modal can be opened in three ways:

  1. Built-in trigger button — The component renders a “My Profile” button automatically.
  2. Custom event — Dispatch seva:open-my-profile on document:
    document.dispatchEvent(new CustomEvent('seva:open-my-profile'));
  3. Direct method call — Call openModal() on the element:
    document.querySelector('seva-my-profile').openModal();

Displays the user’s email and phone number. If a phone number is set, a Verified or Unverified badge is shown. A Change (or Add) button opens the phone OTP flow.

Shown only if the user has a linked member record. Displays directory email and phone, with an Edit button for active members. If the membership is inactive, an “inactive membership” notice is shown instead of the edit button.

type MyProfileView = 'loading' | 'display' | 'edit-member' | 'phone-otp';
interface MyProfileUser {
id: string;
firstName: string;
lastName: string;
email: string;
phoneNumber: string | null;
phoneNumberVerified: boolean;
}
interface MyProfileMember {
id: string;
firstName: string | null;
lastName: string | null;
email: string | null;
phone: string | null;
membershipNumber: string | null;
membershipTypeName: string | null;
duesStatus: string | null;
active: boolean;
joinDate: string | null;
}
interface MyProfileResponse {
user: MyProfileUser;
member: MyProfileMember | null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Profile</title>
<script type="module" src="YOUR_COMPONENTS_URL/seva-my-profile.js"></script>
<style>
body { font-family: system-ui, sans-serif; max-width: 400px; margin: 2rem auto; }
</style>
</head>
<body>
<h1>My Account</h1>
<seva-my-profile
tenant-slug="my-club"
api-url="https://api.seva.tools">
</seva-my-profile>
<!-- Or use a custom button -->
<button onclick="document.dispatchEvent(new CustomEvent('seva:open-my-profile'))">
Open Profile
</button>
<script>
document.addEventListener('seva:profile-updated', () => {
console.log('Profile was updated');
});
</script>
</body>
</html>