feat(ui): add workspace customization and simplify navigation

This commit is contained in:
poyrazavsever
2026-07-17 12:47:54 +03:00
parent b04031a3e3
commit dafd9eb344
24 changed files with 855 additions and 224 deletions
+11 -2
View File
@@ -128,15 +128,20 @@ export class BrandingService {
throw new DomainError("FORBIDDEN", "Instance marka ayarları başka bir owner'a ait.");
}
const update = { ...parsed.data };
if (parsed.data.primaryColor && parsed.data.accentColor === undefined) {
update.accentColor = deriveAccentColor(parsed.data.primaryColor);
}
if (existing) {
this.repository.update({ ...parsed.data, updatedByUserId: scope.ownerUserId });
this.repository.update({ ...update, updatedByUserId: scope.ownerUserId });
} else {
this.repository.create({
id: "default",
ownerUserId: scope.ownerUserId,
updatedByUserId: scope.ownerUserId,
...DEFAULT_BRANDING,
...parsed.data,
...update,
});
}
return this.getPublic();
@@ -202,6 +207,10 @@ export function contrastRatio(first: string, second: string): number {
return (Math.max(firstLuminance, secondLuminance) + 0.05) / (Math.min(firstLuminance, secondLuminance) + 0.05);
}
export function deriveAccentColor(primary: string): string {
return mixHex(primary, "#FFFFFF", 0.88);
}
function readableForeground(background: string): "#000000" | "#FFFFFF" {
return contrastRatio(background, "#000000") >= contrastRatio(background, "#FFFFFF")
? "#000000"
+60
View File
@@ -0,0 +1,60 @@
import "server-only";
import { eq } from "drizzle-orm";
import { z } from "zod";
import type { ColorMode } from "@/lib/color-mode";
import { getSqliteConnection } from "@/server/db/client";
import { userPreferences } from "@/server/db/schema";
import { assertEnabledActor, type DomainActor } from "@/server/domain/actor";
import { DomainError } from "@/server/domain/errors";
const colorModeInputSchema = z.object({
colorMode: z.enum(["light", "dark", "system"]),
});
export type PublicUserPreferences = {
colorMode: ColorMode;
};
export function getUserPreferences(actor: DomainActor): PublicUserPreferences {
assertEnabledActor(actor);
const row = getSqliteConnection().db
.select({ colorMode: userPreferences.colorMode })
.from(userPreferences)
.where(eq(userPreferences.ownerUserId, actor.authUserId))
.get();
return {
colorMode: (row?.colorMode as ColorMode | undefined) ?? "system",
};
}
export function updateColorModePreference(
actor: DomainActor,
input: unknown,
): PublicUserPreferences {
assertEnabledActor(actor);
const parsed = colorModeInputSchema.safeParse(input);
if (!parsed.success) {
throw new DomainError("VALIDATION_ERROR", "Renk modu tercihi geçersiz.");
}
const { db } = getSqliteConnection();
db.insert(userPreferences)
.values({
ownerUserId: actor.authUserId,
colorMode: parsed.data.colorMode,
})
.onConflictDoUpdate({
target: userPreferences.ownerUserId,
set: {
colorMode: parsed.data.colorMode,
updatedAt: new Date().toISOString(),
},
})
.run();
return { colorMode: parsed.data.colorMode };
}