Profile
Let signed-in users view and edit their profile. Compose your own form field-by-field with <u-profile> + <u-field>, or drop in <u-full-profile> for a complete UI in one tag. Custom attributes, SSO navigation to connected services, and account deletion round it out.
View this guide as MarkdownComposed profile form
u-profile loads and saves the data; each u-field renders one profile field with validation, labels and the right input type (date picker, phone, country select with flags). You own the layout — group fields in any markup you like.
- Field-by-field composition — u-field per profile attribute, your layout
- Typed inputs — dates, phone numbers, country select with icons
- Validation + labels — render-default-label and built-in error handling
<u-signed-in>
<!-- u-profile loads the user's data; each u-field binds one profile field.
partial-validation limits validation to the fields rendered here. -->
<u-profile partial-validation="true">
<u-field field="salutation" render-default-label="true"></u-field>
<div class="grid gap-x-4 sm:grid-cols-2">
<u-field field="first_name" render-default-label="true"></u-field>
<u-field field="last_name" render-default-label="true"></u-field>
<u-field field="date_of_birth" required="true" render-default-label="true"></u-field>
<u-field field="phone_number" render-default-label="true"></u-field>
</div>
<u-field field="country_code" country-code-display-option="icon" render-default-label="true"
></u-field>
<u-submit-button
class-name="mt-4 px-4 py-2 text-white bg-accent hover:bg-accent-hover rounded-lg disabled:opacity-50 w-full cursor-pointer">
{t('Spara ändringar', 'Save changes')}
</u-submit-button>
</u-profile>
</u-signed-in>
<u-signed-in not>
<p class="text-text-light">
{t('Logga in på', 'Sign in on the')} <a href="/auth" class="text-primary underline">{t('inloggningssidan', 'Auth page')}</a> {t('för att redigera din profil.', 'to edit your profile.')}
</p>
</u-signed-in> Full profile in one tag
u-full-profile renders a complete, ready-made profile editor. Use the fields attribute to pick and order fields (including custom_attributes.*), or omit it to show everything the tenant defines. The fastest path to a working account page.
- One component — complete profile UI with zero markup
- Field selection — fields="first_name,last_name,…" picks and orders
<u-signed-in>
<!-- One component, complete profile UI. Limit and order fields with the
fields attribute — or omit it to render everything the tenant defines. -->
<u-full-profile
fields="first_name,last_name,date_of_birth,country_code"
country-code-display-option="icon"></u-full-profile>
</u-signed-in>
<u-signed-in not>
<p class="text-text-light">
{t('Logga in på', 'Sign in on the')} <a href="/auth" class="text-primary underline">{t('inloggningssidan', 'Auth page')}</a> {t('för att se hela profilen', 'to see the full profile')}
component.
</p>
</u-signed-in> Custom attributes
Tenant-defined custom attributes behave like normal fields via u-field — or use u-raw-field to build fully custom inputs. These sport chips are plain checkboxes bound to a multi-value attribute, styled with CSS only.
- custom_attributes.* fields — text, select, multi-select, radio groups
- u-raw-field escape hatch — any UI you can build with native inputs
<u-signed-in>
<u-profile partial-validation="true">
<p class="mb-2 text-sm font-medium">{t.member_number}</p>
<u-field field="custom_attributes.member_number"></u-field>
<!-- Configured "show, no edit" in Admin — the SDK renders it readonly from the
form schema. Value is set server-side by the subscription demo workflow. -->
<p class="mb-2 mt-4 text-sm font-medium">{t.active_membership}</p>
<u-field field="custom_attributes.active_membership"></u-field>
<p class="mb-2 mt-4 text-sm font-medium">{t.favorite_nut}</p>
<u-field field="custom_attributes.favorite_nut"></u-field>
<u-submit-button
class-name="mt-4 px-4 py-2 text-white bg-accent hover:bg-accent-hover rounded-lg disabled:opacity-50 w-full cursor-pointer">
{t.save}
</u-submit-button>
</u-profile>
</u-signed-in>
<u-signed-in not>
<p class="text-text-light">
{t.signin_hint} <a href="/auth" class="text-primary underline">{t.signin_link}</a> {t.signin_rest}
</p>
</u-signed-in>
<style is:inline>
/* Hide the schema default label — we render translated labels above */
u-field[field^="custom_attributes."]::part(field_label) {
display: none;
}
</style> Medlemsnummer
Aktivt medlemskap
Favoritnöt
Logga in på inloggningssidan för att redigera din profil.
Header profile icon
The classic header pattern: u-signed-in swaps a sign-in link for an avatar menu with profile and logout entries. Pure CSS hover dropdown — no JS required.
- Signed-in/out switch — u-signed-in / u-signed-in not
- u-logout-button anywhere — style it like any menu item
<!-- A header user-menu: avatar button with a hover dropdown.
u-signed-in / u-signed-in not switch between menu and sign-in link. -->
<div class="flex justify-end">
<u-signed-in>
<div class="group relative inline-block">
<button
class="flex items-center justify-center rounded-full border border-border bg-white p-2 shadow-sm hover:bg-background-light"
aria-label="User menu">
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="8" r="4"></circle>
<path d="M4 20c0-3.5 3.5-6 8-6s8 2.5 8 6"></path>
</svg>
</button>
<!-- hover bridge so the menu doesn't close between button and panel -->
<div class="absolute left-0 right-0 top-full h-2"></div>
<div
class="absolute right-0 top-full z-20 mt-2 hidden w-48 rounded-lg border border-border bg-white shadow-xl group-hover:block">
<a href="/profile" class="block px-4 py-2 text-sm hover:bg-background-light"
>Profile settings</a
>
<div class="border-t border-border"></div>
<u-logout-button
class-name="block w-full text-left px-4 py-2 text-sm hover:bg-background-light cursor-pointer">
Sign out
</u-logout-button>
</div>
</div>
</u-signed-in>
<u-signed-in not>
<a href="/auth" class="btn btn-primary !min-h-0 !px-4 !py-2 text-sm">Sign in</a>
</u-signed-in>
</div> Hosted Unidy pages & account deletion
u-jump-to-unidy generates a one-time login token and opens Unidy's hosted pages — including the profile page where account deletion lives (an App-Store requirement most integrations need). no-auth skips the token for public pages.
- Hosted Unidy pages — subscriptions, tickets, terms, profile
- Account deletion — deep-link to /profile#logindata
<u-signed-in>
<div class="flex flex-col gap-3">
<!-- One-time-token jump into Unidy's hosted pages -->
<u-jump-to-unidy
path="/subscriptions"
newtab
class-name="px-4 py-2 border border-border text-sm font-medium rounded-lg cursor-pointer hover:bg-background-light disabled:opacity-50">
Manage subscriptions in Unidy
</u-jump-to-unidy>
<!-- Pages that need no authentication: add no-auth -->
<u-jump-to-unidy
path="/terms_of_use"
no-auth
newtab
class-name="text-sm text-text-light underline cursor-pointer hover:text-text">
Terms of use (no auth)
</u-jump-to-unidy>
<!-- Account deletion lives on Unidy's profile page — deep-link straight to it -->
<u-jump-to-unidy
path="/profile#logindata"
newtab
class-name="px-4 py-2 bg-danger hover:bg-danger-hover text-white text-sm font-medium rounded-lg cursor-pointer disabled:opacity-50">
Delete my account
</u-jump-to-unidy>
</div>
</u-signed-in>
<u-signed-in not>
<p class="text-text-light">
Sign in on the <a href="/auth" class="text-primary underline">Auth page</a> to use hosted-page navigation.
</p>
</u-signed-in> Sign in on the Auth page to use hosted-page navigation.
Connected services
Show which third-party services a user has connected, and let them connect or disconnect in place. The connect flow runs on the /partner page (which holds the u-oauth-provider); this card checks the live connection state via the service_connections endpoint.
- Connect / disconnect — visual state card with one-click actions
- service_connections API — live connection state, no local guessing
- u-signed-in gating — only shown to authenticated users
{clientId ? (
<>
<u-signed-in>
<div id="pfProvider" class="space-y-3" data-client-id={clientId}>
<!-- Disconnected state -->
<div id="pfLocked" class="flex items-center justify-between p-4 rounded-xl border border-border bg-background-light">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-[#001a3d] flex items-center justify-center flex-shrink-0">
<img src="/images/djurgardens-logo.svg" alt="" class="w-7 h-7" />
</div>
<div>
<p class="font-semibold text-sm">Djurgården Fan Portal</p>
<p class="text-xs text-text-muted">{t('Få personligt matchdagsinnehåll', 'Get personalized matchday content')}</p>
</div>
</div>
<a
href="/partner"
class="text-xs font-medium px-3 py-1.5 rounded-lg bg-primary text-white hover:bg-primary-dark transition-colors cursor-pointer no-underline"
>
Connect
</a>
</div>
<!-- Connected state -->
<div id="pfConnected" class="flex items-center justify-between p-4 rounded-xl border border-green-200 bg-green-50">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0">
<svg class="w-4 h-4 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/>
</svg>
</div>
<div>
<p class="font-semibold text-sm text-green-900">Djurgården Fan Portal</p>
<p class="text-xs text-green-700">Connected</p>
</div>
</div>
<button
id="pfDisconnectBtn"
class="text-xs font-medium cursor-pointer px-3 py-1.5 rounded-lg border border-error-border text-danger hover:bg-error-bg transition-colors disabled:opacity-50"
>
Disconnect
</button>
</div>
</div>
</u-signed-in>
<u-signed-in not>
<p class="text-text-muted text-sm">{t('Logga in för att hantera anslutna tjänster.', 'Sign in to manage connected services.')}</p>
</u-signed-in>
</>
) : (
<p class="text-text-muted text-sm">No partner service configured for this demo.</p>
)}
<style>
#pfConnected { display: none; }
#pfProvider.pf-connected #pfLocked { display: none; }
#pfProvider.pf-connected #pfConnected { display: flex; }
</style>
<script type="module">
import { getUnidyClient, onAuthChange } from "https://cdn.jsdelivr.net/npm/@unidy.io/sdk@1.9.0/dist/sdk/index.esm.js";
const provider = document.getElementById("pfProvider");
const disconnectBtn = document.getElementById("pfDisconnectBtn");
const PARTNER_CLIENT_ID = provider?.dataset.clientId;
function showConnected() { provider?.classList.add("pf-connected"); }
function showDisconnected() { provider?.classList.remove("pf-connected"); }
async function getServiceHeaders() {
const client = getUnidyClient();
const idToken = await client.oauth.getIdToken();
return {
"Authorization": "Token " + client.apiClient.api_key,
"X-Id-Token": idToken,
};
}
async function checkConnection() {
if (!PARTNER_CLIENT_ID) return;
try {
const client = getUnidyClient();
const headers = await getServiceHeaders();
const resp = await fetch(client.apiClient.baseUrl + "/api/sdk/v1/service_connections", { headers });
if (!resp.ok) { showDisconnected(); return; }
const services = await resp.json();
const connected = Array.isArray(services) && services.some((s) => s.id === PARTNER_CLIENT_ID);
connected ? showConnected() : showDisconnected();
} catch {
showDisconnected();
}
}
disconnectBtn?.addEventListener("click", async () => {
if (!PARTNER_CLIENT_ID) return;
disconnectBtn.disabled = true;
try {
const client = getUnidyClient();
const headers = await getServiceHeaders();
const resp = await fetch(
client.apiClient.baseUrl + "/api/sdk/v1/service_connections/" + encodeURIComponent(PARTNER_CLIENT_ID),
{ method: "DELETE", headers }
);
if (!resp.ok) throw new Error("HTTP " + resp.status);
showDisconnected();
} catch {
/* keep connected state on failure */
} finally {
disconnectBtn.disabled = false;
}
});
// Poll until u-config is initialized (waitForConfig is not exported by the SDK)
await new Promise(resolve => {
const iv = setInterval(() => {
try { getUnidyClient(); clearInterval(iv); resolve(); } catch {}
}, 50);
});
checkConnection();
onAuthChange(checkConnection);
</script> Djurgården Fan Portal
Få personligt matchdagsinnehåll
Djurgården Fan Portal
Connected
Logga in för att hantera anslutna tjänster.