feat: add theme provider and control components for light/dark mode
- Implemented ThemeProvider to manage theme state and preferences. - Added ThemeControl component for users to switch between system, light, and dark themes. - Integrated localStorage for theme preference persistence. - Updated document styles based on theme changes. test: add tests for permanent deletion of habits and related records - Created tests to ensure permanent deletion of habits removes all dependent records. - Verified that deletion requires ownership, authentication, and checks for archived habits. - Added tests to confirm rollback behavior on deletion failures. test: add tests for habit palette and progress card functionality - Implemented tests for habit color progression and theme-based color shading. - Added tests for progress card calculations and calendar display logic. feat: define theme types and preferences - Introduced Theme and ThemePreference types for better type safety. - Created utility functions for resolving theme based on user preference and system settings. - Defined theme palettes for light and dark modes to ensure adequate contrast.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { and, asc, desc, eq, lte, sql } from 'drizzle-orm';
|
||||
import { and, asc, desc, eq, inArray, lte, sql } from 'drizzle-orm';
|
||||
import type { AppDatabase } from '../auth';
|
||||
import type { PublicUser } from '../shared/user';
|
||||
import { habits, habitRevisions, habitDays, taskOccurrences, progressEvents, habitCalendarSettings, combinedCharts, users } from '../db/schema';
|
||||
@@ -90,6 +90,25 @@ export class HabitService {
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
permanentlyDelete(id: string) {
|
||||
this.db.transaction(() => {
|
||||
if (!this.current(id).archived) throw new ApiError(409, 'Archive the habit before permanently deleting it');
|
||||
const days = this.db.select({ id: habitDays.id }).from(habitDays).where(eq(habitDays.habitId, id));
|
||||
this.db.delete(progressEvents).where(inArray(progressEvents.dayId, days)).run();
|
||||
this.db.delete(taskOccurrences).where(inArray(taskOccurrences.dayId, days)).run();
|
||||
this.db.delete(habitDays).where(eq(habitDays.habitId, id)).run();
|
||||
this.db.delete(habitRevisions).where(eq(habitRevisions.habitId, id)).run();
|
||||
this.db.delete(habitCalendarSettings).where(eq(habitCalendarSettings.habitId, id)).run();
|
||||
for (const chart of this.db.select().from(combinedCharts).where(eq(combinedCharts.userId, this.user.id)).all()) {
|
||||
if (!chart.habitIds.includes(id)) continue;
|
||||
const habitIds = chart.habitIds.filter(habitId => habitId !== id);
|
||||
if (habitIds.length) this.db.update(combinedCharts).set({ habitIds, updatedAt: this.now }).where(eq(combinedCharts.id, chart.id)).run();
|
||||
else this.db.delete(combinedCharts).where(eq(combinedCharts.id, chart.id)).run();
|
||||
}
|
||||
this.db.delete(habits).where(eq(habits.id, id)).run();
|
||||
});
|
||||
this.invalidate(id);
|
||||
}
|
||||
revise(id: string, config: HabitConfig) {
|
||||
this.invalidate(id);
|
||||
this.owned(id);
|
||||
|
||||
Reference in New Issue
Block a user