Adds a "CSS Variables" panel to the Styling tab with a color picker and description for every color token in globals.css (base, sidebar, design token aliases, event colors, all-day chips), plus a "Custom CSS" textarea that loads after the default stylesheet and can override any rule. Overrides are stored per-user (customCssVars, customCss) and applied on the root container's inline style, so they take precedence over themes and per-field settings. v1.113.0
403 lines
18 KiB
TypeScript
403 lines
18 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { getServerSession } from 'next-auth';
|
|
import { authOptions } from '@/lib/auth';
|
|
import { prisma } from '@/lib/prisma';
|
|
import bcrypt from 'bcryptjs';
|
|
|
|
// Get user profile
|
|
export async function GET(request: NextRequest) {
|
|
const session = await getServerSession(authOptions);
|
|
if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
|
|
const user = await (prisma.user as any).findUnique({
|
|
where: { email: session.user.email },
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
email: true,
|
|
timezone: true,
|
|
autoRolling: true,
|
|
protectEventTimes: true,
|
|
showCalendarProviderIcon: true,
|
|
language: true,
|
|
dateFormat: true,
|
|
timeFormat: true,
|
|
startHour: true,
|
|
endHour: true,
|
|
showNextTask: true,
|
|
calendarEditMode: true,
|
|
focusTimerDuration: true,
|
|
focusBreakDuration: true,
|
|
showTimeGrid: true,
|
|
showSomeday: true,
|
|
showAllDayEvents: true,
|
|
showSchedule: true,
|
|
showTaskCheckboxes: true,
|
|
showProjectIcons: true,
|
|
showPriorityIcons: true,
|
|
priorityStyle: true,
|
|
cellDuration: true,
|
|
viewStyle: true,
|
|
viewDays: true,
|
|
fontSize: true,
|
|
goalFallbackType: true,
|
|
goalDefaultSentence: true,
|
|
goalFontFamily: true,
|
|
goalFontSize: true,
|
|
goalFontWeight: true,
|
|
goalScope: true,
|
|
dateLayout: true,
|
|
mobileDateLayout: true,
|
|
dateVerticalAlign: true,
|
|
headerDisplay: true,
|
|
headerCustomFormat: true,
|
|
headerCurrentDayFormat: true,
|
|
mobilePortraitHeaderDisplay: true,
|
|
mobileLandscapeHeaderDisplay: true,
|
|
headlineFont: true,
|
|
headlineFontSize: true,
|
|
headlineFontWeight: true,
|
|
dateFontFamily: true,
|
|
dateFontSize: true,
|
|
dateFontWeight: true,
|
|
timeTaskFontFamily: true,
|
|
timeTaskFontSize: true,
|
|
timeTaskFontWeight: true,
|
|
bodyFont: true,
|
|
taskFontFamily: true,
|
|
taskFontSize: true,
|
|
taskFontWeight: true,
|
|
eventFontFamily: true,
|
|
eventFontSize: true,
|
|
eventFontWeight: true,
|
|
fontWeight: true,
|
|
weekdayColor: true,
|
|
dateColor: true,
|
|
taskColor: true,
|
|
todayHighlightColor: true,
|
|
weekendColorSat: true,
|
|
weekendColorSun: true,
|
|
pastDayColor: true,
|
|
hourLabelFormat: true,
|
|
showSubHourSlots: true,
|
|
allDayPosition: true,
|
|
cwFontFamily: true,
|
|
cwFontSize: true,
|
|
cwFontWeight: true,
|
|
cwColor: true,
|
|
yearFontFamily: true,
|
|
yearFontSize: true,
|
|
yearFontWeight: true,
|
|
yearColor: true,
|
|
dayHeaderGap: true,
|
|
showCompletedTasks: true,
|
|
showLines: true,
|
|
startDayOffset: true,
|
|
weekdayFormat: true,
|
|
weekdayCase: true,
|
|
customWeekdayNames: true,
|
|
weekStartDay: true,
|
|
quoteSourceUrls: true,
|
|
quoteLanguages: true,
|
|
kanbanStages: true,
|
|
accountNumber: true,
|
|
lightTheme: true,
|
|
darkTheme: true,
|
|
notificationsEnabled: true,
|
|
mobileFontScale: true,
|
|
weatherEnabled: true,
|
|
weatherLat: true,
|
|
weatherLon: true,
|
|
weatherLocation: true,
|
|
weatherRecentCities: true,
|
|
viewSettings: true,
|
|
customCssVars: true,
|
|
customCss: true,
|
|
hasCompletedOnboarding: true,
|
|
createdAt: true
|
|
}
|
|
});
|
|
|
|
if (!user) {
|
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
|
}
|
|
|
|
return NextResponse.json({ user });
|
|
}
|
|
|
|
// Update user profile
|
|
export async function PATCH(request: NextRequest) {
|
|
const session = await getServerSession(authOptions);
|
|
|
|
if (!session || !session.user?.email) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
try {
|
|
const body = await request.json();
|
|
const {
|
|
name, timezone, password, autoRolling, protectEventTimes, showCalendarProviderIcon,
|
|
language, dateFormat, timeFormat, startHour, endHour,
|
|
showNextTask, calendarEditMode, focusTimerDuration, focusBreakDuration,
|
|
showTimeGrid, showSomeday, showAllDayEvents, showSchedule,
|
|
cellDuration, viewStyle, viewDays, fontSize,
|
|
headlineFont, headlineFontSize, headlineFontWeight,
|
|
dateFontFamily, dateFontSize, dateFontWeight,
|
|
timeTaskFontFamily, timeTaskFontSize, timeTaskFontWeight,
|
|
bodyFont, taskFontFamily, taskFontSize, taskFontWeight,
|
|
eventFontFamily, eventFontSize, eventFontWeight,
|
|
fontWeight, weekendColorSat, weekendColorSun,
|
|
weekdayColor, dateColor, taskColor, todayHighlightColor,
|
|
pastDayColor, goalFallbackType, goalDefaultSentence,
|
|
goalFontFamily, goalFontSize, goalFontWeight, goalScope,
|
|
dateLayout, mobileDateLayout, dateVerticalAlign, headerDisplay, headerCustomFormat, headerCurrentDayFormat, mobilePortraitHeaderDisplay, mobileLandscapeHeaderDisplay,
|
|
hourLabelFormat, showSubHourSlots, allDayPosition,
|
|
cwFontFamily, cwFontSize, cwFontWeight, cwColor,
|
|
yearFontFamily, yearFontSize, yearFontWeight, yearColor,
|
|
showTaskCheckboxes, showProjectIcons, showPriorityIcons, priorityStyle, dayHeaderGap,
|
|
showCompletedTasks, showLines, startDayOffset, weekdayFormat, weekdayCase, customWeekdayNames, weekStartDay, quoteSourceUrls, quoteLanguages,
|
|
kanbanStages, lightTheme, darkTheme, notificationsEnabled, mobileFontScale,
|
|
weatherEnabled, weatherLat, weatherLon, weatherLocation, weatherRecentCities, viewSettings,
|
|
customCssVars, customCss,
|
|
hasCompletedOnboarding
|
|
} = body;
|
|
|
|
const updateData: any = {
|
|
...(name !== undefined && { name }),
|
|
...(timezone !== undefined && { timezone }),
|
|
...(autoRolling !== undefined && { autoRolling }),
|
|
...(protectEventTimes !== undefined && { protectEventTimes }),
|
|
...(showCalendarProviderIcon !== undefined && { showCalendarProviderIcon }),
|
|
...(language !== undefined && { language }),
|
|
...(dateFormat !== undefined && { dateFormat }),
|
|
...(timeFormat !== undefined && { timeFormat }),
|
|
...(startHour !== undefined && !isNaN(startHour) && { startHour }),
|
|
...(endHour !== undefined && !isNaN(endHour) && { endHour }),
|
|
...(showNextTask !== undefined && { showNextTask }),
|
|
...(calendarEditMode !== undefined && { calendarEditMode }),
|
|
...(focusTimerDuration !== undefined && !isNaN(focusTimerDuration) && { focusTimerDuration }),
|
|
...(focusBreakDuration !== undefined && !isNaN(focusBreakDuration) && { focusBreakDuration }),
|
|
...(showTimeGrid !== undefined && { showTimeGrid }),
|
|
...(showSomeday !== undefined && { showSomeday }),
|
|
...(showAllDayEvents !== undefined && { showAllDayEvents }),
|
|
...(showSchedule !== undefined && { showSchedule }),
|
|
...(showTaskCheckboxes !== undefined && { showTaskCheckboxes }),
|
|
...(showProjectIcons !== undefined && { showProjectIcons }),
|
|
...(showPriorityIcons !== undefined && { showPriorityIcons }),
|
|
...(priorityStyle !== undefined && { priorityStyle }),
|
|
...(cellDuration !== undefined && !isNaN(cellDuration) && { cellDuration }),
|
|
...(viewStyle !== undefined && { viewStyle }),
|
|
...(viewDays !== undefined && !isNaN(viewDays) && { viewDays }),
|
|
...(fontSize !== undefined && { fontSize }),
|
|
...(headlineFont !== undefined && { headlineFont }),
|
|
...(headlineFontSize !== undefined && { headlineFontSize }),
|
|
...(headlineFontWeight !== undefined && { headlineFontWeight }),
|
|
...(dateFontFamily !== undefined && { dateFontFamily }),
|
|
...(dateFontSize !== undefined && { dateFontSize }),
|
|
...(dateFontWeight !== undefined && { dateFontWeight }),
|
|
...(timeTaskFontFamily !== undefined && { timeTaskFontFamily }),
|
|
...(timeTaskFontSize !== undefined && { timeTaskFontSize }),
|
|
...(timeTaskFontWeight !== undefined && { timeTaskFontWeight }),
|
|
...(bodyFont !== undefined && { bodyFont }),
|
|
...(taskFontFamily !== undefined && { taskFontFamily }),
|
|
...(taskFontSize !== undefined && { taskFontSize }),
|
|
...(taskFontWeight !== undefined && { taskFontWeight }),
|
|
...(eventFontFamily !== undefined && { eventFontFamily }),
|
|
...(eventFontSize !== undefined && { eventFontSize }),
|
|
...(eventFontWeight !== undefined && { eventFontWeight }),
|
|
...(fontWeight !== undefined && { fontWeight }),
|
|
...(weekdayColor !== undefined && { weekdayColor }),
|
|
...(dateColor !== undefined && { dateColor }),
|
|
...(taskColor !== undefined && { taskColor }),
|
|
...(todayHighlightColor !== undefined && { todayHighlightColor }),
|
|
...(weekendColorSat !== undefined && { weekendColorSat }),
|
|
...(weekendColorSun !== undefined && { weekendColorSun }),
|
|
...(pastDayColor !== undefined && { pastDayColor }),
|
|
...(goalFallbackType !== undefined && { goalFallbackType }),
|
|
...(goalDefaultSentence !== undefined && { goalDefaultSentence }),
|
|
...(goalFontFamily !== undefined && { goalFontFamily }),
|
|
...(goalFontSize !== undefined && { goalFontSize }),
|
|
...(goalFontWeight !== undefined && { goalFontWeight }),
|
|
...(goalScope !== undefined && { goalScope }),
|
|
...(dateLayout !== undefined && { dateLayout }),
|
|
...(mobileDateLayout !== undefined && { mobileDateLayout }),
|
|
...(dateVerticalAlign !== undefined && { dateVerticalAlign }),
|
|
...(headerDisplay !== undefined && { headerDisplay }),
|
|
...(headerCustomFormat !== undefined && { headerCustomFormat }),
|
|
...(headerCurrentDayFormat !== undefined && { headerCurrentDayFormat }),
|
|
...(mobilePortraitHeaderDisplay !== undefined && { mobilePortraitHeaderDisplay }),
|
|
...(mobileLandscapeHeaderDisplay !== undefined && { mobileLandscapeHeaderDisplay }),
|
|
...(hourLabelFormat !== undefined && { hourLabelFormat }),
|
|
...(showSubHourSlots !== undefined && { showSubHourSlots }),
|
|
...(allDayPosition !== undefined && { allDayPosition }),
|
|
...(cwFontFamily !== undefined && { cwFontFamily }),
|
|
...(cwFontSize !== undefined && { cwFontSize }),
|
|
...(cwFontWeight !== undefined && { cwFontWeight }),
|
|
...(cwColor !== undefined && { cwColor }),
|
|
...(yearFontFamily !== undefined && { yearFontFamily }),
|
|
...(yearFontSize !== undefined && { yearFontSize }),
|
|
...(yearFontWeight !== undefined && { yearFontWeight }),
|
|
...(yearColor !== undefined && { yearColor }),
|
|
...(dayHeaderGap !== undefined && { dayHeaderGap }),
|
|
...(showCompletedTasks !== undefined && { showCompletedTasks }),
|
|
...(showLines !== undefined && { showLines }),
|
|
...(startDayOffset !== undefined && { startDayOffset }),
|
|
...(weekdayFormat !== undefined && { weekdayFormat }),
|
|
...(weekdayCase !== undefined && { weekdayCase }),
|
|
...(customWeekdayNames !== undefined && { customWeekdayNames }),
|
|
...(weekStartDay !== undefined && { weekStartDay }),
|
|
...(quoteSourceUrls !== undefined && { quoteSourceUrls }),
|
|
...(quoteLanguages !== undefined && { quoteLanguages }),
|
|
...(kanbanStages !== undefined && { kanbanStages }),
|
|
...(lightTheme !== undefined && { lightTheme }),
|
|
...(darkTheme !== undefined && { darkTheme }),
|
|
...(notificationsEnabled !== undefined && { notificationsEnabled }),
|
|
...(mobileFontScale !== undefined && { mobileFontScale: parseFloat(mobileFontScale) || 1.0 }),
|
|
...(weatherEnabled !== undefined && { weatherEnabled }),
|
|
...(weatherLat !== undefined && { weatherLat: weatherLat !== null ? parseFloat(weatherLat) : null }),
|
|
...(weatherLon !== undefined && { weatherLon: weatherLon !== null ? parseFloat(weatherLon) : null }),
|
|
...(weatherLocation !== undefined && { weatherLocation }),
|
|
...(weatherRecentCities !== undefined && { weatherRecentCities }),
|
|
...(viewSettings !== undefined && { viewSettings }),
|
|
...(customCssVars !== undefined && { customCssVars }),
|
|
...(customCss !== undefined && { customCss }),
|
|
...(hasCompletedOnboarding !== undefined && { hasCompletedOnboarding }),
|
|
};
|
|
if (password && password.trim() !== "") {
|
|
updateData.passwordHash = await bcrypt.hash(password, 10);
|
|
}
|
|
|
|
const user = await (prisma.user as any).update({
|
|
where: { email: session.user.email },
|
|
data: updateData,
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
email: true,
|
|
timezone: true,
|
|
autoRolling: true,
|
|
protectEventTimes: true,
|
|
language: true,
|
|
dateFormat: true,
|
|
timeFormat: true,
|
|
startHour: true,
|
|
endHour: true,
|
|
showNextTask: true,
|
|
calendarEditMode: true,
|
|
focusTimerDuration: true,
|
|
focusBreakDuration: true,
|
|
showTimeGrid: true,
|
|
showSomeday: true,
|
|
showAllDayEvents: true,
|
|
showSchedule: true,
|
|
showTaskCheckboxes: true,
|
|
showProjectIcons: true,
|
|
showPriorityIcons: true,
|
|
priorityStyle: true,
|
|
cellDuration: true,
|
|
viewStyle: true,
|
|
viewDays: true,
|
|
fontSize: true,
|
|
headlineFont: true,
|
|
headlineFontSize: true,
|
|
headlineFontWeight: true,
|
|
dateFontFamily: true,
|
|
dateFontSize: true,
|
|
dateFontWeight: true,
|
|
timeTaskFontFamily: true,
|
|
timeTaskFontSize: true,
|
|
timeTaskFontWeight: true,
|
|
bodyFont: true,
|
|
taskFontFamily: true,
|
|
taskFontSize: true,
|
|
taskFontWeight: true,
|
|
eventFontFamily: true,
|
|
eventFontSize: true,
|
|
eventFontWeight: true,
|
|
fontWeight: true,
|
|
weekdayColor: true,
|
|
dateColor: true,
|
|
taskColor: true,
|
|
todayHighlightColor: true,
|
|
weekendColorSat: true,
|
|
weekendColorSun: true,
|
|
pastDayColor: true,
|
|
hourLabelFormat: true,
|
|
showSubHourSlots: true,
|
|
allDayPosition: true,
|
|
goalFallbackType: true,
|
|
goalDefaultSentence: true,
|
|
goalFontFamily: true,
|
|
goalFontSize: true,
|
|
goalFontWeight: true,
|
|
goalScope: true,
|
|
dateLayout: true,
|
|
mobileDateLayout: true,
|
|
dateVerticalAlign: true,
|
|
headerDisplay: true,
|
|
headerCustomFormat: true,
|
|
headerCurrentDayFormat: true,
|
|
mobilePortraitHeaderDisplay: true,
|
|
mobileLandscapeHeaderDisplay: true,
|
|
cwFontFamily: true,
|
|
cwFontSize: true,
|
|
cwFontWeight: true,
|
|
cwColor: true,
|
|
yearFontFamily: true,
|
|
yearFontSize: true,
|
|
yearFontWeight: true,
|
|
yearColor: true,
|
|
dayHeaderGap: true,
|
|
showCompletedTasks: true,
|
|
showLines: true,
|
|
startDayOffset: true,
|
|
weekdayFormat: true,
|
|
weekdayCase: true,
|
|
customWeekdayNames: true,
|
|
weekStartDay: true,
|
|
quoteSourceUrls: true,
|
|
quoteLanguages: true,
|
|
kanbanStages: true,
|
|
accountNumber: true,
|
|
lightTheme: true,
|
|
darkTheme: true,
|
|
notificationsEnabled: true,
|
|
mobileFontScale: true,
|
|
weatherEnabled: true,
|
|
weatherLat: true,
|
|
weatherLon: true,
|
|
weatherLocation: true,
|
|
weatherRecentCities: true,
|
|
viewSettings: true,
|
|
customCssVars: true,
|
|
customCss: true,
|
|
hasCompletedOnboarding: true,
|
|
}
|
|
});
|
|
|
|
return NextResponse.json({ success: true, user });
|
|
} catch (e) {
|
|
console.error('Error updating profile:', e);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to update profile', details: (e as Error).message },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// Delete account
|
|
export async function DELETE(request: NextRequest) {
|
|
const session = await getServerSession(authOptions);
|
|
if (!session?.user?.email) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
|
|
try {
|
|
await prisma.user.delete({
|
|
where: { email: session.user.email }
|
|
});
|
|
return NextResponse.json({ success: true });
|
|
} catch (error) {
|
|
console.error('Error deleting account:', error);
|
|
return NextResponse.json({ error: 'Failed to delete account' }, { status: 500 });
|
|
}
|
|
}
|