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' 'use client'
import { useEffect, useState, useCallback, useMemo } from 'react' import { useEffect, useState, useCallback } from 'react'
import { useSession } from 'next-auth/react' import { useSession } from 'next-auth/react'
import { useRouter, usePathname } from '@/i18n/navigation' import { useRouter, usePathname } from '@/i18n/navigation'
import { useTranslations, useLocale } from 'next-intl' 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 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) { export default function CommunityMatchList({ matchType }: Props) {
const { data: session } = useSession() const { data: session } = useSession()
const router = useRouter() const router = useRouter()
const pathname = usePathname() const pathname = usePathname()
const locale = useLocale() const locale = useLocale()
const [userTZ, setUserTZ] = useState<string>(() => { const userTZ = getUserTimeZone(session?.user?.timeZone);
const fromCookie = readCookieClient('tz') const weekdayFmt = new Intl.DateTimeFormat(
if (isValidIanaTzClient(fromCookie)) return fromCookie! locale === 'de' ? 'de-DE' : 'en-GB',
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', {
weekday: 'long', weekday: 'long',
timeZone: userTZ, timeZone: userTZ,
}), }
[locale, userTZ]) );
const tMatches = useTranslations('matches') const tMatches = useTranslations('matches')
const tMapvote = useTranslations('mapvote') const tMapvote = useTranslations('mapvote')
@ -162,24 +145,6 @@ export default function CommunityMatchList({ matchType }: Props) {
const [now, setNow] = useState(() => Date.now()) 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(() => { useEffect(() => {
const id = setInterval(() => setNow(Date.now()), 1000) const id = setInterval(() => setNow(Date.now()), 1000)
return () => clearInterval(id) return () => clearInterval(id)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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