feat: manage organizations from switcher

Rename the centre switcher to organization switcher and wire it to loaded organizations.

Add dropdown actions for switching organizations, creating a new organization, and editing the active organization in dialogs.

Persist organization changes through dashboard endpoints while preserving the current redirect path.
This commit is contained in:
2026-06-06 14:14:20 +01:00
parent cbd2fb2921
commit 7c57a22a3c
7 changed files with 510 additions and 77 deletions
@@ -0,0 +1,50 @@
import { redirect, error } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { organizations } from '$lib/server/db/schema';
import { organizationCreateSchema } from '$lib/schemas/organizations.schema';
import { setActiveOrganization } from '$lib/server/organizations';
import type { RequestHandler } from './$types';
function safeRedirect(value: string) {
return value.startsWith('/') && !value.startsWith('//') ? value : '/dashboard';
}
export const POST: RequestHandler = async ({ locals, request }) => {
if (!locals.user) {
redirect(303, '/login');
}
const formData = await request.formData();
const parsed = organizationCreateSchema.safeParse(Object.fromEntries(formData));
if (!parsed.success) {
error(400, 'Check the organization details and try again.');
}
const id = crypto.randomUUID();
const now = new Date();
await db.insert(organizations).values({
id,
name: parsed.data.name,
workspace: parsed.data.workspace,
addressLine1: parsed.data.addressLine1 || null,
addressLine2: parsed.data.addressLine2 || null,
city: parsed.data.city || null,
region: parsed.data.region || null,
postcode: parsed.data.postcode || null,
country: parsed.data.country,
bankName: parsed.data.bankName || null,
bankAccountName: parsed.data.bankAccountName || null,
bankAccountNumber: parsed.data.bankAccountNumber || null,
bankSortCode: parsed.data.bankSortCode || null,
bankIban: parsed.data.bankIban || null,
bankSwift: parsed.data.bankSwift || null,
createdAt: now,
updatedAt: now
});
await setActiveOrganization(locals, id);
redirect(303, safeRedirect(parsed.data.redirectTo));
};
@@ -0,0 +1,56 @@
import { and, eq, isNull } from 'drizzle-orm';
import { error, redirect } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { organizations } from '$lib/server/db/schema';
import { loadOrganizationContext } from '$lib/server/organizations';
import { organizationEditSchema } from '$lib/schemas/organizations.schema';
import type { RequestHandler } from './$types';
function safeRedirect(value: FormDataEntryValue | null) {
return typeof value === 'string' && value.startsWith('/') && !value.startsWith('//')
? value
: '/dashboard';
}
export const POST: RequestHandler = async ({ locals, request }) => {
if (!locals.user) {
redirect(303, '/login');
}
const { activeOrganizationId } = await loadOrganizationContext(locals);
const formData = await request.formData();
const parsed = organizationEditSchema.safeParse(Object.fromEntries(formData));
if (!parsed.success || parsed.data.id !== activeOrganizationId) {
error(400, 'Check the organization details and try again.');
}
await db
.update(organizations)
.set({
name: parsed.data.name,
workspace: parsed.data.workspace,
addressLine1: parsed.data.addressLine1 || null,
addressLine2: parsed.data.addressLine2 || null,
city: parsed.data.city || null,
region: parsed.data.region || null,
postcode: parsed.data.postcode || null,
country: parsed.data.country,
bankName: parsed.data.bankName || null,
bankAccountName: parsed.data.bankAccountName || null,
bankAccountNumber: parsed.data.bankAccountNumber || null,
bankSortCode: parsed.data.bankSortCode || null,
bankIban: parsed.data.bankIban || null,
bankSwift: parsed.data.bankSwift || null,
updatedAt: new Date()
})
.where(
and(
eq(organizations.id, activeOrganizationId),
eq(organizations.id, parsed.data.id),
isNull(organizations.archivedAt)
)
);
redirect(303, safeRedirect(formData.get('redirectTo')));
};
@@ -0,0 +1,18 @@
import { redirect } from '@sveltejs/kit';
import { setActiveOrganization } from '$lib/server/organizations';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ locals, request }) => {
const formData = await request.formData();
const organizationId = formData.get('organizationId');
const redirectTo = formData.get('redirectTo');
if (typeof organizationId === 'string') {
await setActiveOrganization(locals, organizationId);
}
redirect(
303,
typeof redirectTo === 'string' && redirectTo.startsWith('/') ? redirectTo : '/dashboard'
);
};