From 33f59460fd58d3e8e8616c0d4dce1312bf0dce11 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 4 Apr 2026 04:22:00 +0000 Subject: [PATCH] Autosave: 20260404-042157 --- backend/src/db/api/users.js | 10 +- backend/src/routes/organizationLogin.js | 56 ++- frontend/src/components/AsideMenuLayer.tsx | 43 +++ .../src/components/ConnectedEntityCard.tsx | 6 + .../src/components/CurrentWorkspaceChip.tsx | 303 +++++++++++++++ .../src/components/MyOrgTenantSummary.tsx | 191 ++++++++++ .../Organizations/CardOrganizations.tsx | 92 +++-- .../Organizations/ListOrganizations.tsx | 196 ++++++---- frontend/src/components/TenantStatusChip.tsx | 23 ++ .../src/components/Tenants/CardTenants.tsx | 355 +++++++----------- .../src/components/Tenants/ListTenants.tsx | 272 ++++++-------- frontend/src/layouts/Authenticated.tsx | 2 + frontend/src/pages/command-center.tsx | 3 + frontend/src/pages/dashboard.tsx | 3 + frontend/src/pages/login.tsx | 41 ++ .../organizations/organizations-edit.tsx | 3 +- .../organizations/organizations-view.tsx | 3 + frontend/src/pages/profile.tsx | 2 + frontend/src/pages/register.tsx | 275 +++++++++----- 19 files changed, 1283 insertions(+), 596 deletions(-) create mode 100644 frontend/src/components/CurrentWorkspaceChip.tsx create mode 100644 frontend/src/components/MyOrgTenantSummary.tsx create mode 100644 frontend/src/components/TenantStatusChip.tsx diff --git a/backend/src/db/api/users.js b/backend/src/db/api/users.js index f6b1b67..c716367 100644 --- a/backend/src/db/api/users.js +++ b/backend/src/db/api/users.js @@ -903,19 +903,23 @@ module.exports = class UsersDBApi { static async createFromAuth(data, options) { const transaction = (options && options.transaction) || undefined; + const organizationId = data.organizationId || data.organizationsId || null; const users = await db.users.create( { email: data.email, firstName: data.firstName, authenticationUid: data.authenticationUid, password: data.password, - - organizationId: data.organizationId, - }, { transaction }, ); + if (organizationId) { + await users.setOrganizations(organizationId, { + transaction, + }); + } + const app_role = await db.roles.findOne({ where: { name: config.roles?.user || "User" }, }); diff --git a/backend/src/routes/organizationLogin.js b/backend/src/routes/organizationLogin.js index 718b6c1..568ef0e 100644 --- a/backend/src/routes/organizationLogin.js +++ b/backend/src/routes/organizationLogin.js @@ -1,13 +1,31 @@ - - - const express = require('express'); +const db = require('../db/models'); const OrganizationsDBApi = require('../db/api/organizations'); const wrapAsync = require('../helpers').wrapAsync; const router = express.Router(); +async function loadLinkedTenantsForOrganization(organizationId) { + const linkedTenants = await db.tenants.findAll({ + attributes: ['id', 'name', 'slug', 'primary_domain', 'timezone', 'default_currency', 'is_active'], + include: [ + { + model: db.organizations, + as: 'organizations_filter', + required: true, + attributes: [], + through: { attributes: [] }, + where: { id: organizationId }, + }, + ], + limit: 3, + order: [['name', 'asc']], + }); + + return linkedTenants.map((tenant) => tenant.get({ plain: true })); +} + /** * @swagger * /api/organizations: @@ -33,23 +51,29 @@ const router = express.Router(); * 500: * description: Some server error */ - router.get( '/', wrapAsync(async (req, res) => { - const payload = await OrganizationsDBApi.findAll(req.query); - const simplifiedPayload = payload.rows.map(org => ({ - id: org.id, - name: org.name - })); - res.status(200).send(simplifiedPayload); - + const payload = await OrganizationsDBApi.findAll(req.query || {}, true, {}); + + const simplifiedPayload = await Promise.all( + (payload.rows || []).map(async (org) => { + const linkedTenants = await loadLinkedTenantsForOrganization(org.id); + + return { + id: org.id, + name: org.name, + linkedTenants, + linkedTenantNames: linkedTenants + .map((tenant) => tenant.name) + .filter(Boolean), + primaryTenant: linkedTenants[0] || null, + }; + }), + ); + + res.status(200).send(simplifiedPayload); }), ); - - - module.exports = router; - - diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx index 68c4fe4..3fc909c 100644 --- a/frontend/src/components/AsideMenuLayer.tsx +++ b/frontend/src/components/AsideMenuLayer.tsx @@ -4,6 +4,8 @@ import BaseIcon from './BaseIcon'; import AsideMenuList from './AsideMenuList'; import { MenuAsideItem } from '../interfaces'; import { useAppSelector } from '../stores/hooks'; +import { loadLinkedTenantSummary } from '../helpers/organizationTenants'; +import type { LinkedTenantRecord } from '../helpers/organizationTenants'; const ASIDE_WIDTH_STORAGE_KEY = 'aside-width'; const DEFAULT_ASIDE_WIDTH = 320; @@ -25,6 +27,7 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props const darkMode = useAppSelector((state) => state.style.darkMode); const { currentUser } = useAppSelector((state) => state.auth); const [asideWidth, setAsideWidth] = useState(DEFAULT_ASIDE_WIDTH); + const [linkedTenants, setLinkedTenants] = useState([]); useEffect(() => { if (typeof window === 'undefined') return; @@ -45,6 +48,40 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props } }, [asideWidth]); + useEffect(() => { + const organizationId = + currentUser?.organizations?.id || currentUser?.organization?.id || currentUser?.organizationsId || currentUser?.organizationId; + + if (!organizationId) { + setLinkedTenants([]); + return; + } + + let isMounted = true; + + loadLinkedTenantSummary(organizationId) + .then((summary) => { + if (isMounted) { + setLinkedTenants(summary.rows.slice(0, 2)); + } + }) + .catch((error) => { + console.error('Failed to load sidebar tenant context:', error); + if (isMounted) { + setLinkedTenants([]); + } + }); + + return () => { + isMounted = false; + }; + }, [ + currentUser?.organizations?.id, + currentUser?.organization?.id, + currentUser?.organizationsId, + currentUser?.organizationId, + ]); + const handleAsideLgCloseClick = (e: React.MouseEvent) => { e.preventDefault(); props.onAsideLgCloseClick(); @@ -61,6 +98,11 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props const organizationName = currentUser?.organizations?.name || currentUser?.organization?.name || 'Corporate workspace'; + const tenantContextLabel = linkedTenants.length + ? `Tenant${linkedTenants.length > 1 ? 's' : ''}: ${linkedTenants + .map((tenant) => tenant.name || 'Unnamed tenant') + .join(', ')}` + : 'No tenant link surfaced yet'; return (