timezone via cookies

This commit is contained in:
Linrador 2025-09-25 20:39:06 +02:00
parent 530425a82c
commit c03811e860
16 changed files with 80 additions and 109 deletions

View File

@ -1,6 +1,6 @@
'use client'
import { useEffect, useState, useCallback, useMemo } from 'react'
import { useEffect, useState, useCallback } from 'react'
import { useSession } from 'next-auth/react'
import { useRouter, usePathname } from '@/i18n/navigation'
import { useTranslations, useLocale } from 'next-intl'
@ -99,36 +99,19 @@ function dateKeyInTZ(date: Date | string, timeZone: string): string {
return `${p.year}-${pad(p.month)}-${pad(p.day)}`; // YYYY-MM-DD
}
function readCookieClient(name: string): string | undefined {
if (typeof document === 'undefined') return undefined
const m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'))
return m ? decodeURIComponent(m[1]) : undefined
}
function isValidIanaTzClient(tz?: string): tz is string {
if (!tz) return false
try { new Intl.DateTimeFormat('en-US', { timeZone: tz }).format(0); return true } catch { return false }
}
export default function CommunityMatchList({ matchType }: Props) {
const { data: session } = useSession()
const router = useRouter()
const pathname = usePathname()
const locale = useLocale()
const [userTZ, setUserTZ] = useState<string>(() => {
const fromCookie = readCookieClient('tz')
if (isValidIanaTzClient(fromCookie)) return fromCookie!
if (session?.user?.timeZone && isValidIanaTzClient(session.user.timeZone)) return session.user.timeZone
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'Europe/Berlin'
}
)
console.log(userTZ);
const weekdayFmt = useMemo(() =>
new Intl.DateTimeFormat(locale === 'de' ? 'de-DE' : 'en-GB', {
const userTZ = getUserTimeZone(session?.user?.timeZone);
const weekdayFmt = new Intl.DateTimeFormat(
locale === 'de' ? 'de-DE' : 'en-GB',
{
weekday: 'long',
timeZone: userTZ,
}),
[locale, userTZ])
}
);
const tMatches = useTranslations('matches')
const tMapvote = useTranslations('mapvote')
@ -162,24 +145,6 @@ export default function CommunityMatchList({ matchType }: Props) {
const [now, setNow] = useState(() => Date.now())
// Beim Mount & Tab-Fokus Cookie neu einlesen
useEffect(() => {
const apply = () => {
const fromCookie = readCookieClient('tz')
if (isValidIanaTzClient(fromCookie)) {
setUserTZ(prev => (prev === fromCookie ? prev : fromCookie!))
}
}
apply()
const onFocus = () => apply()
window.addEventListener('focus', onFocus)
document.addEventListener('visibilitychange', onFocus)
return () => {
window.removeEventListener('focus', onFocus)
document.removeEventListener('visibilitychange', onFocus)
}
}, [])
useEffect(() => {
const id = setInterval(() => setNow(Date.now()), 1000)
return () => clearInterval(id)
@ -639,4 +604,4 @@ function formatCountdown(ms: number) {
const s = totalSec % 60
const pad = (n:number)=>String(n).padStart(2,'0')
return `${h}:${pad(m)}:${pad(s)}`
}
}

View File

@ -35,11 +35,11 @@ exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.16.2
* Prisma Client JS version: 6.16.1
* Query Engine version: 1c57fdcd7e44b29b9313256c76699e91c3ac3c43
*/
Prisma.prismaVersion = {
client: "6.16.2",
client: "6.16.1",
engine: "1c57fdcd7e44b29b9313256c76699e91c3ac3c43"
}
@ -370,7 +370,7 @@ const config = {
"value": "prisma-client-js"
},
"output": {
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
"fromEnvVar": null
},
"config": {
@ -384,7 +384,7 @@ const config = {
}
],
"previewFeatures": [],
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
"isCustomOutput": true
},
"relativeEnvPaths": {
@ -392,12 +392,13 @@ const config = {
"schemaEnvPath": "../../../.env"
},
"relativePath": "../../../prisma",
"clientVersion": "6.16.2",
"clientVersion": "6.16.1",
"engineVersion": "1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"datasourceNames": [
"db"
],
"activeProvider": "postgresql",
"postinstall": false,
"inlineDatasources": {
"db": {
"url": {

View File

@ -20,11 +20,11 @@ exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.16.2
* Prisma Client JS version: 6.16.1
* Query Engine version: 1c57fdcd7e44b29b9313256c76699e91c3ac3c43
*/
Prisma.prismaVersion = {
client: "6.16.2",
client: "6.16.1",
engine: "1c57fdcd7e44b29b9313256c76699e91c3ac3c43"
}

View File

@ -460,7 +460,7 @@ export namespace Prisma {
export import Exact = $Public.Exact
/**
* Prisma Client JS version: 6.16.2
* Prisma Client JS version: 6.16.1
* Query Engine version: 1c57fdcd7e44b29b9313256c76699e91c3ac3c43
*/
export type PrismaVersion = {

View File

@ -35,11 +35,11 @@ exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.16.2
* Prisma Client JS version: 6.16.1
* Query Engine version: 1c57fdcd7e44b29b9313256c76699e91c3ac3c43
*/
Prisma.prismaVersion = {
client: "6.16.2",
client: "6.16.1",
engine: "1c57fdcd7e44b29b9313256c76699e91c3ac3c43"
}
@ -371,7 +371,7 @@ const config = {
"value": "prisma-client-js"
},
"output": {
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
"fromEnvVar": null
},
"config": {
@ -385,7 +385,7 @@ const config = {
}
],
"previewFeatures": [],
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
"isCustomOutput": true
},
"relativeEnvPaths": {
@ -393,12 +393,13 @@ const config = {
"schemaEnvPath": "../../../.env"
},
"relativePath": "../../../prisma",
"clientVersion": "6.16.2",
"clientVersion": "6.16.1",
"engineVersion": "1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"datasourceNames": [
"db"
],
"activeProvider": "postgresql",
"postinstall": false,
"inlineDatasources": {
"db": {
"url": {

View File

@ -151,7 +151,7 @@
},
"./*": "./*"
},
"version": "6.16.2",
"version": "6.16.1",
"sideEffects": false,
"imports": {
"#wasm-engine-loader": {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -54,6 +54,9 @@ model User {
pterodactylClientApiKey String?
timeZone String? // IANA-TZ, z.B. "Europe/Berlin"
// ✅ Datenschutz: darf eingeladen werden?
canBeInvited Boolean @default(true)
}
enum UserStatus {
@ -173,7 +176,7 @@ model MatchPlayer {
teamId String?
team Team? @relation(fields: [teamId], references: [id])
match Match @relation(fields: [matchId], references: [id])
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
user User @relation(fields: [steamId], references: [steamId])
stats PlayerStats?
@ -239,7 +242,7 @@ model RankHistory {
createdAt DateTime @default(now())
user User @relation("UserRankHistory", fields: [steamId], references: [steamId])
match Match? @relation("MatchRankHistory", fields: [matchId], references: [id])
match Match? @relation("MatchRankHistory", fields: [matchId], references: [id], onDelete: Cascade)
}
model Schedule {
@ -263,7 +266,7 @@ model Schedule {
confirmedBy User? @relation("ConfirmedSchedules", fields: [confirmedById], references: [steamId])
linkedMatchId String? @unique
linkedMatch Match? @relation(fields: [linkedMatchId], references: [id])
linkedMatch Match? @relation(fields: [linkedMatchId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@ -293,7 +296,7 @@ model DemoFile {
createdAt DateTime @default(now())
match Match @relation(fields: [matchId], references: [id])
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
user User @relation(fields: [steamId], references: [steamId])
}
@ -326,7 +329,7 @@ enum MapVoteAction {
model MapVote {
id String @id @default(uuid())
matchId String @unique
match Match @relation(fields: [matchId], references: [id])
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
bestOf Int @default(3)
mapPool String[]
@ -359,7 +362,7 @@ model MapVoteStep {
chosenBy String?
chooser User? @relation("VoteStepChooser", fields: [chosenBy], references: [steamId])
vote MapVote @relation(fields: [voteId], references: [id])
vote MapVote @relation(fields: [voteId], references: [id], onDelete: Cascade)
@@unique([voteId, order])
@@index([teamId])
@ -371,7 +374,7 @@ model MatchReady {
steamId String
acceptedAt DateTime @default(now())
match Match @relation("MatchReadyMatch", fields: [matchId], references: [id])
match Match @relation("MatchReadyMatch", fields: [matchId], references: [id], onDelete: Cascade)
user User @relation("MatchReadyUser", fields: [steamId], references: [steamId])
@@id([matchId, steamId])

View File

@ -35,11 +35,11 @@ exports.Prisma = Prisma
exports.$Enums = {}
/**
* Prisma Client JS version: 6.16.2
* Prisma Client JS version: 6.16.1
* Query Engine version: 1c57fdcd7e44b29b9313256c76699e91c3ac3c43
*/
Prisma.prismaVersion = {
client: "6.16.2",
client: "6.16.1",
engine: "1c57fdcd7e44b29b9313256c76699e91c3ac3c43"
}
@ -370,7 +370,7 @@ const config = {
"value": "prisma-client-js"
},
"output": {
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
"fromEnvVar": null
},
"config": {
@ -384,7 +384,7 @@ const config = {
}
],
"previewFeatures": [],
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
"isCustomOutput": true
},
"relativeEnvPaths": {
@ -392,12 +392,13 @@ const config = {
"schemaEnvPath": "../../../.env"
},
"relativePath": "../../../prisma",
"clientVersion": "6.16.2",
"clientVersion": "6.16.1",
"engineVersion": "1c57fdcd7e44b29b9313256c76699e91c3ac3c43",
"datasourceNames": [
"db"
],
"activeProvider": "postgresql",
"postinstall": false,
"inlineDatasources": {
"db": {
"url": {