feat(ui): add workspace customization and simplify navigation
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user