Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

PlaceOS Backoffice is an Angular 20 admin UI for managing PlaceOS building automation systems. It uses standalone components (no NgModules), zoneless change detection with signals, and integrates with PlaceOS backend APIs.
PlaceOS Backoffice is an Angular 22 admin UI for managing PlaceOS building automation systems. It uses standalone components (no NgModules), zoneless change detection with signals, and integrates with PlaceOS backend APIs.

## Commands

Expand All @@ -25,10 +25,10 @@ bun run lint # ESLint

## Tech Stack

- **Framework**: Angular 20 with standalone components and signals
- **Framework**: Angular 22 with standalone components and signals
- **Build**: Nx 23 + Vite 8 + @analogjs/vite-plugin-angular
- **Testing**: Vitest + Playwright
- **Styling**: Tailwind CSS 3.4 with custom CSS variables
- **Styling**: Tailwind CSS 4 (legacy `tailwind.config.js` loaded via `@config`) with custom CSS variables
- **Backend**: @placeos/ts-client for PlaceOS REST API integration
- **Real-time**: MQTT for dashboard updates

Expand All @@ -54,7 +54,7 @@ src/app/
Each feature follows this structure:
- `*.routes.ts` - Lazy-loaded feature routing
- `*-state.service.ts` - Feature state management with signals/observables
- Component files with `standalone: true`
- Standalone components (the default since Angular 19, so `standalone: true` is not needed)

### Key Patterns

Expand Down Expand Up @@ -90,11 +90,11 @@ export class MyComponent extends AsyncHandler {

## Notes

- Angular 20 with standalone components and signals
- TypeScript 5.8.3
- Angular 22 with standalone components and signals
- TypeScript ~6.0
- Supports multiple locales (see `app.locales` in settings)
- Service worker support for PWA functionality (production builds)
- Custom localization support via `public/assets/locales`, **TranslationPipe** and **LocalesService**
- Custom localization support via `public/assets/locale`, **TranslatePipe** and **LocaleService**. `en-AU.json` is the fallback locale
- Use **IconComponent** for icons

## Code styles
Expand Down
18 changes: 0 additions & 18 deletions e2e/src/admin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,24 +286,6 @@ test.describe('Admin', () => {
});
});

/**
* US-ADM-014: Manage Email Templates
* NOTE: This test is skipped because the mailing-list tab is disabled in the application
*/
test.describe('US-ADM-014: Manage Email Templates', () => {
test.skip('AC-ADM-014-1: View Templates - should show email templates section', async ({
page,
}) => {
await adminPage.goto();

// Navigate to email templates section
await adminPage.viewEmailTemplates();

// Verify content is displayed
await expect(adminPage.mainContent).toBeVisible();
});
});

/**
* US-ADM-015: Monitor Build Jobs
*/
Expand Down
37 changes: 0 additions & 37 deletions e2e/src/pages/admin.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,6 @@ export class AdminPage extends BasePage {
return this.page.locator('a[href*="/admin/upload-library"]').first();
}

/**
* Get the mailing list section link (route: /admin/mailing-list)
* NOTE: This tab is currently disabled in the application
*/
get mailingListLink(): Locator {
return this.page.locator('a[href*="/admin/mailing-list"]').first();
}

/**
* Get the build jobs section link (route: /admin/build-jobs)
*/
Expand Down Expand Up @@ -448,35 +440,6 @@ export class AdminPage extends BasePage {
await this.save();
}

// Email Templates

/**
* View email templates
*/
async viewEmailTemplates(): Promise<void> {
await this.mailingListLink.click();
await this.page.waitForTimeout(500);
}

/**
* Create email template
*/
async createEmailTemplate(data: {
name: string;
subject: string;
body: string;
}): Promise<void> {
await this.viewEmailTemplates();
await this.addButton.click();
await this.dialog.waitFor({ timeout: 5000 });

await this.fillField('Name', data.name);
await this.fillField('Subject', data.subject);
await this.fillField('Body', data.body);

await this.save();
}

// Build Jobs

/**
Expand Down
12 changes: 6 additions & 6 deletions public/assets/locale/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
"SETTINGS_MASKED_ERROR": "丕爻鬲亘丿賱 噩賲賷毓 丕賱賯賷賲 丕賱賲禺賮賷丞 賯亘賱 丨賮馗 丕賱廿毓丿丕丿丕鬲 丕賱賲卮賮乇丞",
"SETTINGS_INHERITED": "廿毓丿丕丿 賲賵乇賵孬 賲賳 [{{ parent }}]({{ path }})({{ type }})",
"SETTINGS_LOCAL": "廿毓丿丕丿 賲丨賱賷 賲賳 {{ type }}",
"SETTINGS_SAVE_ERROR": "賮卮賱 丨賮馗 丕賱廿毓丿丕丿丕鬲. 丕賱禺胤兀 {{ error }}",
"SETTINGS_SAVE_ERROR": "賮卮賱 丨賮馗 丕賱廿毓丿丕丿丕鬲. 丕賱禺胤兀: {{ error }}",
"SETTINGS_SAVE_SUCCESS": "鬲賲 丨賮馗 鬲睾賷賷乇丕鬲 廿毓丿丕丿丕鬲 {{ type }} 亘賳噩丕丨",
"SETTINGS_SAVE_SUCCESS_ALL": "鬲賲 丨賮馗 噩賲賷毓 鬲睾賷賷乇丕鬲 丕賱廿毓丿丕丿丕鬲 亘賳噩丕丨",
"ADD_EXISTING": "廿囟丕賮丞 賲賵噩賵丿",
Expand Down Expand Up @@ -532,7 +532,7 @@
"FIELD_ACTION_FN_CALL": "廿噩乇丕亍 丕爻鬲丿毓丕亍 賵馗賷賮丞",
"FIELD_ACTION_EMAIL": "廿噩乇丕亍 亘乇賷丿 廿賱賰鬲乇賵賳賷",
"REORDER_CONFIRM_TITLE": "廿毓丕丿丞 鬲乇鬲賷亘 廿噩乇丕亍 {{ type }} 賱賱賲丨賮夭",
"REORDER_CONFIRM_MSG": "賴賱 兀賳鬲 賲鬲兀賰丿 兀賳賰 鬲乇賷丿 廿夭丕賱丞 卮乇胤 丕賱賲丨賮夭 賴匕丕責<br>爻賷鬲賲 鬲丨丿賷孬 噩賲賷毓 丕賱兀賳馗賲丞 丕賱鬲賷 鬲爻鬲禺丿賲 賴匕丕 丕賱賲丨賮夭 <strong>賮賵乇丕賸</strong>.",
"REORDER_CONFIRM_MSG": "賴賱 兀賳鬲 賲鬲兀賰丿 兀賳賰 鬲乇賷丿 廿毓丕丿丞 鬲乇鬲賷亘 廿噩乇丕亍丕鬲 丕賱賲丨賮夭責<br>爻賷鬲賲 鬲丨丿賷孬 噩賲賷毓 丕賱兀賳馗賲丞 丕賱鬲賷 鬲爻鬲禺丿賲 賴匕丕 丕賱賲丨賮夭 <strong>賮賵乇丕賸</strong>.",
"REORDER_CONFIRM_LOADING": "噩丕乇賷 廿毓丕丿丞 鬲乇鬲賷亘 廿噩乇丕亍丕鬲 丕賱賲丨賮夭...",
"REORDER_CONFIRM_ERROR": "賮卮賱 賮賷 廿毓丕丿丞 鬲乇鬲賷亘 廿噩乇丕亍 丕賱賲丨賮夭. 丕賱禺胤兀: {{ error }}",
"REORDER_CONFIRM_SUCCESS": "鬲賲 廿毓丕丿丞 鬲乇鬲賷亘 廿噩乇丕亍 丕賱賲丨賮夭 亘賳噩丕丨",
Expand Down Expand Up @@ -626,7 +626,7 @@
"STAFF_CARD": "乇賯賲 丕賱亘胤丕賯丞",
"PASSWORD_REQUIRED": "賲胤賱賵亘 賰賱賲丞 賲乇賵乇 氐丕賱丨丞",
"PASSWORD_CONFIRM": "鬲兀賰賷丿 賰賱賲丞 丕賱賲乇賵乇",
"PASSOWRD_MATCH": "賰賱賲丕鬲 丕賱賲乇賵乇 睾賷乇 賲鬲胤丕亘賯丞"
"PASSWORDS_MATCH": "賰賱賲丕鬲 丕賱賲乇賵乇 睾賷乇 賲鬲胤丕亘賯丞"
},
"DOMAINS": {
"SINGULAR": "賲噩丕賱",
Expand Down Expand Up @@ -686,8 +686,8 @@
"AUTHENTICATION_EMPTY": "賱丕 鬲賵噩丿 胤乇賯 賲氐丕丿賯丞 賱賴匕丕 丕賱賲噩丕賱",
"AUTHENTICATION_SOURCE_TYPE": "賳賵毓 賲氐丿乇 丕賱賲氐丕丿賯丞",
"AUTHENTICATION_SOURCE_SELECT": "丕禺鬲乇 賲氐丿乇 丕賱賲氐丕丿賯丞...",
"AUTHENTICATION_SAVE_ERROR": "鬲賲 鬲丨丿賷孬 賲氐丿乇 丕賱賲氐丕丿賯丞 亘賳噩丕丨",
"AUTHENTICATION_SAVE_SUCCESS": "賮卮賱 鬲丨丿賷孬 賲氐丿乇 丕賱賲氐丕丿賯丞. 丕賱禺胤兀: {{ error }}",
"AUTHENTICATION_SAVE_SUCCESS": "鬲賲 鬲丨丿賷孬 賲氐丿乇 丕賱賲氐丕丿賯丞 亘賳噩丕丨",
"AUTHENTICATION_SAVE_ERROR": "賮卮賱 鬲丨丿賷孬 賲氐丿乇 丕賱賲氐丕丿賯丞. 丕賱禺胤兀: {{ error }}",
"AUTHENTICATION_NAME_REQUIRED": "賲胤賱賵亘 丕爻賲 賱賲氐丿乇 丕賱賲氐丕丿賯丞",
"OAUTH_SITE": "丕賱賲賵賯毓",
"OAUTH_SITE_PLACEHOLDER": "毓賳賵丕賳 URL 賱賲賵賮乇 SSO",
Expand Down Expand Up @@ -896,7 +896,7 @@
"APP_KEYS_LAST_DETAILS": "丌禺乇 鬲賮丕氐賷賱 賲賮鬲丕丨 鬲胤亘賷賯",
"APP_KEYS_FIELD_SCOPES": "賳胤丕賯丕鬲",
"APP_KEYS_FIELD_PERMISSIONS": "丕賱賵氐賵賱",
"APP_KEYS_SCOPES_REQUIRE": "賲胤賱賵亘 賳胤丕賯 賵丕丨丿 毓賱賶 丕賱兀賯賱",
"APP_KEYS_SCOPES_REQUIRED": "賲胤賱賵亘 賳胤丕賯 賵丕丨丿 毓賱賶 丕賱兀賯賱",
"APP_KEYS_NAME_REQUIRED": "賲胤賱賵亘 丕爻賲",
"APP_KEYS_PERMISSIONS_EMPTY": "賱丕 鬲賵噩丿 兀匕賵賳丕鬲",
"APP_KEYS_COPIED": "鬲賲 賳爻禺 賲賮鬲丕丨 丕賱鬲胤亘賷賯 廿賱賶 丕賱丨丕賮馗丞",
Expand Down
30 changes: 19 additions & 11 deletions public/assets/locale/en-AU.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
"CANCEL": "Cancel",
"CLOSE": "Close",
"CONFIRM": "Confirm",
"ITEM_REMOVE": "Remove {{ item }}",
"FIELD_REQUIRED": "This field is required",
"DATE_EMPTY": "No date selected",
"SAVE": "Save",
"SAVE_ALL": "Save All",
"CLEAR": "Clear Changes",
Expand All @@ -70,6 +73,7 @@
"ITEM_ADD_SELECTED": "Add selected",
"PROCESSING_REQUEST": "Processing request...",
"PASSWORD": "Password",
"PASSWORD_TOGGLE": "Show password",
"IMAGE_ADD_URL": "Add image via URL",
"IMAGE_UPLOADS": "Upload Image(s)",
"DROP_UPLOAD_MSG": "Drop files to upload them to the cloud",
Expand Down Expand Up @@ -139,7 +143,7 @@
"SETTINGS_MASKED_ERROR": "Replace all masked values before you save encrypted settings",
"SETTINGS_INHERITED": "Setting inherited from [{{ parent }}]({{ path }})({{ type }})",
"SETTINGS_LOCAL": "Local setting from {{ type }}",
"SETTINGS_SAVE_ERROR": "Failed to save settings. Error {{ error }}",
"SETTINGS_SAVE_ERROR": "Failed to save settings. Error: {{ error }}",
"SETTINGS_SAVE_SUCCESS": "Successfully saved {{ type }} settings changes",
"SETTINGS_SAVE_SUCCESS_ALL": "Successfully saved all settings changes",
"ADD_EXISTING": "Add existing",
Expand Down Expand Up @@ -607,8 +611,8 @@
"ACTION_FN_EMPTY": "No function call actions for this trigger",
"FIELD_ACTION_FN_CALL": "Function call Action",
"FIELD_ACTION_EMAIL": "Email Action",
"REORDER_CONFIRM_TITLE": "Reoreder trigger {{ type }} action",
"REORDER_CONFIRM_MSG": "Are you sure you want remove this trigger condition?<br>All systems using this trigger will be updated <strong>immediately</strong>.",
"REORDER_CONFIRM_TITLE": "Reorder trigger {{ type }} action",
"REORDER_CONFIRM_MSG": "Are you sure you want to reorder the trigger actions?<br>All systems using this trigger will be updated <strong>immediately</strong>.",
"REORDER_CONFIRM_LOADING": "Re-ordering trigger actions...",
"REORDER_CONFIRM_ERROR": "Failed to re-ordered trigger action. Error: {{ error }}",
"REORDER_CONFIRM_SUCCESS": "Successfully re-ordered trigger action",
Expand Down Expand Up @@ -679,6 +683,7 @@
"NEW": "New user",
"BULK": "Bulk add users",
"REMOVE": "Remove user",
"DELETE": "Delete user",
"DELETE_MSG": "<p>Are you sure you want delete this user?</p><p>The user will be removed from the system within 24 hours</p>",
"DELETE_LOADING": "Deleting user...",
"DELETE_SUCCESS": "Successfully deleted user.",
Expand Down Expand Up @@ -729,7 +734,7 @@
"STAFF_CARD": "Card Number",
"PASSWORD_REQUIRED": "A valid password is required",
"PASSWORD_CONFIRM": "Confirm Password",
"PASSOWRD_MATCH": "Passwords do not match",
"PASSWORDS_MATCH": "Passwords do not match",
"IMAGE": "Profile Image",
"IMAGE_INVALID": "A valid URL is required for profile image",
"LOCATABLE": "Enable Location Tracking"
Expand Down Expand Up @@ -867,9 +872,9 @@
"FIELD_USER": "User",
"FIELD_ROLE": "Role",
"APPLICATIONS_EMPTY": "No applications for this domain",
"APPLICATIONS_SAVING": "Saving domain application...",
"APPLICATIONS_SAVE_ERROR": "Failed to save domain application",
"APPLICATIONS_SAVE_SUCCESS": "Successfully saved domain application",
"APPLICATION_SAVING": "Saving domain application...",
"APPLICATION_SAVE_ERROR": "Failed to save domain application. Error: {{ error }}",
"APPLICATION_SAVE_SUCCESS": "Successfully saved domain application",
"COPY_SECRET": "Copy secret to Clipboard",
"COPIED_SECRET": "Copied client secret to Clipboard",
"VIEW_SECRET": "View Secret",
Expand All @@ -891,8 +896,8 @@
"AUTHENTICATION_EMPTY": "No authentication methods for this domain",
"AUTHENTICATION_SOURCE_TYPE": "Authentication Source Type",
"AUTHENTICATION_SOURCE_SELECT": "Select authentication source...",
"AUTHENTICATION_SAVE_ERROR": "Successfully updated authentication source",
"AUTHENTICATION_SAVE_SUCCESS": "Failed to update authentication source. Error: {{ error }}",
"AUTHENTICATION_SAVE_SUCCESS": "Successfully updated authentication source",
"AUTHENTICATION_SAVE_ERROR": "Failed to update authentication source. Error: {{ error }}",
"AUTHENTICATION_NAME_REQUIRED": "An auth source name is required",
"OAUTH_SITE": "Site",
"OAUTH_SITE_PLACEHOLDER": "URL of the SSO provider",
Expand Down Expand Up @@ -1020,14 +1025,17 @@
"BROKERS_AUTH_TYPE_NONE": "No Auth",
"BROKERS_FILTERS_EMPTY": "No filters",
"BROKERS_NAME_REQUIRED": "A broker name is required",
"BROKERS_HOST_REQUIRED": "A broker name is required",
"BROKERS_HOST_REQUIRED": "A broker host is required",
"BROKERS_PORT_REQUIRED": "A valid port number between 1 - 65535 is required",
"BROKERS_USERNAME": "Username",
"BROKERS_USERNAME_REQUIRED": "A valid username is required",
"BROKERS_PASSWORD": "Password",
"BROKERS_PASSWORD_REQUIRED": "A valid password is required ",
"BROKERS_CERT": "Certificate",
"BROKERS_CERT_REQUIRED": "A valid certificate is required ",
"BROKERS_SAVING": "Saving MQTT broker...",
"BROKERS_SAVE_ERROR": "Failed to save MQTT broker. Error: {{ error }}",
"BROKERS_SAVE_SUCCESS": "Successfully saved MQTT broker.",
"TENANTS_HEADER": "Staff API Tenants",
"TENANTS_ADD": "Add Tenant",
"TENANTS_NEW": "New Tenant",
Expand Down Expand Up @@ -1122,7 +1130,7 @@
"APP_KEYS_TTL_MONTH": "30 Days",
"APP_KEYS_TTL_90_DAYS": "90 Days",
"APP_KEYS_TTL_YEAR": "1 Year",
"APP_KEYS_SCOPES_REQUIRE": "At least one scope is required",
"APP_KEYS_SCOPES_REQUIRED": "At least one scope is required",
"APP_KEYS_NAME_REQUIRED": "A name is required",
"APP_KEYS_PERMISSIONS_EMPTY": "No permissions",
"APP_KEYS_COPIED": "Copied Application Key to clipboard",
Expand Down
18 changes: 9 additions & 9 deletions public/assets/locale/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
"SETTINGS_MASKED_ERROR": "Replace all masked values before you save encrypted settings",
"SETTINGS_INHERITED": "Setting inherited from [{{ parent }}]({{ path }})({{ type }})",
"SETTINGS_LOCAL": "Local setting from {{ type }}",
"SETTINGS_SAVE_ERROR": "Failed to save settings. Error {{ error }}",
"SETTINGS_SAVE_ERROR": "Failed to save settings. Error: {{ error }}",
"SETTINGS_SAVE_SUCCESS": "Successfully saved {{ type }} settings changes",
"SETTINGS_SAVE_SUCCESS_ALL": "Successfully saved all settings changes",
"ADD_EXISTING": "Add existing",
Expand Down Expand Up @@ -531,7 +531,7 @@
"ACTIONS": "Actions",
"ACTION_ADD": "Add Action",
"ACTION_NEW": "New Action",
"ACTION_FIELD_TYPE": "ACtion Type",
"ACTION_FIELD_TYPE": "Action Type",
"ACTION_EXECUTE": "Execute Method",
"ACTION_EXECUTE_SELECT": "Select method to execute",
"ACTION_EMAIL": "Send Email",
Expand All @@ -542,8 +542,8 @@
"ACTION_FN_EMPTY": "No function call actions for this trigger",
"FIELD_ACTION_FN_CALL": "Function call Action",
"FIELD_ACTION_EMAIL": "Email Action",
"REORDER_CONFIRM_TITLE": "Reoreder trigger {{ type }} action",
"REORDER_CONFIRM_MSG": "Are you sure you want remove this trigger condition?<br>All systems using this trigger will be updated <strong>immediately</strong>.",
"REORDER_CONFIRM_TITLE": "Reorder trigger {{ type }} action",
"REORDER_CONFIRM_MSG": "Are you sure you want to reorder the trigger actions?<br>All systems using this trigger will be updated <strong>immediately</strong>.",
"REORDER_CONFIRM_LOADING": "Re-ordering trigger actions...",
"REORDER_CONFIRM_ERROR": "Failed to re-ordered trigger action. Error: {{ error }}",
"REORDER_CONFIRM_SUCCESS": "Successfully re-ordered trigger action",
Expand Down Expand Up @@ -639,7 +639,7 @@
"STAFF_CARD": "Card Number",
"PASSWORD_REQUIRED": "A valid password is required",
"PASSWORD_CONFIRM": "Confirm Password",
"PASSOWRD_MATCH": "Passwords do not match"
"PASSWORDS_MATCH": "Passwords do not match"
},
"DOMAINS": {
"SINGULAR": "Domain",
Expand Down Expand Up @@ -699,8 +699,8 @@
"AUTHENTICATION_EMPTY": "No authentication methods for this domain",
"AUTHENTICATION_SOURCE_TYPE": "Authentication Source Type",
"AUTHENTICATION_SOURCE_SELECT": "Select authentication source...",
"AUTHENTICATION_SAVE_ERROR": "Successfully updated authentication source",
"AUTHENTICATION_SAVE_SUCCESS": "Failed to update authentication source. Error: {{ error }}",
"AUTHENTICATION_SAVE_SUCCESS": "Successfully updated authentication source",
"AUTHENTICATION_SAVE_ERROR": "Failed to update authentication source. Error: {{ error }}",
"AUTHENTICATION_NAME_REQUIRED": "An auth source name is required",
"OAUTH_SITE": "Site",
"OAUTH_SITE_PLACEHOLDER": "URL of the SSO provider",
Expand Down Expand Up @@ -827,7 +827,7 @@
"BROKERS_AUTH_TYPE_NONE": "No Auth",
"BROKERS_FILTERS_EMPTY": "No filters",
"BROKERS_NAME_REQUIRED": "A broker name is required",
"BROKERS_HOST_REQUIRED": "A broker name is required",
"BROKERS_HOST_REQUIRED": "A broker host is required",
"BROKERS_PORT_REQUIRED": "A valid port number between 1 - 65535 is required",
"BROKERS_USERNAME": "Username",
"BROKERS_USERNAME_REQUIRED": "A valid username is required",
Expand Down Expand Up @@ -927,7 +927,7 @@
"APP_KEYS_TTL_MONTH": "30 Days",
"APP_KEYS_TTL_90_DAYS": "90 Days",
"APP_KEYS_TTL_YEAR": "1 Year",
"APP_KEYS_SCOPES_REQUIRE": "At least one scope is required",
"APP_KEYS_SCOPES_REQUIRED": "At least one scope is required",
"APP_KEYS_NAME_REQUIRED": "A name is required",
"APP_KEYS_PERMISSIONS_EMPTY": "No permissions",
"APP_KEYS_COPIED": "Copied Application Key to clipboard",
Expand Down
Loading
Loading