Skip to content
Twilight React Playground
ثيم رائدaren

Profile, Settings

route-moduleBeginnerserverbrowser

The signed-in customer's profile and account settings pages, built from Salla's ready-made web components and refused to guests.

import { Profile, ProfilePage, profileLoader, ProfilePageProps, Settings, SettingsPage, settingsLoader, SettingsPageProps } from '@salla.sa/twilight-theme-engine/routes/account';

In plain words

The pages under /account belong to a signed-in customer. /account/profile edits their name, email and phone, and /account/settings holds their preferences. Both forms are Salla's ready-made web components, so these loaders only check that someone is signed in and set the title.

Every account page renders inside the account layout, next to the customer menu. "Customer account" on Built-in pages opens the profile page; as a guest you see what the engine shows a visitor who is not signed in.

Signature

const Profile: {
  readonly id: 'customer.profile';
  readonly loader: (
    ctx: { locale?: string },
    extend?: (data: ProfilePageProps, ctx: { params: {} }) => Record<string, unknown> | Promise<Record<string, unknown>>
  ) => Promise<ProfilePageProps>;
  readonly head: (ctx: TwilightContext, data: ProfilePageProps) => HeadDescriptor;
  readonly Component: React.LazyExoticComponent<(props: ProfilePageProps) => JSX.Element>;
};
const Settings: { id: 'customer.settings'; loader; head; Component };   // same shape, SettingsPageProps

const ProfilePage: typeof Profile.Component;
const SettingsPage: typeof Settings.Component;
function profileLoader(ctx: { locale: string }): Promise<ProfilePageProps>;    // locale required, unused
function settingsLoader(ctx: { locale: string }): Promise<SettingsPageProps>;

interface ProfilePageProps { page: Page }    // { title, slug: 'profile' }
interface SettingsPageProps { page: Page }   // { title, slug: 'settings' }

Example

app/routes/account.profile.tsx (generated)
import { createFileRoute } from '@tanstack/react-router';
import { Profile } from '@salla.sa/twilight-theme-engine/routes/account';
import type { ProfilePageProps } from '@salla.sa/twilight-theme-engine/routes/account';
import { CustomerPageSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';

export const Route = createFileRoute('/{-$locale}/account/profile')({
  loader: ({ params }): Promise<ProfilePageProps> => Profile.loader({ locale: params.locale }),
  head: withHead(Profile),
  pendingComponent: () => <CustomerPageSkeleton />,
  component: ProfileComponent,
});

function ProfileComponent() {
  const data: ProfilePageProps = Route.useLoaderData();
  return <Profile.Component {...data} />;
}

How it behaves

  • Both loaders read authToken from the router's request context (the customer's token cookie). Without one they throw unauthorized(), and the router's default error component shows a 401 page with no action button. They make no API request.

  • ProfilePage renders Salla's user-profile, verify and user-settings web components between the slots customer:profile.start, customer:profile.form.start, customer:profile.form.end and customer:profile.end. SettingsPage renders the user-settings component between customer:settings.start and customer:settings.end.

  • Account routes nest under the engine's /{-$locale}/account layout route, which renders CustomerLayout with the page of the deepest loader that returned one. While it is pending it shows the component registered under the key account:layout-pending, or CustomerLayoutSkeleton when none is.

  • head sets the title and hreflang alternates only.

  • All account components are lazy. SettingsPageProps is exported here, not by the /routes barrel.

Gotchas

  • On every /account/* page, useRouteId() returns the router's own id (/{-$locale}/account/profile), not customer.profile: the engine's id map lists /{-$locale}/profile, /{-$locale}/orders and so on, without account (TANSTACK_ROUTE_ID_MAP in packages/theme-engine/src/tanstack/router.tsx). useIsCustomer() is therefore false on these pages.

Related

Source and docs