# Multi-Tenancy & Branding

## Table of Contents

* [Overview](#overview)
* [Supported Tenants and Brands](#supported-tenants-and-brands)
* [What Branding Controls](#what-branding-controls)
  * [Visual Identity](#visual-identity)
  * [Page Layout and Navigation](#page-layout-and-navigation)
  * [Dashboard and Home Page](#dashboard-and-home-page)
  * [Login Page](#login-page)
  * [Footer](#footer)
  * [Maintenance Page](#maintenance-page)
* [Brand Folder Structure](#brand-folder-structure)
* [Applying a Brand for Development](#applying-a-brand-for-development)
  * [Using the Brand Script (macOS and Linux)](#using-the-brand-script-macos-and-linux)
  * [Using the Branded App Builder (All Platforms)](#using-the-branded-app-builder-all-platforms)
* [Saving Brand Changes Back to Source](#saving-brand-changes-back-to-source)
* [How Branding Affects the User Experience](#how-branding-affects-the-user-experience)
* [Common Scenarios](#common-scenarios)
* [Troubleshooting](#troubleshooting)

## Overview

OtisEd.Nimble is a shared portal platform used by multiple educational agencies. Each agency operates as its own **tenant** — a completely separate environment with its own users, data, and visual identity, all running on the same underlying system.

The branding system allows each tenant to present the portal as their own product. When a user from the Kentucky Department of Education logs in, they see the Kentucky color scheme, logos, navigation layout, and custom home page. A user from the Virgin Islands sees entirely different visuals and content. The underlying features and data work the same way — only the presentation changes.

This guide explains how the branding system is organized and how to work with it.

## Supported Tenants and Brands

The portal currently supports five brand configurations:

| Brand Name         | Description                                                                                |
| ------------------ | ------------------------------------------------------------------------------------------ |
| **Kentucky**       | Kentucky Department of Education (KDE) — authenticated portal with a left navigation panel |
| **KentuckyPublic** | Public-facing Kentucky portal for unauthenticated users                                    |
| **NorthDakota**    | North Dakota Department of Public Instruction (NDDPI)                                      |
| **USVI**           | U.S. Virgin Islands Department of Education (VIDE)                                         |
| **OtisEd**         | Default OtisEd-branded build, used for development and internal testing                    |

Each brand name corresponds to a folder inside the `Branding/` directory of the web project.

## What Branding Controls

Branding is not a simple theme or color picker. Each brand can fully replace or extend any part of the portal's visual presentation. Here is what each brand controls:

### Visual Identity

Each brand provides its own set of images and stylesheets:

* **Header logo** — the organization's logo displayed in the top navigation bar
* **Login page header** — a banner or logo shown on the sign-in screen
* **Home page imagery** — photographs or illustrations featured on the dashboard
* **Favicon** — the small icon shown in the browser tab
* **Color scheme** — overrides to fonts, link colors, button styles, and layout colors via CSS files

The USVI brand, for example, includes themed home-page imagery organized into education categories. The Kentucky brand includes a custom home page photo.

### Page Layout and Navigation

Each brand can provide a completely different page layout. The Kentucky brand adds a **left navigation panel** that appears alongside the main content area — a component not present in other brands. It also includes a skip-navigation control for accessibility compliance.

All brands share the same top navigation menu and session management, but the surrounding structure of the page can vary.

### Dashboard and Home Page

The home page (dashboard) is driven by a widget system. Each tenant's administrators configure which widgets appear in the `HOMEPAGE` widget group. The dashboard renders three widget zones:

* **CONTENT** — the main area at the top of the page
* **FOOTER1** — a content section in the lower-left column
* **FOOTER2** — a content section in the lower-right column

Below those widget zones, users see any active system alerts and announcements. Because all content comes from the widget system, each tenant controls their home page entirely through the portal's administration interface — no code changes required.

The Kentucky brand also loads a custom homepage stylesheet that fine-tunes the visual spacing and styling of the dashboard layout.

### Login Page

The sign-in screen can be fully overridden per brand. Kentucky and OtisEd both provide a custom login page that controls the layout, branding CSS references, and appearance of authentication-related messages. This is a server-rendered page shown before the user has logged in, so it can display organization-specific headers, background images, and help text.

### Footer

Each brand provides its own site footer component. The default footer shows a copyright notice with the Otis Educational Systems name. Individual brands can customize this text, add links, or change the layout of the footer bar entirely.

### Maintenance Page

When the portal is taken offline for maintenance, a branded maintenance page is shown in place of the application. The message displayed is configurable through the portal's Site Settings administration area. If no custom message has been configured, the default text references the STARS Reporting and Analytics Portal by name — brands can override this with their own product name and messaging.

## Brand Folder Structure

Each brand lives in its own folder under `src/OtisEd.Nimble.Web/Branding/`. The folder structure inside each brand is consistent:

```
Branding/
  Kentucky/
    ReactApp/           # React app overrides (replaces matching base app files)
      public/           # Browser tab icon, manifest, index HTML
      src/
        css/branding/   # Brand-specific CSS: branding.css, reporting.css, styles.css
        site/branding/  # Layout, Dashboard, Footer, and other page components
        public/         # Public (unauthenticated) page layouts
    css/                # CSS files copied to wwwroot/css/branding/
    images/             # Image files copied to wwwroot/images/
    Pages/
      Account/          # Server-rendered login page overrides
```

The `ReactApp/` subfolder uses a **file override** approach: any file placed here replaces the corresponding file in the base React application. Files that are not overridden fall through to the shared base implementation. This means a brand only needs to include the files it customizes — everything else is inherited automatically.

The `css/` and `images/` folders contain static assets that are placed into the web server's public file area, making them accessible to both the server-rendered pages and the React application.

## Applying a Brand for Development

There are two ways to apply a brand when running the portal locally.

### Using the Brand Script (macOS and Linux)

From the `src/OtisEd.Nimble.Web/Branding/` directory, run:

```
./brand.sh <BrandName>
```

For example, to apply the Kentucky brand:

```
./brand.sh Kentucky
```

The script copies the brand's files directly into the working React application and web server directories. After running the script, start or restart the React development server. A `last_brand.txt` file records which brand was last applied.

A Windows equivalent (`brand.bat`) is also available for the same directory.

**Important:** This script modifies the shared source files. If you switch between brands, you must re-run the script with the new brand name. The current brand is tracked in `last_brand.txt` to help prevent accidental overwrites.

### Using the Branded App Builder (All Platforms)

A more isolated approach creates a self-contained `branded-react-app/` directory that does not modify the shared source files. From the `src/OtisEd.Nimble.Web/ReactApp/` directory, run:

```
node create-branded-react-app.js <BrandName>
```

For example:

```
node create-branded-react-app.js Kentucky
```

This command:

1. Creates a `branded-react-app/` folder that contains a full copy of the React application
2. Merges in the brand-specific overrides from the selected brand folder
3. Copies brand CSS files to the correct locations within the branded app
4. Copies brand images to the correct locations
5. Starts watching both the base React app and the brand folder for changes, automatically syncing updates to `branded-react-app/` as you work

To run the branded app, open a second terminal, navigate into the `branded-react-app/` directory, and run `npm run start`.

If the `branded-react-app/` directory already exists from a previous run, the script preserves `node_modules/` to avoid a lengthy reinstall. To force a clean rebuild (for example, after adding new packages), add the `--clean` flag:

```
node create-branded-react-app.js Kentucky --clean
```

The `branded-react-app/` folder is generated output and is not committed to source control.

**Windows note:** Symlink creation may require administrator privileges or Developer Mode enabled on Windows. If symlinks are unavailable, the script falls back to copying files instead — this works correctly but uses more disk space.

## Saving Brand Changes Back to Source

When you have made visual or layout changes while working with a brand applied using `brand.sh`, you need to save those changes back into the brand's source folder before they are preserved in version control.

From the `Branding/` directory, run:

```
./savebrand.sh <BrandName>
```

For example:

```
./savebrand.sh Kentucky
```

This script reads the current state of the working CSS, branding components, and login pages, and copies them back into the brand folder. It checks `last_brand.txt` to confirm the brand you are saving matches the brand that was last applied — this prevents you from accidentally overwriting one brand's files with another brand's changes.

After running the save script, the brand folder is updated and ready to commit.

## How Branding Affects the User Experience

From an end user's perspective, branding is largely invisible — it simply ensures the portal looks and feels like their organization's system rather than a generic tool.

The key ways users experience branding:

* **Before login:** The sign-in page shows their organization's logo and color scheme. Some brands show custom background images or welcome text on the login screen.
* **After login:** The header logo, color palette, and navigation structure match their organization's identity. Kentucky users, for example, see a left-side navigation panel that other tenants do not have.
* **Home page:** The content displayed on the dashboard is managed by their organization's administrators through the widget system. The visual layout of the dashboard (spacing, background colors, imagery) is set by their brand's stylesheet.
* **Throughout the portal:** Link colors, heading fonts, button styles, and other design details reflect the brand's CSS overrides rather than the default OtisEd styling.
* **Maintenance periods:** If the portal is taken offline, the maintenance page displays their organization's product name and any custom message configured by administrators.

Users do not see any indication that multiple organizations share the same system. The branding creates a fully separate visual identity for each tenant.

## Common Scenarios

**Scenario: Updating the Kentucky home page photo**

A new header photo needs to appear on the Kentucky portal's home page. Add the new image file to `Branding/Kentucky/images/`, update the CSS or the relevant component in `Branding/Kentucky/ReactApp/` to reference it, apply the Kentucky brand using `brand.sh Kentucky`, verify the change in a local development build, then run `savebrand.sh Kentucky` to preserve the changes and commit the updated brand folder.

**Scenario: Customizing the footer for USVI**

The USVI footer needs a different copyright notice and an external link. Edit the `SiteFooter.tsx` file inside `Branding/USVI/ReactApp/src/site/branding/components/`. Apply and test with `brand.sh USVI` or the branded app builder, then save back with `savebrand.sh USVI`.

**Scenario: Adding a new tenant brand**

A new educational agency is joining the platform. Create a new folder under `Branding/` with the agency's name, following the same subfolder structure as an existing brand. At minimum, provide `ReactApp/src/css/branding/branding.css`, the layout and dashboard components, and header/login images. Add the new brand name to the valid brands list in `create-branded-react-app.js` and to the validation list in `brand.sh` and `brand.bat`.

**Scenario: Testing the portal without any custom branding**

Use `brand.sh OtisEd` to apply the default OtisEd brand. This is the baseline configuration used for development and internal testing when no client-specific branding is needed.

## Troubleshooting

**The branded app does not reflect my changes.** If you are using the branded app builder, confirm the file watcher is still running in your terminal. If it stopped, re-run the build command. If you are using `brand.sh`, re-run the script and restart your development server.

**The script reports a symlink error on Windows.** This is expected on Windows when not running as an administrator or without Developer Mode enabled. The script automatically falls back to copying files — the branded app will still work correctly.

**The save script says the brand does not match.** The `savebrand.sh` script compares the brand name you provide against `last_brand.txt`. If you applied brand A but are trying to save as brand B, the script stops to prevent accidental data loss. Re-apply the correct brand using `brand.sh`, verify it is working, and then run `savebrand.sh` with the matching brand name.

**Images or CSS changes are not appearing after a brand switch.** After running `brand.sh`, the web server's static files may be cached in your browser. Hard-reload the page (Ctrl+Shift+R or Cmd+Shift+R) to bypass cached assets.

**The maintenance page shows the wrong product name.** The default maintenance message is defined in the brand's Maintenance page component. A configurable message can be set through Site Settings in the administration area — this configured message takes precedence over the default. Check Site Settings first before editing source files.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://nimble.docs.otised.com/guides/multi-tenancy-branding.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
