Ban / pause / delete watcher
Mount once in any authenticated layout. When the user's membership flips off active, the watcher paints a full-screen blocking modal with a 9-second countdown, then signs them out and redirects.
Your access has been revoked.
An administrator banned your account for this app. You'll be signed out and won't be able to sign back in. Contact the owner if you think this was a mistake.
Static snapshot at 6 seconds. The real watcher mounts invisibly and only renders this modal when the user's membership flips off "active".
Renders nothing while membership is active. The modal in the preview above only appears when an admin bans / pauses / deletes the user, or when the user deactivates themselves from another tab.
The four states
Each state renders the same card chrome with its own icon, headline, copy, and tone. The countdown is identical (9 seconds at default), so the only thing the user can do is read why and watch the dots fill in.
| State | Headline | Tone |
|---|---|---|
banned | Your access has been revoked. | Red. Admin action, user cannot sign back in. |
paused | Your access has been paused. | Amber. Temporary admin action, contact owner to restore. |
deleted | Your account was removed. | Red. Membership purged in 90 days unless restored. |
inactive | You're taking a break from this app. | Amber. User-initiated deactivation, fully reversible. |
Usage
import {ElvixLifecycleWatcher,ElvixProvider,} from "@elvix.is/sdk/react";/*** Mount once in the root layout that wraps every authenticated page.* On ban / pause / delete / inactive, the watcher paints a full-screen* modal with a 9-second countdown, then signs the user out.*/export default function AppLayout({ children }: { children: React.ReactNode }) {return (<ElvixProvider clientId="your-app"><ElvixLifecycleWatcher signInUrl="/sign-in" />{children}</ElvixProvider>);}
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
graceSec | number | optional | 9 | Countdown in seconds. The modal renders one progress dot per second. |
signInUrl | string | optional | "/sign-in/console" | Redirect target after the countdown finishes and the session is cleared. |
signOutUrl | string | optional | "/api/auth/sign-out" | Same-origin POST that revokes the cookie. Ignored cross-origin (token is dropped locally instead). |
cookieName | string | optional | "elvix_token" | Cookie cleared alongside the in-memory token. Must match what your sign-in page wrote. |
apiBase | string | optional | none | Set when embedded cross-origin (your app on a different origin than elvix). Switches the transport from SSE to polling against POST /api/v1/session. |
applicationId | string | provider-resolved | from useElvixApp() | App scope. Pass explicitly when mounting outside the provider tree. |
userId | string | provider-resolved | from session | Same shape as applicationId. |
pollMs | number | optional | 7000 | Polling interval when in cross-origin mode. Worst-case sign-out latency is pollMs + graceSec * 1000 (about 16s at defaults). |
onSignedOut | (reason: "banned" | "paused" | "deleted" | "session_expired") => void | optional | reload page | Skip the default reload and own the next navigation client-side. |
Mount this once per signed-in surface (root layout is canonical). Do not mount it on a public marketing layout or your sign-in page. There is no session to watch, the bootstrap call is wasted, and SSE will fail with no userId to subscribe with.
Transport auto-selects. Same-origin gets SSE for near-instant detection. Cross-origin embeds (your app on app.example.com, elvix on elvix.is) fall back to polling every pollMs because EventSource cannot carry the bearer token.
Custom signed-out flow
Without onSignedOut, the watcher does a window.location.replace(signInUrl) after clearing the token + cookie. Pass onSignedOut to skip that and own the navigation: route client-side, fire a toast explaining the reason, log analytics, hand the user to a custom landing page.
"use client";import { ElvixLifecycleWatcher } from "@elvix.is/sdk/react";import { useRouter } from "next/navigation";import { toast } from "sonner";export function SignedInChrome({ children }: { children: React.ReactNode }) {const router = useRouter();return (<><ElvixLifecycleWatchercookieName="elvix_token"onSignedOut={(reason) => {// Token + cookie already cleared. Host owns the next navigation.if (reason === "banned") toast.error("Your access was revoked.");if (reason === "paused") toast("Your access is paused.");if (reason === "deleted") toast("Your membership was removed.");router.replace("/sign-in");}}/>{children}</>);}
Related
ElvixProvider
Top-level context. Fetches the brand chord, factor config, and copy from elvix; pipes session state and the event channel to every nested <Elvix*> component.
ReadElvixSignInForm
The full branded sign-in surface (card, header, brand wash, every Console-enabled method, Secured-by-elvix badge). Exported as both `ElvixSignInForm` and the short alias `ElvixSignIn` — same component.
Read