Notifications
The signed-in customer's notifications, such as order status changes, as one list with read and unread styling.
import { Notifications, NotificationsPage, notificationsLoader, NotificationsPageProps, Notification } from '@salla.sa/twilight-theme-engine/routes/account';In plain words
/account/notifications lists what the store told the customer: an order shipped, a product is back in stock. Unread ones are highlighted, and each links to what it is about.
The page component only reads its props, so it can be drawn from any list of notifications, as the demo does with sample data.
Signature
const Notifications: {
readonly id: 'customer.notifications';
readonly loader: (
ctx: { locale?: string },
extend?: (data: NotificationsPageProps, ctx: { params: {} }) => Record<string, unknown> | Promise<Record<string, unknown>>
) => Promise<NotificationsPageProps>;
readonly head: (ctx: TwilightContext, data: NotificationsPageProps) => HeadDescriptor;
readonly Component: React.LazyExoticComponent<(props: NotificationsPageProps) => JSX.Element>;
};
const NotificationsPage: typeof Notifications.Component;
function notificationsLoader(ctx: { locale: string }): Promise<NotificationsPageProps>; // locale unused
interface NotificationsPageProps {
page: Page; // { title, slug: 'notifications' }
notifications: Notification[]; // no pagination
}
interface Notification {
id: number;
title: string;
body: string;
url: string;
is_read: boolean;
time_ago: string;
created_at: number;
color: string;
icon: string;
}Try it live
import { Suspense } from 'react';
import {
NotificationsPage,
type NotificationsPageProps,
} from '@salla.sa/twilight-theme-engine/routes/account';
// NotificationsPage is lazy: outside a route, give it a Suspense boundary.
export function AccountInbox(props: NotificationsPageProps) {
return (
<Suspense fallback={null}>
<NotificationsPage {...props} />
</Suspense>
);
}
Example
import { createFileRoute } from '@tanstack/react-router';
import { Notifications } from '@salla.sa/twilight-theme-engine/routes/account';
import type { NotificationsPageProps } 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/notifications')({
loader: ({ params }): Promise<NotificationsPageProps> =>
Notifications.loader({ locale: params.locale }),
head: withHead(Notifications),
pendingComponent: () => <CustomerPageSkeleton />,
component: NotificationsComponent,
});
function NotificationsComponent() {
const data: NotificationsPageProps = Route.useLoaderData();
return <Notifications.Component {...data} />;
}
How it behaves
notificationsLoaderwraps the API call inorUnauthorized(a 401 or 403 shows the 401 page) and returns itsdataarray asnotifications.NotificationsPageignorespage. It draws each notification with its title, body andtime_ago, marks unread ones, and showsNoContentfor an empty list, between the slotscustomer:notifications.items.startandcustomer:notifications.items.end.color,iconandcreated_atare in the data but not used by the component.headsets the title and hreflang alternates only.
Gotchas
packages/theme-engine/docs/notifications-route.md mounts it as
component: Notifications.Component. A route component receives no props, sonotificationsisundefinedand the page throws onnotifications.length. SpreadRoute.useLoaderData()into it, as the generated route does. ThehasMoreprop and the notification fields that doc lists (is_new,sub_title,date) do not exist.Each notification is a plain
<a href>, not the engineLink: opening one loads the whole page again instead of navigating inside the app.A guest sees a 400 page, not the 401 page: Salla answers the notifications request without a customer token with
400 token_not_provided, whichorUnauthorizedpasses on.