diff --git a/packages/fastify/README.md b/packages/fastify/README.md
index 5585a8a..2d548d0 100644
--- a/packages/fastify/README.md
+++ b/packages/fastify/README.md
@@ -147,3 +147,17 @@ Install with pnpm:
```bash
pnpm add --filter "<@scope/project>" @prefabs.tech/fastify-config @prefabs.tech/fastify-mailer @prefabs.tech/fastify-s3 @prefabs.tech/fastify-slonik @prefabs.tech/fastify-user slonik supertokens-node @prefabs.tech/saas-fastify
```
+
+## Testing
+
+From the monorepo root:
+
+```bash
+pnpm test --filter @prefabs.tech/saas-fastify
+```
+
+From this package folder:
+
+```bash
+pnpm test
+```
diff --git a/packages/react/ANALYSIS.md b/packages/react/ANALYSIS.md
new file mode 100644
index 0000000..7e2c943
--- /dev/null
+++ b/packages/react/ANALYSIS.md
@@ -0,0 +1,265 @@
+
+
+## Package
+
+- **Path**: `packages/react`
+- **Name**: `@prefabs.tech/saas-react`
+- **Type**: ESM package (`"type": "module"`)
+- **Runtime deps**: `axios`, `zod`
+- **Peer deps (consumed directly by our code)**: `react`, `react-dom`, `react-router-dom`, `react-toastify`, `primereact`, `@prefabs.tech/react-config`, `@prefabs.tech/react-form`, `@prefabs.tech/react-i18n`, `@prefabs.tech/react-ui`
+
+## Entry points & public exports
+
+### `src/index.ts`
+
+- **Module augmentation (OURS)**: augments `@prefabs.tech/react-config`’s `AppConfig` with `saas: SaasConfig`.
+- **Re-exports (OURS, as barrels)**:
+ - `export * from "./api"`
+ - `export * from "./constants"`
+ - `export * from "./hooks"`
+ - `export * from "./routes"`
+ - `export * from "./types"`
+ - `export * from "./utils"`
+ - `export * from "./views"`
+ - `export * from "./SaasWrapper"`
+- **Named exports (OURS)**:
+ - Components: `AccountSwitcher`, `AccountForm`, `AccountInfo`, `AccountInvitationForm`, `AccountInvitationModal`, `AccountInvitationsTable`, `AccountSignupForm`, `AccountUsersTable`, `AccountsTable`, `MyAccounts`, `UserSignupForm`
+ - Contexts: `accountsContext`, `AccountsProvider`
+
+## Base Library Passthrough Analysis
+
+### `axios` — MODIFIED
+
+- **Options type**: base library (`axios`) is used directly; we don’t expose/forward `AxiosRequestConfig` types.
+- **Options passed**: **transformed**
+ - We create a preconfigured instance in `src/api/axios/client.ts` with:
+ - `baseURL` set from our config
+ - default JSON content type for POST
+ - optional `x-account-id` header read from storage
+- **Features restricted**: partial (callers don’t control the instance config beyond `baseURL`; config is embedded in our wrapper).
+- **Features added**:
+ - Automatic `x-account-id` header injection (account session selection)
+ - Shared “API base URL” behavior through `useConfig()` + `client(apiBaseUrl)` pattern
+
+### `zod` — NO WRAPPED DEPENDENCY (used directly)
+
+No wrapped dependency passthrough surface; `zod` is used directly inside our schemas and form validation logic.
+
+### `@prefabs.tech/react-form`, `@prefabs.tech/react-ui`, `@prefabs.tech/react-i18n`, `react-router-dom`, `react-toastify` — NO WRAPPED DEPENDENCY (used directly)
+
+These libraries are consumed directly inside our components/views/routes. We do not provide a thin wrapper that forwards their configuration wholesale; instead we build opinionated components and hooks on top of them.
+
+## “Ours” vs “Theirs” classification
+
+### API layer (`src/api/*`)
+
+- **OURS**
+ - `client(baseURL: string)` (`src/api/axios/client.ts`): creates an `axios` instance and injects headers.
+ - `encodeURIParameter(arg)` (`src/api/utilities.ts`): JSON encodes parameters (or returns `undefined`).
+ - `useQuery(url, parameters?, options?)` (`src/api/common/UseQuery.ts`): React hook wrapping `axios.get` with:
+ - **defaults**: `lazy=false`, `skip=false`
+ - **branching**: auto-trigger on mount unless `lazy`/`skip`
+ - **error normalization**: treats `response.data.status === "ERROR"` as error
+ - `useMutation(options?)` (`src/api/common/UseMutation.ts`): React hook wrapping `axios.request` with:
+ - **defaults**: `method="POST"`, `withCredentials=true`
+ - **error normalization**: treats `response.data.status === "ERROR"` as error
+ - Account endpoints (`src/api/accounts/index.ts`):
+ - `doesAccountExist({ apiBaseUrl })` (GET `/`, `withCredentials: true`)
+ - `getMyAccounts({ apiBaseUrl })` (GET `/my-accounts`, `withCredentials: true`)
+ - `signup({ apiBaseUrl, path, data, accountSignup=true })`:
+ - **default**: `accountSignup = true`
+ - **transformation**: uses `prepareSignupData({ data, accountSignup })`
+
+- **THEIRS (direct calls)**
+ - `axios.create(...)`, `.get(...)`, `.post(...)`, `.request(...)` are used as base library invocations inside our wrappers.
+
+### Context/providers (`src/contexts/*`)
+
+- **OURS**
+ - `accountsContext` + `AccountsProvider` (`src/contexts/AccountsProvider.tsx`)
+ - **defaults** from config: `autoSelectAccount=true`, `allowMultipleSessions=true`
+ - **branches**:
+ - Determines `isMainApp` based on `window.location.host === mainApp?.domain`
+ - Chooses active account based on:
+ - subdomain match when **not** main app
+ - saved account id from storage (session wins when enabled)
+ - `autoSelectAccount` and “only one account” fallback
+ - **side effects**:
+ - Writes/removes `x-account-id` in `localStorage` and optionally `sessionStorage`
+ - Fetches accounts when `userId` is present
+ - `configContext` + `ConfigProvider` (`src/contexts/ConfigProvider.tsx`)
+
+### Hooks (`src/hooks/*`)
+
+- **OURS**
+ - `useAccounts()` (`src/hooks/UseAccounts.ts`): reads `accountsContext`; throws if missing provider.
+ - `useConfig()` (`src/hooks/UseConfig.ts`): reads `configContext`; throws if missing provider.
+ - Account data hooks (thin wrappers over `useQuery`/`useMutation`):
+ - `useAddAccountMutation`
+ - `useEditAccountMutation` (**default override**: forces `method: "PUT"` while spreading provided options)
+ - `useGetAccountQuery`
+ - `useGetMyAccountQuery`
+ - Invitation hooks:
+ - `useAddInvitationMutation`
+ - `useDeleteInvitationMutation`
+ - `useGetInvitationQuery`
+ - `useGetInvitationsQuery`
+ - `useJoinInvitationMutation`
+ - `useResendInvitationMutation`
+ - `useRevokeInvitationMutation`
+ - `useSignupInvitationMutation`
+ - User hooks:
+ - `useDisableUserMutation`
+ - `useEnableUserMutation`
+ - `useGetUsersQuery`
+
+- **THEIRS (direct calls)**
+ - React hooks (`useContext`, `useCallback`, etc.) are used directly as building blocks.
+
+### Utils (`src/utils/*`)
+
+- **OURS**
+ - `checkIsAdminApp()` (`src/utils/common.ts`):
+ - **default**: admin subdomain constant `ADMIN_SUBDOMAIN_DFAULT = "admin"`
+ - **branch**: compares subdomain to admin constant
+ - `prepareUiConfig(ui?)` (`src/utils/config.ts`):
+ - **defaults**: merges `CONFIG_UI_DEFAULT` into provided UI config
+ - `prepareConfig(config)` (`src/utils/config.ts`):
+ - **defaults**:
+ - `mainApp.subdomain` defaults to `"app"`
+ - `mainApp.domain` defaults to `"{subdomain}.{rootDomain}"` (supports deprecated `mainAppSubdomain`)
+ - `ui` defaults come from `CONFIG_UI_DEFAULT`
+ - **transformation**: returns a normalized `SaasConfig` used by providers/components
+ - `prepareSignupData({ data, accountSignup=true })` (`src/utils/account.ts`):
+ - **branch**: different shape for account vs user signup payload
+ - **defaults**:
+ - `accountSignup = true`
+ - `useSeparateDatabase` becomes `false` when slug missing
+
+### Routes (`src/routes/*`)
+
+- **OURS**
+ - `getSaasAdminRoutes(type="authenticated", options?)` (`src/routes/GetSaasAdminRoutes.tsx`)
+ - **branch**: for `type === "unauthenticated"` or `"public"`, returns no routes.
+ - **default paths**: uses `DEFAULT_PATHS.*` (path overwrite is TODO; `element` can be overwritten)
+ - `GetSaasAppRoutes({ type="authenticated", options })` and `getSaasAppRoutes(type="authenticated", options?)`
+ - **branch**:
+ - `"authenticated"`: account settings, join invitation (disabled when not main app), my accounts
+ - `"unauthenticated"`: invitation signup + signup
+ - `"public"`: accept invitation
+ - **dependency**: uses `useAccounts()` to read `isMainApp` meta
+
+### UI components (`src/components/*`)
+
+- **OURS**
+ - `AccountSwitcher` (`src/components/accounts/Switcher.tsx`)
+ - **defaults**: `noHelperText=false`
+ - **branch**: returns loading icon when accounts are not loaded
+ - `AccountsTable` (`src/components/accounts/Table/index.tsx`)
+ - **defaults**:
+ - `className="table-accounts"`
+ - `visibleColumns=["name","registeredNumber","taxId","type"]`
+ - `persistState=true`
+ - `initialSorting=[{ id: "name", desc: false }]`
+ - **branch**: column set changes based on config `entity`
+ - `MyAccounts` + `Account` (`src/components/MyAccounts/*`)
+ - `AccountForm` (`src/components/account/Form/AccountForm.tsx`)
+ - **validation**: zod schema; slug requirements depend on `subdomains === "required"`
+ - **defaultValues**: derive from existing account or config `entity`
+ - `AccountInvitationForm` (`src/components/account/Invitations/InvitationForm.tsx`)
+ - **defaults**:
+ - `roles`: `customRoles || saasAccountRoles || SAAS_ACCOUNT_ROLES_DEFAULT`
+ - default values include `expiresAt` only when `expiryDateField.display`
+ - role auto-selected when exactly one role is available
+ - **conditional schema**:
+ - merges role schema if roles exist
+ - merges expiresAt schema if expiry date displayed
+ - merges additional schema if provided
+ - **side effects**: shows toast notifications on success/failure
+ - Signup components
+ - `AccountSignupForm` (`src/components/Signup/AccountSignupForm.tsx`)
+ - **defaults**:
+ - `activeIndex=0` (2-step flow)
+ - default form values initialized for account + user fields
+ - **branching**:
+ - schema switches by `activeIndex`
+ - terms & conditions field required only when `termsAndConditionsUrl` present
+ - submit button label changes between “Next” and “Submit”
+ - password/confirmPassword must match (refine)
+ - `UserSignupForm` (`src/components/Signup/UserSignupForm.tsx`)
+ - **defaults**: email defaults to provided `email || ""`
+ - **branching**: terms & conditions requirement depends on `termsAndConditionsUrl`
+ - Other exported component surface (via barrel exports):
+ - `AccountInfo`
+ - `AccountInvitationModal`
+ - `AccountInvitationsTable`
+ - `AccountUsersTable`
+ - (and supporting field/table components exported from `src/components/*` barrels)
+
+- **THEIRS (direct calls)**
+ - UI, form, i18n libraries are called directly inside components (`@prefabs.tech/react-ui`, `@prefabs.tech/react-form`, `@prefabs.tech/react-i18n`, `react-toastify`).
+
+### Views/pages (`src/views/*`)
+
+- **OURS**
+ - `AccountAddPage`, `AccountEditPage`, `AccountSettingsPage`, `AccountViewPage`
+ - `AcceptInvitationPage`, `JoinInvitationPage`, `SignupInvitationPage`
+ - `MyAccountsPage`, `SignupPage`
+
+These are mostly composition over our components plus Prefabs UI primitives (`Page`, etc.) and translations.
+
+## Framework constructs / lifecycle / side effects
+
+- **React context**: `accountsContext`, `configContext`
+- **React hooks used for lifecycle**:
+ - `useEffect` in `SaasWrapper` triggers an API call to check domain registration.
+ - `useEffect` in `AccountsProvider` triggers `fetchMyAccounts` when `userId` is present.
+- **Routing**: returns `` elements for `react-router-dom` integration.
+- **Storage side effects**: `AccountsProvider.switchAccount()` writes/removes `x-account-id` to/from local and optional session storage.
+- **Notifications**: invitation form uses `react-toastify` to show success/error toasts.
+
+## Conditional branches & feature flags (observed)
+
+- **Config normalization** (`prepareConfig`)
+ - `mainApp.subdomain` fallback (`"app"`)
+ - `mainApp.domain` computed from subdomain + `rootDomain` when missing
+ - `ui` merged with `CONFIG_UI_DEFAULT`
+- **Admin vs app mode**:
+ - `checkIsAdminApp()` uses subdomain comparison to `"admin"`
+ - `SaasWrapper` chooses whether to wrap children in `AccountsProvider` based on admin/app
+- **Routes**:
+ - Admin routes removed for unauthenticated/public mode
+ - App routes vary by authenticated/unauthenticated/public
+ - Invitation join route disabled when not `isMainApp`
+- **Account selection**:
+ - auto-select account unless disabled
+ - saved account id resolution uses session first when enabled
+ - subdomain-based account selection when not main app
+- **Signup forms**:
+ - optional terms-and-conditions validation based on presence of `termsAndConditionsUrl`
+ - slug validation strictness depends on `subdomains === "required"`
+
+## Default values (observed)
+
+- **Constants** (`src/constants.ts`)
+ - `ACCOUNT_HEADER_NAME = "x-account-id"`
+ - `ADMIN_SUBDOMAIN_DFAULT = "admin"`
+ - `SIGNUP_PATH_DEFAULT = "/auth/signup"`
+ - `SAAS_ACCOUNT_ROLES_DEFAULT = ["SAAS_ACCOUNT_OWNER", "SAAS_ACCOUNT_MEMBER"]`
+ - `DEFAULT_PATHS` for app/admin routing
+ - `CONFIG_UI_DEFAULT` for form action alignment/reversal
+- **Hooks**
+ - `useQuery`: `lazy=false`, `skip=false`
+ - `useMutation`: `method="POST"`, `withCredentials=true`
+- **AccountsProvider**
+ - `autoSelectAccount=true`, `allowMultipleSessions=true` (from config fallback)
+- **prepareConfig**
+ - `mainApp.subdomain="app"` when missing
+
+## Completeness checklist
+
+- [x] Classified every **public export category** as "ours" vs "theirs"
+- [x] Listed framework constructs (contexts, hooks, routing)
+- [x] Identified conditional branches (config normalization, routing mode, admin/app, signup schema conditions)
+- [x] Documented default values we define (constants, hook defaults, provider defaults, config defaults)
+- [x] Produced passthrough classification for wrapped dependency (`axios`)
diff --git a/packages/react/FEATURES.md b/packages/react/FEATURES.md
new file mode 100644
index 0000000..ad8321e
--- /dev/null
+++ b/packages/react/FEATURES.md
@@ -0,0 +1,88 @@
+
+
+# @prefabs.tech/saas-react — Features
+
+## Configuration & Defaults
+
+1. **App config augmentation**: augments `@prefabs.tech/react-config`’s `AppConfig` with `saas: SaasConfig`.
+
+2. **Default constants**: exports defaults for:
+ - `ACCOUNT_HEADER_NAME = "x-account-id"`
+ - `ADMIN_SUBDOMAIN_DFAULT = "admin"`
+ - `SIGNUP_PATH_DEFAULT = "/auth/signup"`
+ - `SAAS_ACCOUNT_ROLES_DEFAULT = ["SAAS_ACCOUNT_OWNER", "SAAS_ACCOUNT_MEMBER"]`
+ - `DEFAULT_PATHS` for app/admin routes
+
+3. **UI defaults merging**: `prepareConfig` merges `CONFIG_UI_DEFAULT` into `config.ui` (account/invitation/signup form action alignment + reversal).
+
+## HTTP & API Helpers (axios)
+
+4. **Axios client factory**: `client(baseURL)` creates a preconfigured axios instance and injects `x-account-id` from storage.
+
+5. **Domain registration check**: `doesAccountExist({ apiBaseUrl })` calls `GET /` with credentials.
+
+6. **Fetch user accounts**: `getMyAccounts({ apiBaseUrl })` calls `GET /my-accounts` with credentials.
+
+7. **Signup API**: `signup({ apiBaseUrl, path, data, accountSignup=true })` shapes payload via `prepareSignupData` and posts to `path`.
+
+8. **Query hook**: `useQuery(url, parameters?, options?)` wraps `axios.get` and provides:
+ - defaults: `lazy=false`, `skip=false`
+ - an imperative `trigger()`
+ - error normalization when `response.data.status === "ERROR"`
+
+9. **Mutation hook**: `useMutation(options?)` wraps `axios.request` and provides:
+ - defaults: `method="POST"`, `withCredentials=true`
+ - an imperative `trigger(url, data?)`
+ - error normalization when `response.data.status === "ERROR"`
+
+## Contexts & Hooks
+
+10. **Config context**: `ConfigProvider` provides SaaS config and `useConfig()` reads it (throws if missing).
+
+11. **Accounts context**: `AccountsProvider` provides accounts state and `useAccounts()` reads it (throws if missing).
+
+12. **Account selection rules**: active account is computed from:
+ - main app vs tenant app mode
+ - saved account id in storage (session preferred when enabled)
+ - `autoSelectAccount` and “single account” fallback
+
+13. **Account persistence**: switching accounts writes/removes `x-account-id` in `localStorage` and optionally `sessionStorage` (when `allowMultipleSessions` is enabled).
+
+14. **Accounts fetching lifecycle**: when `userId` is present, `AccountsProvider` fetches accounts and updates provider state.
+
+## Wrapper & App Mode
+
+15. **Admin app detection**: `checkIsAdminApp()` checks whether the current subdomain equals `"admin"`.
+
+16. **SaasWrapper gating**: `SaasWrapper`:
+ - calls `doesAccountExist` on mount and shows loading / error UI
+ - normalizes config via `prepareConfig`
+ - wraps children with `ConfigProvider`
+ - wraps children with `AccountsProvider` only for non-admin apps
+
+## Routing
+
+17. **Admin routes generator**: `getSaasAdminRoutes(type?, options?)` emits `` elements for admin pages and supports element overrides + disabled flags.
+
+18. **App routes generator**: `getSaasAppRoutes(type?, options?)` emits routes for authenticated/unauthenticated/public modes; join invitation route disables when not in the main app.
+
+## UI Components
+
+19. **AccountSwitcher**: switches accounts using `useAccounts()`; shows loading state when accounts aren’t available.
+
+20. **AccountsTable**: renders a table of accounts; column set changes based on `config.entity`.
+
+21. **MyAccounts**: renders a list of accounts and triggers account switching.
+
+22. **AccountForm**: account create/edit form with zod validation; slug validation depends on `subdomains`.
+
+23. **AccountInvitationForm**: invitation form with conditional schema merging (roles, expiry field, and optional caller-provided schema) and toast notifications.
+
+24. **Signup forms**:
+ - `AccountSignupForm` is a 2-step flow with conditional schema and optional terms & conditions validation
+ - `UserSignupForm` supports optional terms & conditions validation and optional pre-filled email
+
+## Views/Pages
+
+25. **Built-in pages**: exports pages for common SaaS flows (accounts add/edit/view/settings, invitations accept/join/signup, my accounts, signup).
+
diff --git a/packages/react/GUIDE.md b/packages/react/GUIDE.md
new file mode 100644
index 0000000..db9c0e2
--- /dev/null
+++ b/packages/react/GUIDE.md
@@ -0,0 +1,272 @@
+
+
+# @prefabs.tech/saas-react — Developer Guide
+
+## Installation
+
+### For package consumers
+
+```bash
+npm install @prefabs.tech/saas-react
+```
+
+```bash
+pnpm add @prefabs.tech/saas-react
+```
+
+### For monorepo development
+
+```bash
+pnpm install
+pnpm --filter @prefabs.tech/saas-react test
+pnpm --filter @prefabs.tech/saas-react build
+```
+
+## Setup
+
+This is the only “full setup” example. All later snippets assume this wrapper is in place.
+
+```typescript
+import { ToastContainer } from "react-toastify";
+import { SaasWrapper } from "@prefabs.tech/saas-react";
+
+import type { SaasConfig } from "@prefabs.tech/saas-react";
+
+const saas: SaasConfig = {
+ apiBaseUrl: "https://api.example.com",
+ entity: "both",
+ multiDatabase: false,
+ rootDomain: "example.com",
+ subdomains: "required",
+ mainApp: { subdomain: "app" },
+};
+
+export function App() {
+ return (
+
+
+
+
+ );
+}
+```
+
+---
+
+## Base Libraries
+
+### axios — Modified
+
+HTTP calls use a constrained axios client created via an internal `client(apiBaseUrl)`.
+
+-> **Their docs:** [axios](https://www.npmjs.com/package/axios)
+
+**What’s different here:**
+
+- `baseURL` is taken from your `SaasConfig.apiBaseUrl`
+- `x-account-id` is injected from storage
+- POST JSON header defaults are set
+
+**What we add on top:**
+
+- account switching/persistence in `AccountsProvider` that controls the `x-account-id` header
+
+---
+
+## Features
+
+### 1) Provide config (`ConfigProvider` / `useConfig`)
+
+`useConfig()` reads config from context and throws if it is missing. In most apps, prefer `SaasWrapper`, which wraps your tree with `ConfigProvider`.
+
+```typescript
+import { ConfigProvider, useConfig } from "@prefabs.tech/saas-react";
+
+function Child() {
+ const config = useConfig();
+ return null;
+}
+
+function Root({ saas }: { saas: any }) {
+ return (
+
+
+
+ );
+}
+```
+
+### 2) Provide accounts (`AccountsProvider` / `useAccounts`)
+
+`AccountsProvider` stores the user’s accounts list, active account, and switching/persistence behavior for `x-account-id`.
+
+```typescript
+import { AccountsProvider, useAccounts } from "@prefabs.tech/saas-react";
+
+function AccountName() {
+ const { activeAccount } = useAccounts();
+ return {activeAccount?.name};
+}
+
+function Root({ saas, userId }: { saas: any; userId?: string }) {
+ return (
+
+
+
+ );
+}
+```
+
+### 3) Use `SaasWrapper` for app gating
+
+`SaasWrapper` runs the domain registration check, renders loading/error UI when needed, normalizes config defaults, and conditionally installs `AccountsProvider` (non-admin apps only).
+
+```typescript
+import { SaasWrapper } from "@prefabs.tech/saas-react";
+
+void SaasWrapper;
+```
+
+### 4) Generate routes (React Router)
+
+The package exposes helpers that return `` elements for the built-in pages.
+
+```typescript
+import { getSaasAdminRoutes, getSaasAppRoutes } from "@prefabs.tech/saas-react/routes";
+
+const adminRoutes = getSaasAdminRoutes("authenticated");
+const appRoutes = getSaasAppRoutes("authenticated");
+```
+
+You can override elements and disable routes via the `options` argument.
+
+```typescript
+import { getSaasAppRoutes } from "@prefabs.tech/saas-react/routes";
+
+const routes = getSaasAppRoutes("unauthenticated", {
+ routes: {
+ signup: { disabled: true },
+ },
+});
+
+void routes;
+```
+
+### 5) Call the API (thin axios helpers)
+
+```typescript
+import { doesAccountExist, getMyAccounts, signup } from "@prefabs.tech/saas-react/api";
+
+await doesAccountExist({ apiBaseUrl: "https://api.example.com" });
+await getMyAccounts({ apiBaseUrl: "https://api.example.com" });
+await signup({
+ apiBaseUrl: "https://api.example.com",
+ path: "/auth/signup",
+ data: { email: "a@b.com", password: "Password1", confirmPassword: "Password1" } as any,
+ accountSignup: false,
+});
+```
+
+### 6) Use query/mutation hooks
+
+`useQuery` auto-runs by default unless `lazy` or `skip` is set; `useMutation` exposes an imperative trigger.
+
+```typescript
+import { useQuery, useMutation } from "@prefabs.tech/saas-react/api";
+
+function Example() {
+ const { data, loading, error, trigger } = useQuery<{ ok: boolean }>(
+ "my-account",
+ {},
+ { lazy: true },
+ );
+
+ const mutation = useMutation<{ ok: boolean }, { name: string }>({
+ method: "POST",
+ });
+
+ return null;
+}
+```
+
+### 7) Use the exported components/pages
+
+```typescript
+import {
+ AccountSwitcher,
+ AccountsTable,
+ MyAccounts,
+ AccountForm,
+ AccountInvitationForm,
+ AccountUsersTable,
+ AccountSignupForm,
+ UserSignupForm,
+} from "@prefabs.tech/saas-react";
+
+void AccountSwitcher;
+void AccountsTable;
+void MyAccounts;
+void AccountForm;
+void AccountInvitationForm;
+void AccountUsersTable;
+void AccountSignupForm;
+void UserSignupForm;
+```
+
+---
+
+## Use Cases
+
+### Use case 1: Main app vs tenant app behavior
+
+```typescript
+import { useAccounts } from "@prefabs.tech/saas-react";
+
+function TenantAware() {
+ const {
+ meta: { isMainApp, subdomain },
+ } = useAccounts();
+
+ if (!isMainApp) {
+ void subdomain;
+ }
+
+ return null;
+}
+```
+
+### Use case 2: Persist the active account across sessions
+
+```typescript
+import type { SaasConfig } from "@prefabs.tech/saas-react";
+
+const saas: SaasConfig = {
+ apiBaseUrl: "https://api.example.com",
+ entity: "both",
+ multiDatabase: false,
+ rootDomain: "example.com",
+ subdomains: "required",
+ accounts: {
+ allowMultipleSessions: true,
+ autoSelectAccount: true,
+ },
+};
+
+void saas;
+```
+
+### Use case 3: Disable built-in signup routes (SSO-only apps)
+
+```typescript
+import { getSaasAppRoutes } from "@prefabs.tech/saas-react/routes";
+
+const routes = getSaasAppRoutes("unauthenticated", {
+ routes: {
+ signup: { disabled: true },
+ invitationSignup: { disabled: true },
+ },
+});
+
+void routes;
+```
+
diff --git a/packages/react/README.md b/packages/react/README.md
index 10c655d..d6c7674 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -1,311 +1,105 @@
# @prefabs.tech/saas-react
-This package provides essential tools and components to support SaaS functionality in React applications. It simplifies account management, routing, and configuration for multi-tenant SaaS platforms.
+SaaS building blocks for React: account state/context, route helpers, and ready-made components/pages for multi-tenant apps.
-## Installation
+## Why This Package?
-Install with npm:
+Multi-tenant SaaS apps tend to rebuild the same pieces: “active account” state, API header wiring, admin vs app routing, and invitation/signup flows. This package provides a consistent contract (contexts + hooks + routes + components) so apps can share the same tenant behavior with less glue code.
-```bash
-npm install @prefabs.tech/saas-react
-```
+## What You Get
-Install with pnpm:
+### axios — Modified
-```bash
-pnpm add --filter "@scope/project" @prefabs.tech/saas-react
-```
+Wraps [`axios`](https://www.npmjs.com/package/axios) behind a constrained client factory:
-## Usage
+- **Base URL**: set from your SaaS config (`apiBaseUrl`)
+- **Headers**: sets JSON content type for POST and injects `x-account-id` from storage
-### Basic setup
+### Added by This Package
-To use this package, update the App.tsx to wrap your application code with `SaasWrapper`. This wrapper manages account-related states and configurations.
+- **Contexts/providers**
+ - `ConfigProvider` + `useConfig()` for accessing `SaasConfig`
+ - `AccountsProvider` + `useAccounts()` for accounts list + active account + switching
+- **Route helpers** for `react-router-dom`
+ - `getSaasAdminRoutes(...)`
+ - `getSaasAppRoutes(...)`
+- **API utilities & hooks**
+ - `useQuery(...)` and `useMutation(...)` wrappers with default behaviors
+- **UI components & pages** for common SaaS flows
+ - account management (forms/tables)
+ - invitations (invite/join/accept)
+ - signup (account signup vs user signup)
-```typescript
-import { useUser } from "@prefabs.tech/react-user";
-import { SaasWrapper } from "@prefabs.tech/saas-react";
+## Usage Guidelines
+
+- **Always render `SaasWrapper` (or at minimum `ConfigProvider`) above any hooks/components that need config.**
+ - `useConfig()` throws if `ConfigProvider` is missing.
+- **Render `AccountsProvider` (or use `SaasWrapper` in non-admin apps) above anything that calls `useAccounts()`.**
+ - `useAccounts()` throws if `AccountsProvider` is missing.
+- **Be intentional about account persistence behavior.**
+ - Account switching writes/removes `x-account-id` in `localStorage` and optionally `sessionStorage` (when `allowMultipleSessions` is enabled).
+
+## Requirements
+
+- **React**: `react`, `react-dom`
+- **Routing**: `react-router-dom`
+- **UI dependencies consumed by components**:
+ - `@prefabs.tech/react-ui`, `@prefabs.tech/react-form`, `@prefabs.tech/react-i18n`
+ - `react-toastify`
+ - `primereact`
+
+## Quick Start
+
+```ts
import { ToastContainer } from "react-toastify";
+import { SaasWrapper } from "@prefabs.tech/saas-react";
-import config from "./config";
-import { AppRouter } from "./Router";
+import type { SaasConfig } from "@prefabs.tech/saas-react";
-function App() {
- const { user } = useUser();
+const saas: SaasConfig = {
+ apiBaseUrl: "https://api.example.com",
+ entity: "both",
+ multiDatabase: false,
+ rootDomain: "example.com",
+ subdomains: "required",
+ mainApp: { subdomain: "app" },
+};
+export function App() {
return (
-
-
+
+ {/* your routes */}
);
}
-
-export default App;
-```
-
-### Configuration
-
-The `SaasWrapper` accepts a `config` prop to customize its behavior. Below are the available options with detailed explanations:
-
-```typescript
-export type SaasConfig = {
- accounts?: {
- autoSelectAccount?: boolean;
- allowMultipleSessions?: boolean;
- signup?: {
- apiPath?: string;
- appRedirection?: boolean;
- termsAndConditionsUrl?: string;
- };
- };
- apiBaseUrl: string;
- entity: "both" | "individual" | "organization";
- mainAppSubdomain: string;
- rootDomain: string;
- multiDatabase: boolean;
- saasAccountRoles?: string[];
- subdomains: "required" | "optional" | "disabled";
- ui?: {
- account?: {
- form?: {
- actionsAlignment?: "center" | "fill" | "left" | "right";
- actionsReverse?: boolean;
- };
- };
- invitation?: {
- form?: {
- actionsAlignment?: "center" | "fill" | "left" | "right";
- actionsReverse?: boolean;
- };
- };
- signup?: {
- form?: {
- actionsAlignment?: "center" | "fill" | "left" | "right";
- actionsReverse?: boolean;
- };
- };
- };
-};
```
-### Detailed attribute descriptions
-
-- **`accounts`**: Contains account-specific configurations.
- - **`autoSelectAccount`**: When enabled (default), the system automatically selects an account for the user if they have only one account.
- - **`allowMultipleSessions`**: When enabled (default), users can maintain multiple active sessions across different accounts.
- - **`signup`**: Configuration for signup-related operations.
- - **`apiPath`**: The api path for signup
- - **`appRedirection`**: Indicates whether to redirect to the app after signup.
- - **`termsAndConditionsUrl`**: url for the terms and conditions page.
-- **`apiBaseUrl`**: The base url for all api requests.
-- **`entity`**: The type of accounts allowed.
-- **`mainAppSubdomain`**: [Depricated: use `mainApp.subdomain`] Specifies the subdomain for the main application.
-- **`mainApp`**: Configuration for main app's domain and subdomain
- - **`subdomain`**: Specifies the subdomain for the main application
- - **`domain`**: Specifies the full domain for the main applications
-- **`rootDomain`**: The root domain of your SaaS platform.
-- **`multiDatabase`**: Indicates whether the SaaS platform supports multiple databases.
-- **`saasAccountRoles`**: A list of roles available for accounts in the SaaS platform. Used when inviting users to an account.
-- **`subdomains`**: Specifies the subdomain behavior for the SaaS platform. Options include:
- - `"required"`: Subdomains are mandatory for tenant identification.
- - `"optional"`: Subdomains are optional and can be used if needed.
- - `"disabled"`: Subdomains are not used in the SaaS platform.
- **`ui`**: UI related configuration for customization.
-
-### Routing
-
-This package provides pre-configured routes for SaaS applications, designed to simplify navigation in multi-tenant platforms. The two main methods for generating routes are:
-
-#### `getSaasAdminRoutes`
-
-- **Purpose**: Generates routes for the admin section of a SaaS application.
-- **Ideal use case**: Use this method in your **Admin App**.
-- **Parameters**:
- - `type`: Specifies the type of routes. Options:
- - `"authenticated"` (default): Routes for authenticated users.
- - `"unauthenticated"`: Routes for unauthenticated users. No routes are returned at the moment.
- - `"public"`: Routes for public users. No routes are returned at the moment.
- - `options`: An optional object to customize routes. Following route options are available under `options.routes`:
- - `accountsAdd`: Customizes the "Add Account" route.
- - `accountsEdit`: Customizes the "Edit Account" route.
- - `accountsView`: Customizes the "Viefw Account" route.
-
-#### `getSaasAppRoutes`
-
-- **Purpose**: Generates routes for the main application section of a SaaS platform.
-- **Ideal use case**: Use this method in your **Main App**.
-- **Parameters**:
- - `type`: Specifies the type of routes. Options:
- - `"authenticated"` (default): Routes for authenticated users.
- - `"unauthenticated"`: Routes for unauthenticated users.
- - `"public"`: Routes for public access.
- - `options`: An optional object to customize routes. Following route options are available under `options.routes`:
- - `accountSettings`: Customizes the "Account Settings" route.
- - `invitationAccept`: Customizes the "Accept Invitation" route.
- - `invitationJoin`: Customizes the "Join Invitation" route.
- - `invitationSignup`: Customizes the "Signup Invitation" route.
- - `myAccounts`: Customizes the "My Accounts" route.
- - `signup`: Customizes the "Signup" route.
-
-### Example usage
-
-```typescript
-import {
- getSaasAdminRoutes,
- getSaasAppRoutes,
-} from "@prefabs.tech/saas-react/routes";
+## Installation
-// Admin App
-const adminRoutes = getSaasAdminRoutes();
+Install with npm:
-// Main App
-const appRoutes = getSaasAppRoutes();
+```bash
+npm install @prefabs.tech/saas-react
```
-These methods allow you to easily define and manage routes for different sections of your SaaS platform.
-
-### Components and pages
-
-This package provides several reusable components and pages to simplify SaaS application development. Below is a list of all exported components and pages:
-
-#### Components
-
-- **Account components**:
- - `AccountSwitcher`: Allows users to switch between accounts.
- - `AccountForm`: A form for creating or editing account details.
- - `AccountInfo`: Displays detailed information about an account.
- - `AccountInvitationForm`: A form for sending account invitations.
- - `AccountInvitationModal`: A modal for managing account invitations.
- - `AccountInvitationsTable`: Displays a table of account invitations.
- - `AccountSignupForm`: A form for signing up for an account.
- - `AccountUsersTable`: Displays a table of users associated with an account.
- - `AccountsTable`: Displays a table of all accounts.
- - `MyAccounts`: Displays a list of accounts associated with the user.
- - `UserSignupForm`: A form for user signup.
-
-#### Pages
-
-- **AcceptInvitation pages**:
- - `AcceptInvitationPage`: Handles the process of accepting an invitation to join or signup to an account.
- - `JoinInvitationPage`: Manages the process of joining an account via an invitation.
- - `SignupInvitationPage`: Facilitates signing up for an account through an invitation.
-
-- **Account pages**:
- - `AccountAddPage`: Provides a page for adding a new account.
- - `AccountEditPage`: Allows editing of existing account details.
- - `AccountSettingsPage`: Displays and manages account information including user and invitations.
- - `AccountViewPage`: Shows detailed information about a specific account.
-
-- **MyAccounts pages**:
- - `MyAccountsPage`: Displays a list of accounts associated with the user and allows account switching or management.
-
-- **Signup pages**:
- - `SignupPage`: Facilitates user signup and account creation processes. Dynamically renders either an `AccountSignupForm` or `UserSignupForm` based on the app context (main app or user app).
-
-These components and pages can be imported and used directly in your application.
-
-### Customizing tabs in `AccountViewPage` and `AccountSettingsPage`
-
-To cutomize the tabs in `AccountViewPage` in admin app and `AccountSettingsPage` in user app, you can import these page components in your app and pass it as custom element to the route option in `getSaasAdminRoutes` or `getSaasAppRoutes`. Then you can use the page props to customize the tabs in the page along with other attributes.
+Install with pnpm:
-```{typescript}
-{getSaasAdminRoutes("authenticated", {
- routes: {
- // accountSettings in case of user app
- accountsView: {
- element: (
- in case of user app
- showToolbar={true} // to show/hide page toolbar, supported only in AccountViewPage
- tabs={[
- {
- key: "newTab",
- label: "New tab",
- children: Content of new tab
,
- },
- ]}
- activeTab="info"
- visibleTabs={["info", "users", "invitations", "newTab"]}
- />
- ),
- },
- },
-})}
+```bash
+pnpm add @prefabs.tech/saas-react
```
-- To add new tab, add new tab (type: AccountTab) object in `tabs` prop
-- To change the default tab when opening the page, update value in the `activeTab` prop
-- To change the order of tabs, change order of elements in the `visibleTabs` prop
-- To customize existing tab, add an object in tabs with its key and update any attribute. for example `{key: "info", label: "Information"}`
-
-See component to check prop types and other options: [AccountViewPage](https://github.com/prefabs-tech/saas/blob/main/packages/react/src/views/Account/AccountView.tsx) [AccountSettingsPage](https://github.com/prefabs-tech/saas/blob/main/packages/react/src/views/Account/AccountSettings.tsx)
-
-### Customizing form actions aligment
+## Testing
-To customize the actions alignment in forms, you can pass following config options in saas config
+From the monorepo root:
-```{typescript}
-ui?: {
- account?: {
- form?: {
- actionsAlignment?: "center" | "fill" | "left" | "right";
- actionsReverse?: boolean;
- };
- };
- invitation?: {
- form?: {
- actionsAlignment?: "center" | "fill" | "left" | "right";
- actionsReverse?: boolean;
- };
- };
- signup?: {
- form?: {
- actionsAlignment?: "center" | "fill" | "left" | "right";
- actionsReverse?: boolean;
- };
- };
- };
+```bash
+pnpm test --filter @prefabs.tech/saas-react
```
-The current default values are as following
+From this package folder:
-```{typescript}
-export const CONFIG_UI_DEFAULT = {
- account: {
- form: {
- actionsAlignment: "right" as const,
- actionsReverse: false,
- },
- },
- invitation: {
- form: {
- actionsAlignment: "fill" as const,
- actionsReverse: true,
- },
- },
- signup: {
- form: {
- actionsAlignment: "fill" as const,
- actionsReverse: true,
- },
- },
-};
+```bash
+pnpm test
+pnpm test:unit
```
-
-You can play around with `actionsAligment` and `actionsReverse` configs to get desired alignment of form actions.
-
-### i18n support
-
-This package uses `@prefabs.tech/react-i18n` for translations. By default, it uses the following namespaces:
-
-- `account`
-- `accounts`
-
-These namespaces are available in the following locales:
-
-- **English (`en`)**: `locales/en/account.json`, `locales/en/accounts.json`
-- **French (`fr`)**: `locales/fr/account.json`, `locales/fr/accounts.json`
-
-Ensure you register these namespaces in your application's i18n setup.
-
-Refer to the `locales/en` and `locales/fr` folders for the required translation keys.
diff --git a/packages/react/setup-test.ts b/packages/react/setup-test.ts
new file mode 100644
index 0000000..6601d1f
--- /dev/null
+++ b/packages/react/setup-test.ts
@@ -0,0 +1,8 @@
+// Intentionally minimal.
+// This file exists because `vitest.config.ts` references it via `setupFiles`.
+// Add shared test polyfills/mocks here if needed by future tests.
+
+// Silence React 18 act() environment warnings in Vitest/jsdom.
+// See https://react.dev/reference/react/act
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
diff --git a/packages/react/unit/account.test.ts b/packages/react/unit/account.test.ts
new file mode 100644
index 0000000..eb050f9
--- /dev/null
+++ b/packages/react/unit/account.test.ts
@@ -0,0 +1,62 @@
+import { describe, expect, it } from "vitest";
+
+import { prepareSignupData } from "@/utils/account";
+
+import type { AccountSignupData, UserSignupData } from "@/types/account";
+
+describe("prepareSignupData", () => {
+ it("creates user signup payload when accountSignup=false", () => {
+ const payload = prepareSignupData({
+ accountSignup: false,
+ data: { email: "a@b.com", password: "secret" } satisfies UserSignupData,
+ });
+
+ expect(payload).toEqual({
+ formFields: [
+ { id: "email", value: "a@b.com" },
+ { id: "password", value: "secret" },
+ ],
+ });
+ });
+
+ it("nulls company fields when individual=true", () => {
+ const payload = prepareSignupData({
+ data: {
+ email: "a@b.com",
+ password: "secret",
+ name: "Name",
+ individual: true,
+ registeredNumber: "RN",
+ taxId: "TAX",
+ slug: "s",
+ useSeparateDatabase: true,
+ } satisfies AccountSignupData,
+ });
+
+ expect(payload.accountFormFields).toEqual(
+ expect.arrayContaining([
+ { id: "registeredNumber", value: null },
+ { id: "taxId", value: null },
+ ]),
+ );
+ });
+
+ it("forces useSeparateDatabase=false when slug is missing", () => {
+ const payload = prepareSignupData({
+ data: {
+ email: "a@b.com",
+ password: "secret",
+ name: "Name",
+ individual: false,
+ registeredNumber: "RN",
+ taxId: "TAX",
+ slug: "",
+ useSeparateDatabase: true,
+ } satisfies AccountSignupData,
+ });
+
+ expect(payload.accountFormFields).toEqual(
+ expect.arrayContaining([{ id: "useSeparateDatabase", value: false }]),
+ );
+ });
+});
diff --git a/packages/react/unit/common.test.ts b/packages/react/unit/common.test.ts
new file mode 100644
index 0000000..30aadd5
--- /dev/null
+++ b/packages/react/unit/common.test.ts
@@ -0,0 +1,76 @@
+import axios from "axios";
+import { JSDOM } from "jsdom";
+import { describe, expect, it, vi } from "vitest";
+
+import { client } from "@/api/axios/client";
+import { ACCOUNT_HEADER_NAME } from "@/constants";
+import { checkIsAdminApp } from "@/utils/common";
+
+describe("checkIsAdminApp", () => {
+ it("returns true when the subdomain is admin", () => {
+ const dom = new JSDOM("", { url: "https://admin.example.com/" });
+ const windowObject = dom.window as unknown as Window & typeof globalThis;
+ const documentObject = dom.window.document as unknown as Document;
+
+ vi.stubGlobal("window", windowObject);
+ vi.stubGlobal("document", documentObject);
+ expect(checkIsAdminApp()).toBe(true);
+ vi.unstubAllGlobals();
+ });
+
+ it("returns false when the subdomain is not admin", () => {
+ const dom = new JSDOM("", { url: "https://app.example.com/" });
+ const windowObject = dom.window as unknown as Window & typeof globalThis;
+ const documentObject = dom.window.document as unknown as Document;
+
+ vi.stubGlobal("window", windowObject);
+ vi.stubGlobal("document", documentObject);
+ expect(checkIsAdminApp()).toBe(false);
+ vi.unstubAllGlobals();
+ });
+});
+
+describe("client (axios factory)", () => {
+ it("creates axios instance with baseURL and default post JSON content-type", () => {
+ const createSpy = vi.spyOn(axios, "create").mockReturnValue({} as never);
+
+ client("https://api.example.com");
+
+ expect(createSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ baseURL: "https://api.example.com",
+ headers: expect.objectContaining({
+ post: { "Content-Type": "application/json" },
+ }),
+ }),
+ );
+ });
+
+ it("prefers localStorage account id when sessionStorage has any value (current behavior)", () => {
+ const createSpy = vi.spyOn(axios, "create").mockReturnValue({} as never);
+
+ sessionStorage.setItem(ACCOUNT_HEADER_NAME, "session-1");
+ localStorage.setItem(ACCOUNT_HEADER_NAME, "local-1");
+
+ client("https://api.example.com");
+
+ const argument = createSpy.mock.calls[0]?.[0] as unknown as {
+ headers?: Record;
+ };
+ expect(argument.headers?.[ACCOUNT_HEADER_NAME]).toBe("local-1");
+ });
+
+ it("does not read localStorage when sessionStorage is empty (current behavior)", () => {
+ const createSpy = vi.spyOn(axios, "create").mockReturnValue({} as never);
+
+ sessionStorage.removeItem(ACCOUNT_HEADER_NAME);
+ localStorage.setItem(ACCOUNT_HEADER_NAME, "local-1");
+
+ client("https://api.example.com");
+
+ const argument = createSpy.mock.calls[0]?.[0] as unknown as {
+ headers?: Record;
+ };
+ expect(argument.headers?.[ACCOUNT_HEADER_NAME]).toBeUndefined();
+ });
+});
diff --git a/packages/vue/ANALYSIS.md b/packages/vue/ANALYSIS.md
new file mode 100644
index 0000000..2cba6b9
--- /dev/null
+++ b/packages/vue/ANALYSIS.md
@@ -0,0 +1,232 @@
+
+
+## Package
+
+- **Path**: `packages/vue`
+- **Name**: `@prefabs.tech/saas-vue`
+- **Type**: ESM package (`"type": "module"`)
+- **Runtime deps**: (none declared in `dependencies`, but code consumes peer/dev deps such as `vue`, `vue-router`, `pinia`, `axios`, `zod`, `vee-validate`, `@vee-validate/zod`, and Prefabs Vue packages)
+- **Peer deps (consumed by our code)**: `vue`, `vue-router`, `pinia`, `axios`, `zod`, `vee-validate`, `@vee-validate/zod`, `@vueuse/core`, `@prefabs.tech/vue3-config`, `@prefabs.tech/vue3-i18n`, `@prefabs.tech/vue3-layout`, `@prefabs.tech/vue3-ui`, `@prefabs.tech/vue3-user`
+
+## Entry points & public exports
+
+### `src/index.ts`
+
+- **Injection keys / symbols (OURS)**:
+ - `Symbol.for("saas.config")`
+ - `Symbol.for("saas.eventHandlers")`
+ - `Symbol.for("saas.accountTabs")`
+ - `Symbol.for("saas.vue.translations")`
+- **Default translations (OURS)**:
+ - `defaultMessages = { en: enMessages, fr: frMessages }`
+- **Vue plugin (OURS)**: default export `plugin`
+ - `install(app, options)`:
+ - prepares config via `prepareConfig(options.saasConfig)`
+ - provides config + translations + event handlers + account tab config
+- **Composables / exports (OURS)**:
+ - `useTranslations()` (injects `saas.vue.translations` with fallback to defaults)
+ - routes: `export * from "./routes"`
+ - types: `export * from "./types/routes"`
+ - account management exports:
+ - `useMyAccountsStore` (Pinia store)
+ - `useMyAccounts` (composable)
+ - `AccountSwitcher` component
+ - `SaasAccountsProvider`
+ - `SaasWrapper`
+ - `ConfigProvider`
+ - error handling exports:
+ - `useGlobalAccountError`
+ - `NotFoundMessage`
+ - views:
+ - `AccountSettings`
+ - `MyAccounts`
+ - `DEFAULT_PATHS`
+ - utilities:
+ - `checkIsAdminApp`
+
+## Base Library Passthrough Analysis
+
+### `axios` — MODIFIED
+
+- **Options type**: base library used directly; we don’t expose `AxiosRequestConfig`.
+- **Options passed**: **transformed**
+ - `client(baseURL)` creates an axios instance with:
+ - `baseURL`
+ - POST JSON header
+ - `x-account-id` header from `sessionStorage` (or empty string)
+- **Features restricted**: callers use our fixed instance config (no per-call customization besides request args).
+- **Features added**:
+ - account header wiring via `x-account-id`
+
+### `vue-router` — PARTIAL PASSTHROUGH
+
+- **Options type**: `RouteRecordRaw`, `Router` from `vue-router`
+- **Options passed**: **modified/merged**
+ - We define default route records and merge overrides into them (including `meta` deep-merge).
+- **Features restricted**: `getSaas*Routes` returns only the routes for the selected “type”; override shape is limited to `RouteOverwrite`.
+- **Features added**:
+ - `addSaasAdminRoutes(router, ...)` / `addSaasAppRoutes(router, ...)` convenience helpers
+
+### `zod` / `vee-validate` / `@vee-validate/zod` — NO WRAPPED DEPENDENCY (used directly)
+
+Used directly in validation helper files and form components; we don’t wrap these libraries as a separate abstraction layer.
+
+### Prefabs Vue packages (`@prefabs.tech/vue3-*`) — NO WRAPPED DEPENDENCY (used directly)
+
+Consumed directly in components/views (UI/form/i18n/config/user). The “package” surface we expose is our own components/composables/routes/plugin.
+
+## “Ours” vs “Theirs” classification
+
+### Plugin & injection (`src/index.ts`)
+
+- **OURS**
+ - `plugin.install`:
+ - **conditional**: `options.translations ? prependMessages(defaultMessages, options.translations) : defaultMessages`
+ - **provides**:
+ - config (prepared)
+ - translations
+ - event handlers (`notification`)
+ - account tabs configuration
+ - `useTranslations()` injects translations with default fallback
+
+- **THEIRS**
+ - `prependMessages(...)` is a direct helper from `@prefabs.tech/vue3-i18n` (used as intended).
+ - `app.provide`, `inject` are Vue framework primitives.
+
+### Config normalization (`src/utils/config.ts`)
+
+- **OURS**
+ - `prepareUiConfig(ui = {})`: merges `CONFIG_UI_DEFAULT` with provided UI config
+ - `prepareConfig(config)`: returns config with `ui` normalized via `prepareUiConfig`
+ - **branching**: none beyond defaulting `ui` parameter
+
+### Constants & defaults (`src/constant.ts`)
+
+- **OURS**
+ - `ACCOUNT_HEADER_NAME = "x-account-id"`
+ - `ADMIN_SUBDOMAIN_DEFAULT = "admin"`
+ - `SIGNUP_PATH_DEFAULT = "/auth/signup"`
+ - `SAAS_ACCOUNT_ROLES_DEFAULT` (owner/member)
+ - `DEFAULT_PATHS` routing defaults
+ - `REDIRECT_AFTER_LOGIN_KEY = "saas.redirectAfterLogin"`
+ - `CONFIG_UI_DEFAULT` for form action alignment/reversal (**note**: `"filled"` in Vue vs `"fill"` in React)
+
+### Utilities (`src/utils/common.ts`, `src/utils/account.ts`)
+
+- **OURS**
+ - `checkIsAdminApp()` subdomain comparison to `"admin"`
+ - `prepareSignupData({ data, accountSignup=true })`
+ - **branch**: account vs user signup payload shape
+ - **defaults**: `accountSignup=true`, `useSeparateDatabase=false` if no slug
+
+### API wrappers (`src/api/*`)
+
+- **OURS**
+ - `client(baseURL)` wraps `axios.create` with default headers and account-id injection
+ - Accounts API (`src/api/accounts.ts`):
+ - CRUD + “my account(s)” calls
+ - `signup({ accountSignup=true, ... })` uses `prepareSignupData`
+ - Invitation API (`src/api/AccountInvitations.ts`):
+ - **branch**: token endpoints vary based on optional `accountId` (builds URL differently)
+ - `withCredentials: false` for token-based flows
+ - Users API (`src/api/AccountUsers.ts`): enable/disable and list users
+
+- **THEIRS**
+ - `axios` request execution is direct base library usage inside our thin wrappers.
+
+### Stores & composables
+
+- **OURS**
+ - `useMyAccountsStore` (`src/stores/MyAccounts.ts`):
+ - **defaults**: `autoSelectAccount=true`, `allowMultipleSessions=true`
+ - **branching**:
+ - compute `meta.isMainApp` from subdomain vs `mainAppSubdomain`
+ - non-main app requires slug match; throws if not found
+ - main app selects default account based on auto-select / saved account id
+ - saved account id prefers session when enabled
+ - **side effects**: storage writes/removals for `x-account-id`
+ - **wiring**: calls API helpers `getMyAccounts`, `getMyAccount`, `updateMyAccount`
+ - `useMyAccounts(config?)` (`src/composables/UseMyAccounts.ts`):
+ - **branching**:
+ - config resolution order: argument → injected `saas.config` → existing store
+ - throws when config missing and store not initialized
+ - initializes store once config is available
+ - `useGlobalAccountError` (`src/composables/UseGlobalAccountError.ts`):
+ - **branch**: sets global flag only for 404 + `"Account not found"` message
+ - provides `clearError()`
+ - `useConfig()` (`src/composables/UseConfig.ts`):
+ - injects `"config"` with default object
+ - **note**: this is separate from `saas.config` symbol injection used elsewhere
+
+### Routes (`src/routes/*`)
+
+- **OURS**
+ - `getSaasAdminRoutes(type="authenticated", options?)`
+ - includes 4 default admin routes; for unauthenticated/public returns []
+ - supports route override merging via `getRoute(...)`
+ - filters `disabled` routes out
+ - `addSaasAdminRoutes(router, ...)` adds computed routes to router
+ - `getSaasAppRoutes(type="authenticated", options?)`
+ - **branches**: authenticated vs unauthenticated vs public route sets
+ - supports override merging + disabled filtering
+ - `addSaasAppRoutes(router, ...)`
+
+### Components / views (selected key behavior)
+
+- **OURS**
+ - `SaasWrapper.vue`:
+ - **branches**:
+ - shows loading, not-found (global account error), 404 page, generic error page
+ - wraps slot with `ConfigProvider` always; wraps with `SaasAccountsProvider` only when not admin app
+ - **lifecycle**: `onMounted` calls `doesAccountExist`, stores `error`, toggles loading
+ - **requires injection**: `saas.config` must exist (throws otherwise)
+ - `SaasAccountsProvider.vue`:
+ - **branch**: renders slot immediately if no user; otherwise waits for accounts load
+ - **lifecycle**: `watch(userId)` fetches accounts; on sign-out clears store state
+ - integrates `useGlobalAccountError().checkForAccountError`
+ - `ConfigProvider.vue` provides `saas.config`
+ - `AccountSwitcher.vue`:
+ - **defaults**: `emptyLabel=""`, `noHelperText=false`
+ - **branch**: no-op when selecting current account
+ - **side effect**: `window.location.reload()` after switching accounts
+ - `AccountSettings.vue`:
+ - **tabs**: merges default tabs with injected `saas.accountTabs` (function or array)
+ - **branch**: fetches my account only when activeAccount missing; uses global account error detection
+ - Signup view (`views/Signup/Index.vue`):
+ - **branch**: uses account signup form in main app, otherwise user signup form
+ - **defaults**: `apiPath = SIGNUP_PATH_DEFAULT`, `appRedirection=true`
+ - **branch**: redirects to `{slug}.{rootDomain}` after successful signup when enabled and slug present
+ - uses injected `saas.eventHandlers.notification` for error messaging
+
+## Framework constructs / lifecycle / side effects
+
+- **Vue plugin**: `install(app, options)` with multiple `app.provide` calls
+- **Injection**:
+ - config: `Symbol.for("saas.config")`
+ - translations: `Symbol.for("saas.vue.translations")`
+ - event handlers: `Symbol.for("saas.eventHandlers")`
+ - account tabs: `Symbol.for("saas.accountTabs")`
+- **Pinia store**: `defineStore("myAccounts", () => ...)`
+- **Routing**: returns `RouteRecordRaw[]` and provides router add helpers
+- **Side effects**:
+ - local/session storage for `x-account-id`
+ - `window.location.reload()` on account switcher select
+ - `window.location.replace(...)` for post-signup redirection
+
+## Conditional branches & defaults (high-signal)
+
+- **Translations**: merge additional messages only when `options.translations` exists
+- **Admin vs app**: `checkIsAdminApp()` compares subdomain to `"admin"`
+- **Route sets**: depend on `type` in `getSaasAdminRoutes` / `getSaasAppRoutes`
+- **Account selection**: slug-based when not main app; saved-account precedence; auto-select fallback
+- **Account error global flag**: only for \(404 + "Account not found"\)
+- **Signup redirect**: only when `appRedirection && isMainApp && data.slug`
+
+## Completeness checklist
+
+- [x] Classified every public export category as "ours" or "theirs"
+- [x] Listed framework constructs added (plugin, provide/inject, Pinia store, router helpers)
+- [x] Identified conditional branches (routing type, translation merge, admin/app, account selection, signup redirect, error flag)
+- [x] Documented default values we define (constants, config UI defaults, store defaults, route defaults)
+- [x] Produced passthrough classification for wrapped dependencies (`axios`, `vue-router`)
+
diff --git a/packages/vue/FEATURES.md b/packages/vue/FEATURES.md
new file mode 100644
index 0000000..00f4026
--- /dev/null
+++ b/packages/vue/FEATURES.md
@@ -0,0 +1,149 @@
+
+
+# @prefabs.tech/saas-vue — Features
+
+## Plugin & Injection
+
+1. **Vue plugin install hook**: `app.use(SaasVue, options)` prepares `options.saasConfig` and provides it via `Symbol.for("saas.config")`.
+
+```typescript
+import SaasVue from "@prefabs.tech/saas-vue";
+
+app.use(SaasVue, {
+ pinia,
+ router,
+ config,
+ saasConfig: {
+ apiBaseUrl: "https://api.example.com",
+ entity: "both",
+ mainAppSubdomain: "app",
+ multiDatabase: false,
+ rootDomain: "example.com",
+ subdomains: "required",
+ },
+});
+```
+
+2. **Translations provider**: plugin provides translations at `Symbol.for("saas.vue.translations")` and exports `useTranslations()` (with default fallback).
+
+```typescript
+import { useTranslations } from "@prefabs.tech/saas-vue";
+
+const messages = useTranslations();
+```
+
+3. **Event handlers provider**: plugin provides event handlers at `Symbol.for("saas.eventHandlers")` (supports `notification`).
+
+```typescript
+app.use(SaasVue, {
+ // ...
+ notification: (message) => console.log(message.type, message.message),
+});
+```
+
+4. **Account tab customization provider**: plugin provides optional `accountTabs` at `Symbol.for("saas.accountTabs")` (consumed by `AccountSettings`).
+
+```typescript
+app.use(SaasVue, {
+ // ...
+ accountTabs: (defaultTabs) => [...defaultTabs],
+});
+```
+
+## Routing
+
+5. **Admin route records**: `getSaasAdminRoutes(type?, options?)` returns `RouteRecordRaw[]` with override merging and `disabled` filtering.
+
+```typescript
+import { getSaasAdminRoutes } from "@prefabs.tech/saas-vue";
+
+const routes = getSaasAdminRoutes("authenticated", {
+ routes: { accountsView: { disabled: true } },
+});
+```
+
+6. **Admin route registration helper**: `addSaasAdminRoutes(router, type?, options?)` adds routes to a `vue-router` instance.
+
+```typescript
+import { addSaasAdminRoutes } from "@prefabs.tech/saas-vue";
+
+addSaasAdminRoutes(router);
+```
+
+7. **App route records**: `getSaasAppRoutes(type?, options?)` returns app routes for authenticated/unauthenticated/public modes with override merging and `disabled` filtering.
+
+```typescript
+import { getSaasAppRoutes } from "@prefabs.tech/saas-vue";
+
+const routes = getSaasAppRoutes("public");
+```
+
+8. **App route registration helper**: `addSaasAppRoutes(router, type?, options?)` adds app routes to a router.
+
+```typescript
+import { addSaasAppRoutes } from "@prefabs.tech/saas-vue";
+
+addSaasAppRoutes(router, "authenticated");
+```
+
+9. **Default paths**: exports `DEFAULT_PATHS` for built-in route paths.
+
+```typescript
+import { DEFAULT_PATHS } from "@prefabs.tech/saas-vue";
+
+DEFAULT_PATHS.ACCOUNT_SETTINGS;
+```
+
+## Account State (Pinia) & Composables
+
+10. **Accounts store**: exports `useMyAccountsStore()` with state + actions to fetch/update/switch accounts.
+
+```typescript
+import { useMyAccountsStore } from "@prefabs.tech/saas-vue";
+
+const store = useMyAccountsStore();
+store.switchAccount(null);
+```
+
+11. **Config resolution helper**: exports `useMyAccounts(config?)` which resolves config from parameter → injection (`saas.config`) → existing store initialization.
+
+```typescript
+import { useMyAccounts } from "@prefabs.tech/saas-vue";
+
+const store = useMyAccounts();
+```
+
+12. **Account persistence**: switching accounts writes `x-account-id` to `localStorage` and optionally `sessionStorage` (based on `accounts.allowMultipleSessions`).
+
+13. **Main app vs tenant app detection**: store exposes `meta.isMainApp` based on current subdomain vs `mainAppSubdomain`.
+
+## Components & Views
+
+14. **Wrapper component**: `SaasWrapper` performs a domain-registration check (`doesAccountExist`) and renders loading/not-found/error UI before providing config and account context.
+
+15. **Accounts provider component**: `SaasAccountsProvider` initializes the store from config and fetches accounts when `userId` is present; clears account state on sign-out.
+
+16. **Config provider component**: `ConfigProvider` provides SaaS config via `Symbol.for("saas.config")`.
+
+17. **Account switcher component**: `AccountSwitcher` switches accounts; selecting a different account triggers `window.location.reload()`.
+
+18. **Not-found component**: exports `NotFoundMessage` for the “account not found” global error case.
+
+19. **Views**: exports `AccountSettings` and `MyAccounts`.
+
+## Error Handling
+
+20. **Global account error latch**: exports `useGlobalAccountError()` which flips a global flag for \(404 + `"Account not found"`\) and exposes `clearError()`.
+
+## Utilities & Config Defaults
+
+21. **Admin app detection utility**: exports `checkIsAdminApp()` (subdomain equals `"admin"`).
+
+22. **UI defaults merging**: SaaS config UI is normalized by merging against `CONFIG_UI_DEFAULT`.
+
+## Signup Flow
+
+23. **Signup payload shaping**: signup uses `prepareSignupData({ accountSignup })` to send user-only or account+user field payloads.
+
+24. **Post-signup redirect (main app)**: signup view can redirect to `{slug}.{rootDomain}` when enabled and a `slug` exists.
+
diff --git a/packages/vue/GUIDE.md b/packages/vue/GUIDE.md
new file mode 100644
index 0000000..337725f
--- /dev/null
+++ b/packages/vue/GUIDE.md
@@ -0,0 +1,272 @@
+
+
+# @prefabs.tech/saas-vue — Developer Guide
+
+## Installation
+
+### For package consumers
+
+```bash
+npm install @prefabs.tech/saas-vue
+```
+
+```bash
+pnpm add @prefabs.tech/saas-vue
+```
+
+### For monorepo development
+
+```bash
+pnpm install
+pnpm --filter @prefabs.tech/saas-vue typecheck
+pnpm --filter @prefabs.tech/saas-vue build
+```
+
+## Setup
+
+This is the only “full setup” example. All later snippets assume the plugin is installed and you have a router + pinia.
+
+```typescript
+import { createApp } from "vue";
+import { createPinia } from "pinia";
+import { createRouter, createWebHistory } from "vue-router";
+
+import SaasVue, { addSaasAppRoutes, addSaasAdminRoutes } from "@prefabs.tech/saas-vue";
+
+const app = createApp(App);
+const pinia = createPinia();
+const router = createRouter({ history: createWebHistory(), routes: [] });
+
+app.use(pinia);
+app.use(router);
+
+app.use(SaasVue, {
+ pinia,
+ router,
+ config: {} as any, // AppConfig from @prefabs.tech/vue3-config
+ saasConfig: {
+ apiBaseUrl: "https://api.example.com",
+ entity: "both",
+ mainAppSubdomain: "app",
+ multiDatabase: false,
+ rootDomain: "example.com",
+ subdomains: "required",
+ },
+});
+
+addSaasAppRoutes(router, "authenticated");
+addSaasAdminRoutes(router, "authenticated");
+
+app.mount("#app");
+```
+
+---
+
+## Base Libraries
+
+### axios — Modified
+
+HTTP calls are made via an internal `client(baseURL)` that creates an axios instance with opinionated defaults.
+
+-> **Their docs:** [axios](https://www.npmjs.com/package/axios)
+
+**What’s different here:**
+
+- **`baseURL`** comes from your SaaS config (`apiBaseUrl`)
+- **`x-account-id`** is injected from `sessionStorage` (or `""`)
+- **POST JSON header** is set by default
+
+**What we add on top:**
+
+- account switching/persistence that drives the `x-account-id` header
+
+### vue-router — Partial passthrough
+
+Routes are expressed as normal `RouteRecordRaw`s, but the package provides prebuilt route sets and helpers.
+
+-> **Their docs:** [vue-router](https://router.vuejs.org/)
+
+**What we change/add:**
+
+- prebuilt SaaS route sets (admin/app; authenticated/unauthenticated/public)
+- override merging (including `meta` merge)
+- `disabled` filtering
+- helpers to add routes to a router
+
+---
+
+## Features
+
+### 1) Install the SaaS plugin
+
+The default export is a Vue plugin. Installing it prepares your SaaS config (merging UI defaults) and `provide`s it and other values via `Symbol.for(...)` keys.
+
+```typescript
+import SaasVue from "@prefabs.tech/saas-vue";
+
+app.use(SaasVue, {
+ pinia,
+ router,
+ config: {} as any,
+ saasConfig: {
+ apiBaseUrl: "https://api.example.com",
+ entity: "both",
+ mainAppSubdomain: "app",
+ multiDatabase: false,
+ rootDomain: "example.com",
+ subdomains: "required",
+ },
+});
+```
+
+### 2) Add routes (admin + app)
+
+You can either **get** route records or **add** them directly to a router instance.
+
+```typescript
+import { addSaasAdminRoutes, addSaasAppRoutes } from "@prefabs.tech/saas-vue";
+
+addSaasAdminRoutes(router, "authenticated");
+addSaasAppRoutes(router, "authenticated");
+```
+
+To customize routes, pass overrides under `options.routes` and/or set `disabled: true`.
+
+```typescript
+import { getSaasAppRoutes } from "@prefabs.tech/saas-vue";
+
+const appRoutes = getSaasAppRoutes("unauthenticated", {
+ routes: {
+ signup: { disabled: true },
+ invitationSignup: { meta: { marketingCampaign: "spring" } },
+ },
+});
+```
+
+### 3) Use account state (Pinia store)
+
+Account state lives in a Pinia store exported as `useMyAccountsStore()`.
+
+```typescript
+import { useMyAccountsStore } from "@prefabs.tech/saas-vue";
+
+const store = useMyAccountsStore();
+store.switchAccount(null);
+```
+
+### 4) Resolve config + initialize the store (`useMyAccounts`)
+
+`useMyAccounts(config?)` resolves config from parameter → injection → store initialization.
+
+```typescript
+import { useMyAccounts } from "@prefabs.tech/saas-vue";
+
+const store = useMyAccounts();
+```
+
+### 5) Handle the “account not found” case globally
+
+`useGlobalAccountError()` flips a global reactive flag only for the specific error condition \(404 + `"Account not found"`\).
+
+```typescript
+import { useGlobalAccountError } from "@prefabs.tech/saas-vue";
+
+const { showAccountError, clearError } = useGlobalAccountError();
+
+if (showAccountError.value) {
+ clearError();
+}
+```
+
+### 6) Use the wrapper/provider components
+
+```typescript
+import { SaasWrapper } from "@prefabs.tech/saas-vue";
+
+void SaasWrapper;
+```
+
+### 7) Customize tabs in `AccountSettings`
+
+`AccountSettings` merges default tabs with `accountTabs` provided during plugin install (function or array).
+
+```typescript
+app.use(SaasVue, {
+ // ...
+ accountTabs: (defaultTabs) => [
+ ...defaultTabs,
+ {
+ key: "billing",
+ label: "Billing",
+ component: {} as any,
+ },
+ ],
+});
+```
+
+### 8) Customize translations
+
+If you pass `translations` to the plugin, they are prepended to the built-in `en`/`fr` messages.
+
+```typescript
+app.use(SaasVue, {
+ // ...
+ translations: {
+ en: { accounts: { error: { title: "Custom error title" } } } as any,
+ },
+});
+```
+
+### 9) Admin/app detection utility
+
+`checkIsAdminApp()` returns true when the current subdomain is `"admin"`.
+
+```typescript
+import { checkIsAdminApp } from "@prefabs.tech/saas-vue";
+
+const isAdmin = checkIsAdminApp();
+void isAdmin;
+```
+
+---
+
+## Use Cases
+
+### Use case 1: “Main app” vs tenant app behavior
+
+```typescript
+import { useMyAccounts } from "@prefabs.tech/saas-vue";
+
+const store = useMyAccounts();
+
+if (store.meta.isMainApp) {
+ // main app behavior
+} else {
+ // tenant app behavior
+}
+```
+
+### Use case 2: Disable built-in signup routes for SSO-only apps
+
+```typescript
+import { addSaasAppRoutes } from "@prefabs.tech/saas-vue";
+
+addSaasAppRoutes(router, "unauthenticated", {
+ routes: {
+ signup: { disabled: true },
+ invitationSignup: { disabled: true },
+ },
+});
+```
+
+### Use case 3: Show toast notifications on signup failure
+
+```typescript
+app.use(SaasVue, {
+ // ...
+ notification: ({ type, message }) => {
+ console.log(`[${type}] ${message}`);
+ },
+});
+```
+
diff --git a/packages/vue/README.md b/packages/vue/README.md
index e5a4587..9cfc174 100644
--- a/packages/vue/README.md
+++ b/packages/vue/README.md
@@ -1,6 +1,104 @@
# @prefabs.tech/saas-vue
-This package provides essential tools and components to support SaaS functionality in Vue applications. It simplifies account management, routing, and configuration for multi-tenant SaaS platforms.
+SaaS plugin for Vue that provides account switching/state, SaaS route helpers, and ready-made views/components for multi-tenant apps.
+
+## Why This Package?
+
+Multi-tenant SaaS apps tend to re-implement the same plumbing: “active account” state, per-account API headers, invitation flows, and separate admin vs app routing. This package bundles those pieces into a single Vue plugin + composables/components so you can standardize behavior across apps.
+
+## What You Get
+
+### axios — Modified
+
+Wraps [`axios`](https://www.npmjs.com/package/axios) with a constrained client factory:
+
+- **Base URL**: taken from your SaaS config (`apiBaseUrl`)
+- **Headers**: always sets JSON content type for POST and injects `x-account-id` from `sessionStorage`
+
+### vue-router — Partial passthrough
+
+Uses [`vue-router`](https://router.vuejs.org/) route records, but provides opinionated route helpers:
+
+- **Default routes**: prebuilt admin/app route sets (`getSaasAdminRoutes`, `getSaasAppRoutes`)
+- **Overrides**: you can override `component`, `path`, `meta`, and/or mark routes as `disabled`
+- **Convenience**: `addSaasAdminRoutes(router, ...)` and `addSaasAppRoutes(router, ...)`
+
+### Added by This Package
+
+- **Vue plugin** that `provide`s:
+ - SaaS config (`Symbol.for("saas.config")`)
+ - translations (`Symbol.for("saas.vue.translations")`)
+ - event handlers (`Symbol.for("saas.eventHandlers")`)
+ - account tab customization (`Symbol.for("saas.accountTabs")`)
+- **Account state** via Pinia:
+ - `useMyAccountsStore()` store + `useMyAccounts()` composable
+ - active account selection (subdomain-based vs main app) + persistence via `x-account-id`
+- **UI building blocks**:
+ - `SaasWrapper`, `SaasAccountsProvider`, `ConfigProvider`, `AccountSwitcher`, `NotFoundMessage`
+- **Views**:
+ - `AccountSettings`, `MyAccounts`
+- **Signup flow**:
+ - account vs user signup pages and optional redirect to `{slug}.{rootDomain}` after signup
+
+## Usage Guidelines
+
+- **You must install the plugin (or provide `saas.config`) before using most components/composables**.
+ - `SaasWrapper` throws if `Symbol.for("saas.config")` is missing.
+ - `useMyAccounts()` will throw if it cannot resolve config from a parameter, injection, or the store.
+- **Account switching reloads the page by default**.
+ - `AccountSwitcher` calls `window.location.reload()` after switching accounts.
+- **Be aware of the “account not found” global error latch**.
+ - `useGlobalAccountError()` sets a global flag only for a 404 response with message `"Account not found"`, which `SaasWrapper` uses to show the not-found UI.
+
+## Requirements
+
+At minimum, you’ll need:
+
+- **Vue**: `vue` (3.x)
+- **Routing**: `vue-router` (4.x)
+- **State**: `pinia`
+- **HTTP**: `axios`
+- **Prefabs peer packages**:
+ - `@prefabs.tech/vue3-config`
+ - `@prefabs.tech/vue3-i18n`
+ - `@prefabs.tech/vue3-layout`
+ - `@prefabs.tech/vue3-ui`
+ - `@prefabs.tech/vue3-user`
+
+## Quick Start
+
+```ts
+import { createApp } from "vue";
+import { createPinia } from "pinia";
+import { createRouter, createWebHistory } from "vue-router";
+
+import SaasVue from "@prefabs.tech/saas-vue";
+import { addSaasAppRoutes } from "@prefabs.tech/saas-vue";
+
+const app = createApp(App);
+const pinia = createPinia();
+const router = createRouter({ history: createWebHistory(), routes: [] });
+
+app.use(pinia);
+app.use(router);
+
+app.use(SaasVue, {
+ pinia,
+ router,
+ config: {} as any, // your @prefabs.tech/vue3-config AppConfig
+ saasConfig: {
+ apiBaseUrl: "https://api.example.com",
+ entity: "both",
+ mainAppSubdomain: "app",
+ multiDatabase: false,
+ rootDomain: "example.com",
+ subdomains: "required",
+ },
+});
+
+addSaasAppRoutes(router);
+app.mount("#app");
+```
## Installation
@@ -15,3 +113,23 @@ Install with pnpm:
```bash
pnpm add --filter "@scope/project" @prefabs.tech/saas-vue
```
+
+## Testing
+
+From the monorepo root (runs all packages with tests):
+
+```bash
+pnpm test
+```
+
+To run only this package’s tests from the monorepo root:
+
+```bash
+pnpm --filter @prefabs.tech/saas-vue vitest run --coverage
+```
+
+From this package folder:
+
+```bash
+pnpm vitest run --coverage
+```
diff --git a/packages/vue/src/__test__/utilities.test.ts b/packages/vue/src/__test__/utilities.test.ts
new file mode 100644
index 0000000..f4e6223
--- /dev/null
+++ b/packages/vue/src/__test__/utilities.test.ts
@@ -0,0 +1,53 @@
+import { describe, expect, it, vi } from "vitest";
+
+import { prepareSignupData } from "@/utils/account";
+import { checkIsAdminApp } from "@/utils/common";
+
+import type { AccountSignupData, UserSignupData } from "../types/user";
+
+describe("prepareSignupData", () => {
+ it("creates user signup payload when accountSignup=false", () => {
+ const payload = prepareSignupData({
+ accountSignup: false,
+ data: { email: "a@b.com", password: "secret" } satisfies UserSignupData,
+ });
+
+ expect(payload).toEqual({
+ formFields: [
+ { id: "email", value: "a@b.com" },
+ { id: "password", value: "secret" },
+ ],
+ });
+ });
+
+ it("forces useSeparateDatabase=false when slug is missing", () => {
+ const payload = prepareSignupData({
+ data: {
+ email: "a@b.com",
+ password: "secret",
+ name: "Name",
+ individual: false,
+ registeredNumber: "RN",
+ taxId: "TAX",
+ slug: "",
+ useSeparateDatabase: true,
+ } satisfies AccountSignupData,
+ });
+
+ expect(payload.accountFormFields).toEqual(
+ expect.arrayContaining([{ id: "useSeparateDatabase", value: false }]),
+ );
+ });
+});
+
+describe("checkIsAdminApp", () => {
+ it("returns true when the subdomain is admin", () => {
+ const windowObject = {
+ location: { hostname: "admin.example.com" },
+ } as unknown as Window & typeof globalThis;
+
+ vi.stubGlobal("window", windowObject);
+ expect(checkIsAdminApp()).toBe(true);
+ vi.unstubAllGlobals();
+ });
+});