diff --git a/nuxt.config.ts b/nuxt.config.ts
index f7de71c..69fc68d 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -6,6 +6,7 @@ export default defineNuxtConfig({
cookieSecretKey: "",
vapidPrivateKey: "",
public: {
+ defaultTimezone: "Europe/Oslo",
vapidPublicKey: "",
}
}
diff --git a/package.json b/package.json
index 503e65c..bd7dbc8 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
+ "luxon": "^3.5.0",
"nuxt": "^3.15.4",
"vue": "latest",
"vue-router": "latest",
@@ -23,6 +24,7 @@
]
},
"devDependencies": {
+ "@types/luxon": "^3.4.2",
"@types/web-push": "^3.6.4"
}
}
diff --git a/pages/account/settings.vue b/pages/account/settings.vue
index e1283d4..41dcd69 100644
--- a/pages/account/settings.vue
+++ b/pages/account/settings.vue
@@ -5,6 +5,15 @@
Name: {{ session?.account.name }}
Access: {{ session?.account.type }}
+
@@ -26,6 +35,22 @@ definePageMeta({
const { data: session } = useAccountSession();
const { refresh: sessionRefresh } = useAccountSession();
+const timezone = ref(session.value?.account.timezone ?? "");
+
+async function changeSettings() {
+ try {
+ await $fetch("/api/account", {
+ method: "patch",
+ body: {
+ timezone: timezone.value,
+ }
+ });
+ await sessionRefresh();
+ } catch (err: any) {
+ alert(err.data.message);
+ }
+}
+
async function deleteAccount() {
try {
await $fetch.raw("/api/account", {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 361b39c..2108bdd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ luxon:
+ specifier: ^3.5.0
+ version: 3.5.0
nuxt:
specifier: ^3.15.4
version: 3.15.4(@parcel/watcher@2.5.1)(@types/node@22.13.8)(db0@0.2.4)(ioredis@5.5.0)(magicast@0.3.5)(rollup@4.34.9)(terser@5.39.0)(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.8)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0))(yaml@2.7.0)
@@ -21,6 +24,9 @@ importers:
specifier: ^3.6.7
version: 3.6.7
devDependencies:
+ '@types/luxon':
+ specifier: ^3.4.2
+ version: 3.4.2
'@types/web-push':
specifier: ^3.6.4
version: 3.6.4
@@ -873,6 +879,9 @@ packages:
'@types/http-proxy@1.17.16':
resolution: {integrity: sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==}
+ '@types/luxon@3.4.2':
+ resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
+
'@types/node@22.13.8':
resolution: {integrity: sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ==}
@@ -1943,6 +1952,10 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ luxon@3.5.0:
+ resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==}
+ engines: {node: '>=12'}
+
magic-string-ast@0.7.0:
resolution: {integrity: sha512-686fgAHaJY7wLTFEq7nnKqeQrhqmXB19d1HnqT35Ci7BN6hbAYLZUezTQ062uUHM7ggZEQlqJ94Ftls+KDXU8Q==}
engines: {node: '>=16.14.0'}
@@ -4036,6 +4049,8 @@ snapshots:
dependencies:
'@types/node': 22.13.8
+ '@types/luxon@3.4.2': {}
+
'@types/node@22.13.8':
dependencies:
undici-types: 6.20.0
@@ -5210,6 +5225,8 @@ snapshots:
dependencies:
yallist: 3.1.1
+ luxon@3.5.0: {}
+
magic-string-ast@0.7.0:
dependencies:
magic-string: 0.30.17
diff --git a/server/api/account.patch.ts b/server/api/account.patch.ts
index 3b27cd8..9059d6f 100644
--- a/server/api/account.patch.ts
+++ b/server/api/account.patch.ts
@@ -1,12 +1,16 @@
import { Account } from "~/shared/types/account";
import { readAccounts, writeAccounts } from "~/server/database";
+import { DateTime } from "luxon";
export default defineEventHandler(async (event) => {
const session = await requireAccountSession(event);
- const body: Pick = await readBody(event);
+ const body: Pick = await readBody(event);
if (
- !(body.interestedIds instanceof Array)
- || !body.interestedIds.every(id => typeof id === "string")
+ body.interestedIds !== undefined
+ && !(
+ !(body.interestedIds instanceof Array)
+ || !body.interestedIds.every(id => typeof id === "string")
+ )
) {
throw createError({
status: 400,
@@ -14,16 +18,42 @@ export default defineEventHandler(async (event) => {
});
}
+ if (body.timezone !== undefined) {
+ if (typeof body.timezone !== "string") {
+ throw createError({
+ status: 400,
+ message: "Invalid timezone",
+ });
+ }
+ if (body.timezone.length) {
+ const zonedTime = DateTime.local().setZone(body.timezone);
+ if (!zonedTime.isValid) {
+ throw createError({
+ status: 400,
+ message: "Invalid timezone: " + zonedTime.invalidExplanation,
+ });
+ }
+ }
+ }
+
const accounts = await readAccounts();
const sessionAccount = accounts.find(account => account.id === session.accountId);
if (!sessionAccount) {
throw Error("Account does not exist");
}
- if (body.interestedIds.length) {
- sessionAccount.interestedIds = body.interestedIds;
- } else {
- delete sessionAccount.interestedIds;
+ if (body.interestedIds !== undefined) {
+ if (body.interestedIds.length) {
+ sessionAccount.interestedIds = body.interestedIds;
+ } else {
+ delete sessionAccount.interestedIds;
+ }
+ }
+ if (body.timezone !== undefined) {
+ if (body.timezone)
+ sessionAccount.timezone = body.timezone;
+ else
+ delete sessionAccount.timezone;
}
await writeAccounts(accounts);
diff --git a/shared/types/account.d.ts b/shared/types/account.d.ts
index 9a3780a..8662628 100644
--- a/shared/types/account.d.ts
+++ b/shared/types/account.d.ts
@@ -4,6 +4,7 @@ export interface Account {
/** Name of the account. Not present on anonymous accounts */
name?: string,
interestedIds?: string[],
+ timezone?: string,
}
export interface Subscription {