diff --git a/app/components/Settings/SettingsSheet.vue b/app/components/Settings/SettingsSheet.vue index 826c085..837194d 100644 --- a/app/components/Settings/SettingsSheet.vue +++ b/app/components/Settings/SettingsSheet.vue @@ -14,7 +14,7 @@ isOpen.value = false; } - const { settings } = useSettings(); + const { insertionPoint, design } = useSettings(); const { t, locale, locales, setLocale } = useI18n(); const supportedLocales = locales.value.map((l) => l.code); @@ -93,7 +93,7 @@

{{ t("design") }}

diff --git a/app/composables/useFilters.ts b/app/composables/useFilters.ts index 8061bb6..3036bac 100644 --- a/app/composables/useFilters.ts +++ b/app/composables/useFilters.ts @@ -39,41 +39,9 @@ export function useFilter() { }, }); - const tags = computed({ - get() { - return filter.value.tags; - }, - set(value) { - filter.value = { - ...filter.value, - tags: [...value], - }; - }, - }); - - const category = computed({ - get() { - return filter.value.category; - }, - set(value) { - filter.value = { - ...filter.value, - category: [...value], - }; - }, - }); - - const time = computed<"all" | "today" | "week">({ - get() { - return filter.value.time; - }, - set(value) { - filter.value = { - ...filter.value, - time: value, - }; - }, - }); + const tags = createBinding(filter, "tags"); + const category = createBinding(filter, "category"); + const time = createBinding(filter, "time"); const isFiltering = computed(() => { if (filter.value.time !== "all") return true; diff --git a/app/composables/useSettings.ts b/app/composables/useSettings.ts index cf91558..5b0d450 100644 --- a/app/composables/useSettings.ts +++ b/app/composables/useSettings.ts @@ -1,25 +1,45 @@ -type Settings = { - insertionPoint: "top" | "bottom"; - design: "classic" | "floating"; +import { z } from "zod"; + +const SettingsSchema = z.object({ + insertionPoint: z.enum(["top", "bottom"]).default("top"), + design: z.enum(["classic", "floating"]).default("classic"), +}); + +export type Settings = z.infer; + +const defaultSettings: Settings = { + insertionPoint: "top", + design: "classic", }; export function useSettings() { - const settings = useCookie("settings", { - maxAge: 60 * 60 * 24 * 30, // 1month + const cookie = useCookie("settings", { + maxAge: 60 * 60 * 24 * 30, // 1 month sameSite: "lax", refresh: true, - default: () => ({ - insertionPoint: "top", - design: "classic", - }), + default: () => defaultSettings, }); // refresh cookie - if (settings.value) { - settings.value = { - ...settings.value, + if (cookie.value) { + cookie.value = { + ...cookie.value, }; } - return { settings }; + const settings = computed({ + get() { + const result = SettingsSchema.safeParse(cookie.value); + return result.success ? result.data : defaultSettings; + }, + set(value) { + const result = SettingsSchema.safeParse(value); + cookie.value = result.success ? result.data : defaultSettings; + }, + }); + + const insertionPoint = createBinding(settings, "insertionPoint"); + const design = createBinding(settings, "design"); + + return { insertionPoint, design }; } diff --git a/app/pages/index.vue b/app/pages/index.vue index 181c0c6..646ad01 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -38,11 +38,11 @@ const { isMobile } = useDevice(); const isPwa = useIsPwa(); - const { settings } = useSettings(); + const { design } = useSettings(); useHead({ htmlAttrs: { - "data-design": computed(() => settings.value.design), + "data-design": computed(() => design.value), "data-pwa": computed(() => isPwa.value), }, }); diff --git a/app/stores/useTodoStore.ts b/app/stores/useTodoStore.ts index dca7dbf..98768e2 100644 --- a/app/stores/useTodoStore.ts +++ b/app/stores/useTodoStore.ts @@ -59,7 +59,7 @@ export const useTodoStore = defineStore("todos", { async addTodo(todoData: CreateTodoData) { const { showError } = useToast(); - const { settings } = useSettings(); + const { insertionPoint } = useSettings(); let todos = filterTodos(this.data, { category: todoData.category ? [todoData.category] : [], @@ -75,7 +75,7 @@ export const useTodoStore = defineStore("todos", { } const lastTodo = - todos[settings.value.insertionPoint === "top" ? 0 : todos.length - 1]; + todos[insertionPoint.value === "top" ? 0 : todos.length - 1]; const todo = { uuid: v4(), @@ -85,7 +85,7 @@ export const useTodoStore = defineStore("todos", { updateOrInsertAfterTodo( this.data, resolveTodo(todo), - settings.value.insertionPoint, + insertionPoint.value, lastTodo?.uuid, ); @@ -94,7 +94,7 @@ export const useTodoStore = defineStore("todos", { method: "POST", body: { data: todo, - position: settings.value.insertionPoint, + position: insertionPoint.value, previousId: lastTodo?.uuid, }, }).catch(async (err) => { diff --git a/app/utils/cookieBindings.ts b/app/utils/cookieBindings.ts new file mode 100644 index 0000000..8289053 --- /dev/null +++ b/app/utils/cookieBindings.ts @@ -0,0 +1,14 @@ +export function createBinding( + state: WritableComputedRef, + key: K, +) { + return computed({ + get: () => state.value[key], + set: (value) => { + state.value = { + ...state.value, + [key]: Array.isArray(value) ? [...value] : value, + }; + }, + }); +}