Add per account overridable timezone setting
To make it possible to render the timetable in the user's local time we need to know the timezone to render it in on the server. Otherwise there will be hydration errors and paint flashing as the client renders a different timezone. Add a server global default timezone that can be overriden on a per-account bases to prepare for timezone handling the timetable.
This commit is contained in:
parent
264c97b586
commit
c4a6f6b3f9
6 changed files with 83 additions and 7 deletions
|
@ -6,6 +6,7 @@ export default defineNuxtConfig({
|
|||
cookieSecretKey: "",
|
||||
vapidPrivateKey: "",
|
||||
public: {
|
||||
defaultTimezone: "Europe/Oslo",
|
||||
vapidPublicKey: "",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,15 @@
|
|||
Name: {{ session?.account.name }}
|
||||
</p>
|
||||
<p>Access: {{ session?.account.type }}</p>
|
||||
<form @submit.prevent="changeSettings">
|
||||
<label>
|
||||
Timezone
|
||||
<input type="text" v-model="timezone">
|
||||
</label>
|
||||
<button type="submit">
|
||||
Save
|
||||
</button>
|
||||
</form>
|
||||
<p>
|
||||
<PushNotification />
|
||||
</p>
|
||||
|
@ -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", {
|
||||
|
|
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
|
@ -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
|
||||
|
|
|
@ -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<Account, "interestedIds"> = await readBody(event);
|
||||
const body: Pick<Account, "interestedIds" | "timezone"> = 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);
|
||||
|
||||
|
|
1
shared/types/account.d.ts
vendored
1
shared/types/account.d.ts
vendored
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue