feat: journal dashboard widgets, agent polish, and edit-mode live previews
Journal dashboard widgets and quick-add; rich-text quick-add dialogs. Dashboard draft sync for live edit previews; assistant bubble + API tools. Journal UX: stress slider, mood grid, query cap fix.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/lib/db";
|
||||
import { users } from "@/modules/_core/schema";
|
||||
|
||||
export async function getAssistantEnabled(userId: string): Promise<boolean> {
|
||||
const [row] = await db
|
||||
.select({ assistantEnabled: users.assistantEnabled })
|
||||
.from(users)
|
||||
.where(eq(users.id, userId))
|
||||
.limit(1);
|
||||
|
||||
return row?.assistantEnabled ?? false;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import "server-only";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import { parseDashboardLayout, type DashboardLayout } from "./dashboard";
|
||||
|
||||
function draftCookieName(dashboardId: string) {
|
||||
return `dashboard-editor-draft-${dashboardId}`;
|
||||
}
|
||||
|
||||
export async function readEditorDraftLayout(dashboardId: string): Promise<DashboardLayout | null> {
|
||||
const jar = await cookies();
|
||||
const raw = jar.get(draftCookieName(dashboardId))?.value;
|
||||
if (!raw) return null;
|
||||
try {
|
||||
return parseDashboardLayout(JSON.parse(raw));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function writeEditorDraftLayout(dashboardId: string, layout: DashboardLayout) {
|
||||
const jar = await cookies();
|
||||
jar.set(draftCookieName(dashboardId), JSON.stringify(layout), {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
maxAge: 60 * 60,
|
||||
path: "/",
|
||||
});
|
||||
}
|
||||
|
||||
export async function clearEditorDraftLayout(dashboardId: string) {
|
||||
const jar = await cookies();
|
||||
jar.delete(draftCookieName(dashboardId));
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import "server-only";
|
||||
import { getRegistry } from "@/modules/_core";
|
||||
import type { SerializedWidgetMeta } from "@/modules/_core/registry";
|
||||
import type { DashboardLayout, PresetId } from "./dashboard";
|
||||
import type { DashboardLayout, PresetId, WidgetPlacement } from "./dashboard";
|
||||
import { computePresetLayoutFromMetas } from "./dashboard";
|
||||
import { getWidget } from "@/modules/_core";
|
||||
|
||||
/** Build a layout matching one of the design's three dashboard arrangements
|
||||
* using the live module registry. Server-only — the registry is empty on
|
||||
@@ -29,3 +30,31 @@ function computePresetLayout(preset: PresetId): DashboardLayout {
|
||||
export function computeDefaultLayout(): DashboardLayout {
|
||||
return computePresetLayout("classic");
|
||||
}
|
||||
|
||||
export function normalizeDashboardLayout(layout: DashboardLayout): DashboardLayout {
|
||||
return {
|
||||
version: layout.version,
|
||||
widgets: layout.widgets.map((placement) => normalizeWidgetPlacement(placement)),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeWidgetPlacement(placement: WidgetPlacement): WidgetPlacement {
|
||||
const widget = getWidget(placement.widgetId);
|
||||
if (!widget) return placement;
|
||||
|
||||
const merged =
|
||||
placement.config != null &&
|
||||
typeof placement.config === "object" &&
|
||||
!Array.isArray(placement.config)
|
||||
? {
|
||||
...(widget.defaultConfig as Record<string, unknown>),
|
||||
...(placement.config as Record<string, unknown>),
|
||||
}
|
||||
: widget.defaultConfig;
|
||||
|
||||
const parsed = widget.configSchema.safeParse(merged);
|
||||
return {
|
||||
...placement,
|
||||
config: parsed.success ? parsed.data : widget.defaultConfig,
|
||||
};
|
||||
}
|
||||
|
||||
+21
-1
@@ -7,7 +7,27 @@ import * as notesSchema from "@/modules/notes/schema";
|
||||
import * as gardenSchema from "@/modules/garden/schema";
|
||||
import * as journalSchema from "@/modules/journal/schema";
|
||||
|
||||
const client = postgres(process.env["DATABASE_URL"]!);
|
||||
type PostgresClient = ReturnType<typeof postgres>;
|
||||
|
||||
const globalForDb = globalThis as typeof globalThis & {
|
||||
__famappPostgres?: PostgresClient;
|
||||
};
|
||||
|
||||
function createClient(): PostgresClient {
|
||||
const url = process.env["DATABASE_URL"];
|
||||
if (!url) throw new Error("DATABASE_URL is not set");
|
||||
|
||||
return postgres(url, {
|
||||
max: process.env.NODE_ENV === "production" ? 10 : 1,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 10,
|
||||
});
|
||||
}
|
||||
|
||||
const client = globalForDb.__famappPostgres ?? createClient();
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
globalForDb.__famappPostgres = client;
|
||||
}
|
||||
|
||||
const schema = {
|
||||
...coreSchema,
|
||||
|
||||
Reference in New Issue
Block a user