diff --git a/apps/cli/src/commands/reset.ts b/apps/cli/src/commands/reset.ts
deleted file mode 100644
index dd53757..0000000
--- a/apps/cli/src/commands/reset.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import {
- printError,
- printErrorMessageWithReason,
- printObject,
-} from "@/lib/output";
-import { getAPIClient } from "@/lib/trpc";
-import { Command } from "@commander-js/extra-typings";
-
-export const resetCmd = new Command()
- .name("reset")
- .description("Initializes the database with default data")
- .action(async () => {
- await getAPIClient()
- .users.create.mutate({
- email: "ryan@ryanpandya.com",
- name: "Ryan Pandya",
- password: "pleasework",
- confirmPassword: "pleasework",
- })
- .then(printObject)
- .catch(
- printError(
- `Unable to create user for Ryan`,
- ),
- );
- });
diff --git a/apps/cli/src/commands/settings.ts b/apps/cli/src/commands/settings.ts
new file mode 100644
index 0000000..3e2ca6c
--- /dev/null
+++ b/apps/cli/src/commands/settings.ts
@@ -0,0 +1,36 @@
+import { getGlobalOptions } from "@/lib/globals";
+import {
+ printError,
+ printErrorMessageWithReason,
+ printObject,
+ printSuccess,
+} from "@/lib/output";
+import { getAPIClient } from "@/lib/trpc";
+import { Command } from "@commander-js/extra-typings";
+import { getBorderCharacters, table } from "table";
+
+export const settingsCmd = new Command()
+ .name("settings")
+ .description("Manipulate user settings");
+
+settingsCmd
+ .command("timezone")
+ .description("get or update timezone")
+ .argument("[timezone]", "the timezone to set")
+ .action(async (timezone?) => {
+ const api = getAPIClient();
+
+ try {
+ if (timezone) {
+ const res = await api.users.changeTimezone.mutate({ newTimezone: timezone });
+ console.log(`Updated to ${res}.`)
+ return;
+ }
+ else {
+ const res = await api.users.getTimezone.query();
+ console.log(`Current timezone is ${res}.`);
+ }
+ } catch (error) {
+ printErrorMessageWithReason("Failed to do the thing", error as object);
+ }
+ });
diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts
index 97f036d..eda0caf 100644
--- a/apps/cli/src/index.ts
+++ b/apps/cli/src/index.ts
@@ -6,6 +6,7 @@ import { colorsCmd } from "@/commands/colors";
import { categoriesCmd } from "@/commands/categories";
import { daysCmd } from "@/commands/days";
import { hoursCmd } from "./commands/hours";
+import { settingsCmd } from "./commands/settings";
import { config } from "dotenv";
@@ -42,6 +43,7 @@ program.addCommand(daysCmd);
program.addCommand(colorsCmd);
program.addCommand(categoriesCmd);
program.addCommand(hoursCmd);
+program.addCommand(settingsCmd);
setGlobalOptions(program.opts());
diff --git a/apps/web/app/dashboard/day/[dateQuery]/page.tsx b/apps/web/app/dashboard/day/[dateQuery]/page.tsx
index ac653af..accb234 100644
--- a/apps/web/app/dashboard/day/[dateQuery]/page.tsx
+++ b/apps/web/app/dashboard/day/[dateQuery]/page.tsx
@@ -10,6 +10,7 @@ export default async function DayPage({
params: { dateQuery: string };
}) {
let day;
+
try {
day = await api.days.get({ dateQuery: params.dateQuery });
} catch (e) {
diff --git a/apps/web/app/settings/app/page.tsx b/apps/web/app/settings/app/page.tsx
new file mode 100644
index 0000000..27f0965
--- /dev/null
+++ b/apps/web/app/settings/app/page.tsx
@@ -0,0 +1,9 @@
+import AppSettings from "@/components/settings/AppSettings";
+
+export default async function AppSettingsPage() {
+ return (
+
+ );
+}
diff --git a/apps/web/components/dashboard/days/DayView.tsx b/apps/web/components/dashboard/days/DayView.tsx
index e975675..8d936ad 100644
--- a/apps/web/components/dashboard/days/DayView.tsx
+++ b/apps/web/components/dashboard/days/DayView.tsx
@@ -23,8 +23,6 @@ export default async function DayView({
redirect("/");
}
- // const entries = await api.entries.get({ day: day });
-
return (
@@ -32,9 +30,11 @@ export default async function DayView({
{format(day.date, "EEEE, MMMM do")}
-
+
+
+
@@ -45,7 +45,7 @@ export default async function DayView({
- {day.hours}
+
);
diff --git a/apps/web/components/dashboard/sidebar/Sidebar.tsx b/apps/web/components/dashboard/sidebar/Sidebar.tsx
index b2bdc4e..0133062 100644
--- a/apps/web/components/dashboard/sidebar/Sidebar.tsx
+++ b/apps/web/components/dashboard/sidebar/Sidebar.tsx
@@ -4,19 +4,19 @@ import { Separator } from "@/components/ui/separator";
import { api } from "@/server/api/client";
import { getServerAuthSession } from "@/server/auth";
import { Archive, Home, Search, Tag } from "lucide-react";
-
import serverConfig from "@lifetracker/shared/config";
import AllLists from "./AllLists";
+import TimezoneDisplay from "./TimezoneDisplay";
export default async function Sidebar() {
+
+
const session = await getServerAuthSession();
if (!session) {
redirect("/");
}
- const lists = await api.users.list();
-
const searchItem = serverConfig.meilisearch
? [
{
@@ -61,7 +61,7 @@ export default async function Sidebar() {
- Lifetracker v{serverConfig.serverVersion}
+
);
diff --git a/apps/web/components/dashboard/sidebar/TimezoneDisplay.tsx b/apps/web/components/dashboard/sidebar/TimezoneDisplay.tsx
new file mode 100644
index 0000000..2138a31
--- /dev/null
+++ b/apps/web/components/dashboard/sidebar/TimezoneDisplay.tsx
@@ -0,0 +1,57 @@
+'use client';
+import { useState, useEffect, use, } from "react";
+import Link from "next/link";
+import { format } from "date-fns";
+import { TZDate } from "@date-fns/tz";
+import { timezones } from '@/lib/timezones';
+import { useTimezone } from "@lifetracker/shared-react/hooks/timezones";
+import LoadingSpinner from "@/components/ui/spinner";
+import { db } from "@lifetracker/db";
+import { Button } from "@/components/ui/button";
+
+export default function TimezoneDisplay() {
+
+ const dbTimezone = useTimezone();
+ const [timezone, setTimezone] = useState(dbTimezone);
+
+ // Update timezone state when dbTimezone changes
+ useEffect(() => {
+ if (dbTimezone !== undefined) {
+ setTimezone(dbTimezone);
+ }
+ }, [dbTimezone]);
+
+ useEffect(() => {
+ const handleTzChange = (event) => {
+ setTimezone(event.detail.timezone);
+ };
+
+ window.addEventListener('timezoneUpdated', handleTzChange);
+
+ return () => {
+ window.removeEventListener('timezoneUpdated', handleTzChange);
+ };
+ }, []);
+
+
+
+ if (timezone === undefined) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+
+ {format(TZDate.tz(timezone), 'MMM dd, hh:mm aa')}
+
+ in
+
+ {timezones[timezone]}
+
+
+
+ );
+}
diff --git a/apps/web/components/settings/AppSettings.tsx b/apps/web/components/settings/AppSettings.tsx
new file mode 100644
index 0000000..947b01e
--- /dev/null
+++ b/apps/web/components/settings/AppSettings.tsx
@@ -0,0 +1,26 @@
+import { api } from "@/server/api/client";
+import ChangeTimezone from "@/components/settings/ChangeTimezone";
+
+export default async function AppSettings() {
+
+ const userTimezone: string = await api.users.getTimezone().then((res) => {
+ return res;
+ });
+
+ return (
+
+
+ Locale Settings
+
+
+
);
+}
diff --git a/apps/web/components/settings/ChangeTimezone.tsx b/apps/web/components/settings/ChangeTimezone.tsx
new file mode 100644
index 0000000..6b7f454
--- /dev/null
+++ b/apps/web/components/settings/ChangeTimezone.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { timezones } from '@/lib/timezones';
+import TimezoneSelect, { type ITimezone } from 'react-timezone-select'
+import { useUpdateUserTimezone } from "@lifetracker/shared-react/hooks/timezones";
+
+export default function ChangeTimezone({ userTimezone }) {
+ const [selectedTimezone, setSelectedTimezone] = useState(userTimezone);
+ const { mutate: updateUserTimezone, isPending } = useUpdateUserTimezone({
+ onSuccess: () => {
+ toast({
+ description: "User DB Timezone updated!",
+ });
+ },
+ });
+ return (
+
+ {
+ setSelectedTimezone(tz);
+ updateUserTimezone({ newTimezone: tz.value });
+ window.dispatchEvent(new CustomEvent('timezoneUpdated', { detail: { timezone: tz.value } }));
+ }}
+ timezones={{
+ ...timezones
+ }}
+ />
+ );
+}
\ No newline at end of file
diff --git a/apps/web/components/settings/sidebar/Sidebar.tsx b/apps/web/components/settings/sidebar/Sidebar.tsx
index 10150e2..16ed895 100644
--- a/apps/web/components/settings/sidebar/Sidebar.tsx
+++ b/apps/web/components/settings/sidebar/Sidebar.tsx
@@ -5,6 +5,7 @@ import { getServerAuthSession } from "@/server/auth";
import serverConfig from "@lifetracker/shared/config";
import { settingsSidebarItems } from "./items";
+import TimezoneDisplay from "@/components/dashboard/sidebar/TimezoneDisplay";
export default async function Sidebar() {
const session = await getServerAuthSession();
@@ -27,7 +28,8 @@ export default async function Sidebar() {
- Hoarder v{serverConfig.serverVersion}
+
+
);
diff --git a/apps/web/components/settings/sidebar/items.tsx b/apps/web/components/settings/sidebar/items.tsx
index 262775a..d1b10e2 100644
--- a/apps/web/components/settings/sidebar/items.tsx
+++ b/apps/web/components/settings/sidebar/items.tsx
@@ -5,6 +5,7 @@ import {
KeyRound,
User,
Palette,
+ Settings
} from "lucide-react";
export const settingsSidebarItems: {
@@ -22,6 +23,13 @@ export const settingsSidebarItems: {
icon: ,
path: "/settings/info",
},
+
+ {
+ name: "App Configuration",
+ icon: ,
+ path: "/settings/app",
+ },
+
{
name: "Color Settings",
icon: ,
diff --git a/apps/web/lib/timezones.ts b/apps/web/lib/timezones.ts
new file mode 100644
index 0000000..b26073f
--- /dev/null
+++ b/apps/web/lib/timezones.ts
@@ -0,0 +1,82 @@
+export const timezones = {
+ // "Pacific/Midway": "Midway Island, Samoa",
+ "Pacific/Honolulu": "Hawaii",
+ // "America/Juneau": "Alaska",
+ "America/Boise": "Mountain Time",
+ // "America/Dawson": "Dawson, Yukon",
+ // "America/Chihuahua": "Chihuahua, La Paz, Mazatlan",
+ // "America/Phoenix": "Arizona",
+ "America/Chicago": "Central Time",
+ // "America/Regina": "Saskatchewan",
+ "America/Mexico_City": "Mexico",
+ // "America/Belize": "Central America",
+ "America/New_York": "Eastern Time",
+ // "America/Bogota": "Bogota, Lima, Quito",
+ // "America/Caracas": "Caracas, La Paz",
+ // "America/Santiago": "Santiago",
+ // "America/St_Johns": "Newfoundland and Labrador",
+ // "America/Sao_Paulo": "Brasilia",
+ // "America/Tijuana": "Mexico",
+ // "America/Montevideo": "Montevideo",
+ "America/Argentina/Buenos_Aires": "Argentina",
+ // "America/Godthab": "Greenland",
+ "America/Los_Angeles": "Pacific Time",
+ // "Atlantic/Azores": "Azores",
+ // "Atlantic/Cape_Verde": "Cape Verde Islands",
+ // GMT: "UTC",
+ "Europe/London": "The UK",
+ "Europe/Dublin": "Ireland",
+ // "Europe/Lisbon": "Lisbon",
+ // "Africa/Casablanca": "Casablanca, Monrovia",
+ // "Atlantic/Canary": "Canary Islands",
+ // "Europe/Belgrade": "Belgrade, Bratislava, Budapest, Ljubljana, Prague",
+ // "Europe/Sarajevo": "Sarajevo, Skopje, Warsaw, Zagreb",
+ // "Europe/Brussels": "Brussels, Copenhagen, Madrid, Paris",
+ "Europe/Amsterdam": "Western Europe",
+ // "Africa/Algiers": "West Central Africa",
+ // "Europe/Bucharest": "Bucharest",
+ // "Africa/Cairo": "Cairo",
+ "Europe/Helsinki": "Eastern Europe",
+ // "Europe/Athens": "Athens",
+ // "Asia/Jerusalem": "Jerusalem",
+ // "Africa/Harare": "Harare, Pretoria",
+ // "Europe/Moscow": "Istanbul, Minsk, Moscow, St. Petersburg, Volgograd",
+ // "Asia/Kuwait": "Kuwait, Riyadh",
+ "Africa/Nairobi": "Kenya",
+ "Africa/Johannesburg": "South Africa",
+ // "Asia/Baghdad": "Baghdad",
+ // "Asia/Tehran": "Tehran",
+ // "Asia/Dubai": "Abu Dhabi, Muscat",
+ // "Asia/Baku": "Baku, Tbilisi, Yerevan",
+ // "Asia/Kabul": "Kabul",
+ // "Asia/Yekaterinburg": "Ekaterinburg",
+ // "Asia/Karachi": "Islamabad, Karachi, Tashkent",
+ "Asia/Kolkata": "India",
+ // "Asia/Kathmandu": "Kathmandu",
+ // "Asia/Dhaka": "Astana, Dhaka",
+ // "Asia/Colombo": "Sri Jayawardenepura",
+ // "Asia/Almaty": "Almaty, Novosibirsk",
+ // "Asia/Rangoon": "Yangon Rangoon",
+ // "Asia/Bangkok": "Bangkok, Hanoi, Jakarta",
+ // "Asia/Krasnoyarsk": "Krasnoyarsk",
+ // "Asia/Shanghai": "Beijing, Chongqing, Hong Kong SAR, Urumqi",
+ // "Asia/Kuala_Lumpur": "Kuala Lumpur, Singapore",
+ // "Asia/Taipei": "Taipei",
+ // "Australia/Perth": "Perth",
+ // "Asia/Irkutsk": "Irkutsk, Ulaanbaatar",
+ // "Asia/Seoul": "Seoul",
+ "Asia/Tokyo": "Japan",
+ // "Asia/Yakutsk": "Yakutsk",
+ // "Australia/Darwin": "Darwin",
+ // "Australia/Adelaide": "Adelaide",
+ // "Australia/Sydney": "Canberra, Melbourne, Sydney",
+ // "Australia/Brisbane": "Brisbane",
+ // "Australia/Hobart": "Hobart",
+ // "Asia/Vladivostok": "Vladivostok",
+ // "Pacific/Guam": "Guam, Port Moresby",
+ // "Asia/Magadan": "Magadan, Solomon Islands, New Caledonia",
+ // "Asia/Kamchatka": "Kamchatka, Marshall Islands",
+ // "Pacific/Fiji": "Fiji Islands",
+ // "Pacific/Auckland": "Auckland, Wellington",
+ // "Pacific/Tongatapu": "Nuku'alofa",
+};
\ No newline at end of file
diff --git a/apps/web/lib/userLocalSettings/timezone.tsx b/apps/web/lib/userLocalSettings/timezone.tsx
new file mode 100644
index 0000000..be8f84f
--- /dev/null
+++ b/apps/web/lib/userLocalSettings/timezone.tsx
@@ -0,0 +1,6 @@
+"use client";
+
+import type { z } from "zod";
+import { createContext, useContext } from "react";
+
+export const TimezoneCtx = createContext(undefined);
diff --git a/apps/web/package.json b/apps/web/package.json
index 8aa5280..fb26322 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -16,6 +16,7 @@
},
"dependencies": {
"@auth/drizzle-adapter": "^1.4.2",
+ "@date-fns/tz": "^1.2.0",
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
"@hookform/resolvers": "^3.3.4",
@@ -70,6 +71,7 @@
"react-masonry-css": "^1.0.16",
"react-select": "^5.8.0",
"react-syntax-highlighter": "^15.5.0",
+ "react-timezone-select": "^3.2.8",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.0",
"request-ip": "^3.3.0",
diff --git a/packages/db/migrations/0000_even_mysterio.sql b/packages/db/migrations/0000_exotic_dakota_north.sql
similarity index 97%
rename from packages/db/migrations/0000_even_mysterio.sql
rename to packages/db/migrations/0000_exotic_dakota_north.sql
index bdb439d..33821d6 100644
--- a/packages/db/migrations/0000_even_mysterio.sql
+++ b/packages/db/migrations/0000_exotic_dakota_north.sql
@@ -71,7 +71,7 @@ CREATE TABLE `hour` (
`dayId` text NOT NULL,
`categoryId` text,
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade,
- FOREIGN KEY (`dayId`) REFERENCES `day`(`id`) ON UPDATE no action ON DELETE no action,
+ FOREIGN KEY (`dayId`) REFERENCES `day`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`categoryId`) REFERENCES `category`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
@@ -89,7 +89,8 @@ CREATE TABLE `user` (
`emailVerified` integer,
`image` text,
`password` text,
- `role` text DEFAULT 'user'
+ `role` text DEFAULT 'user',
+ `timezone` text DEFAULT 'America/Los_Angeles' NOT NULL
);
--> statement-breakpoint
CREATE TABLE `verificationToken` (
diff --git a/packages/db/migrations/meta/0000_snapshot.json b/packages/db/migrations/meta/0000_snapshot.json
index 350f9a4..0b7fd3d 100644
--- a/packages/db/migrations/meta/0000_snapshot.json
+++ b/packages/db/migrations/meta/0000_snapshot.json
@@ -1,7 +1,7 @@
{
"version": "6",
"dialect": "sqlite",
- "id": "ac3ad6ee-ccd2-4f91-9be7-83bd5b1d9ec8",
+ "id": "170be9e2-c822-4d3a-a0b1-18c8468f1c5d",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"account": {
@@ -564,7 +564,7 @@
"columnsTo": [
"id"
],
- "onDelete": "no action",
+ "onDelete": "cascade",
"onUpdate": "no action"
},
"hour_categoryId_category_id_fk": {
@@ -680,6 +680,14 @@
"notNull": false,
"autoincrement": false,
"default": "'user'"
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'America/Los_Angeles'"
}
},
"indexes": {
diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json
index e09637e..b23047c 100644
--- a/packages/db/migrations/meta/_journal.json
+++ b/packages/db/migrations/meta/_journal.json
@@ -5,8 +5,8 @@
{
"idx": 0,
"version": "6",
- "when": 1732648521848,
- "tag": "0000_even_mysterio",
+ "when": 1732706352404,
+ "tag": "0000_exotic_dakota_north",
"breakpoints": true
}
]
diff --git a/packages/db/schema.ts b/packages/db/schema.ts
index c775588..dcb73b7 100644
--- a/packages/db/schema.ts
+++ b/packages/db/schema.ts
@@ -73,6 +73,7 @@ export const users = sqliteTable("user", {
image: text("image"),
password: text("password"),
role: text("role", { enum: ["admin", "user"] }).default("user"),
+ timezone: text("timezone").notNull().default("America/Los_Angeles"),
});
export const accounts = sqliteTable(
@@ -133,7 +134,6 @@ export const days = sqliteTable("day", {
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
-
});
export const hours = sqliteTable(
@@ -149,7 +149,7 @@ export const hours = sqliteTable(
.references(() => users.id, { onDelete: "cascade" }),
comment: text("comment"),
time: integer("time").unique(),
- dayId: text("dayId").notNull().references(() => days.id),
+ dayId: text("dayId").notNull().references(() => days.id, { onDelete: "cascade" }),
categoryId: text("categoryId").references(() => categories.id),
},
(e) => ({
diff --git a/packages/shared-react/hooks/timezones.ts b/packages/shared-react/hooks/timezones.ts
new file mode 100644
index 0000000..7bf9b56
--- /dev/null
+++ b/packages/shared-react/hooks/timezones.ts
@@ -0,0 +1,18 @@
+import { api } from "../trpc";
+
+export function useUpdateUserTimezone(
+ ...opts: Parameters
+) {
+ const apiUtils = api.useUtils();
+ return api.users.changeTimezone.useMutation({
+ ...opts[0],
+ onSuccess: (res, req, meta) => {
+ return opts[0]?.onSuccess?.(res, req, meta);
+ },
+ });
+}
+
+export function useTimezone() {
+ const res = api.users.getTimezone.useQuery().data;
+ return res;
+}
\ No newline at end of file
diff --git a/packages/shared/utils/days.ts b/packages/shared/utils/days.ts
index 0ce3c1f..b3a0a8c 100644
--- a/packages/shared/utils/days.ts
+++ b/packages/shared/utils/days.ts
@@ -1,13 +1,13 @@
import { format } from "date-fns";
import { TZDate } from "@date-fns/tz";
-export function dateFromInput(input: { dateQuery: string }) {
- let t: string;
+export function dateFromInput(input: { dateQuery: string, timezone: string }) {
+ let t: TZDate;
if (input.dateQuery == "today") {
- t = TZDate.tz("America/Los_Angeles");
+ t = TZDate.tz(input.timezone);
}
else {
- t = new TZDate(input.dateQuery, "Etc/UTC");
+ t = new TZDate(input.dateQuery, input.timezone);
}
return format(t, "yyyy-MM-dd") + "T00:00:00";
}
\ No newline at end of file
diff --git a/packages/tailwind-config/globals.css b/packages/tailwind-config/globals.css
index 1ba64bc..3b3664e 100644
--- a/packages/tailwind-config/globals.css
+++ b/packages/tailwind-config/globals.css
@@ -70,7 +70,18 @@
* {
@apply border-border;
}
+
body {
@apply bg-background text-foreground;
+
+
}
-}
+
+ nextjs-portal {
+ display: none;
+ }
+
+ .select-wrapper {
+ color: black !important;
+ }
+}
\ No newline at end of file
diff --git a/packages/trpc/index.ts b/packages/trpc/index.ts
index a19c922..35ae76f 100644
--- a/packages/trpc/index.ts
+++ b/packages/trpc/index.ts
@@ -10,6 +10,7 @@ interface User {
name?: string | null | undefined;
email?: string | null | undefined;
role: "admin" | "user" | null;
+ timezone: string;
}
export interface Context {
diff --git a/packages/trpc/routers/days.ts b/packages/trpc/routers/days.ts
index 4c10a5a..a9c5aca 100644
--- a/packages/trpc/routers/days.ts
+++ b/packages/trpc/routers/days.ts
@@ -10,6 +10,8 @@ import {
import type { Context } from "../index";
import { authedProcedure, router } from "../index";
import { dateFromInput } from "@lifetracker/shared/utils/days";
+import { format } from "date-fns";
+import { TZDate } from "@date-fns/tz";
async function createDay(date: string, ctx: Context) {
return await ctx.db.transaction(async (trx) => {
@@ -66,8 +68,8 @@ export const daysAppRouter = router({
}))
.output(zDaySchema)
.query(async ({ input, ctx }) => {
- const date = dateFromInput(input);
-
+ const date = dateFromInput({ timezone: ctx.user.timezone, ...input });
+ console.log(ctx.user.timezone);
// Fetch the day data
let dayRes;
dayRes = await ctx.db
@@ -123,10 +125,9 @@ export const daysAppRouter = router({
if (updatedProps.mood) {
updatedProps.mood = parseInt(updatedProps.mood);
}
- console.log(dateQuery, "::", dateFromInput({ dateQuery: dateQuery }));
await ctx.db
.update(days)
.set(updatedProps)
- .where(eq(days.date, dateFromInput({ dateQuery: dateQuery })));
+ .where(eq(days.date, dateFromInput({ dateQuery: dateQuery, timezone: ctx.user.timezone })));
}),
});
diff --git a/packages/trpc/routers/users.ts b/packages/trpc/routers/users.ts
index 55d2a81..9082cfa 100644
--- a/packages/trpc/routers/users.ts
+++ b/packages/trpc/routers/users.ts
@@ -147,12 +147,14 @@ export const usersAppRouter = router({
})
.where(eq(users.id, ctx.user.id));
}),
+
whoami: authedProcedure
.output(
z.object({
id: z.string(),
name: z.string().nullish(),
email: z.string().nullish(),
+ timezone: z.string().nullish(),
}),
)
.query(async ({ ctx }) => {
@@ -165,6 +167,39 @@ export const usersAppRouter = router({
if (!userDb) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
- return { id: ctx.user.id, name: ctx.user.name, email: ctx.user.email };
+ return {
+ id: ctx.user.id,
+ name: ctx.user.name,
+ email: ctx.user.email,
+ timezone: ctx.user.timezone,
+ };
+ }),
+ getTimezone: authedProcedure
+ .output(
+ z.string(),
+ )
+ .query(async ({ ctx }) => {
+ const res = await ctx.db.select({ timezone: users.timezone }).from(users).where(eq(users.id, ctx.user.id));
+
+ return res[0].timezone;
+ }),
+ changeTimezone: authedProcedure
+ .input(
+ z.object({
+ newTimezone: z.string(),
+ }),
+ )
+ .output(z.string())
+ .mutation(async ({ input, ctx }) => {
+ // invariant(ctx.user.timezone, "A user always has a timezone specified");
+ await ctx.db
+ .update(users)
+ .set({
+ timezone: input.newTimezone,
+ })
+ .where(eq(users.id, ctx.user.id));
+
+ console.log("changeTimezone input", input.newTimezone);
+ return input.newTimezone;
}),
});
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bf6ddd7..0aed72d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -145,6 +145,9 @@ importers:
'@auth/drizzle-adapter':
specifier: ^1.4.2
version: 1.7.3
+ '@date-fns/tz':
+ specifier: ^1.2.0
+ version: 1.2.0
'@emoji-mart/data':
specifier: ^1.1.2
version: 1.2.1
@@ -307,6 +310,9 @@ importers:
react-syntax-highlighter:
specifier: ^15.5.0
version: 15.6.1(react@18.3.1)
+ react-timezone-select:
+ specifier: ^3.2.8
+ version: 3.2.8(react-dom@18.3.1(react@18.3.1))(react-select@5.8.3(@types/react@18.2.61)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
remark-breaks:
specifier: ^4.0.0
version: 4.0.0
@@ -10092,6 +10098,13 @@ packages:
peerDependencies:
react: '>= 0.14.0'
+ react-timezone-select@3.2.8:
+ resolution: {integrity: sha512-efEIVmYAHtm+oS+YlE/9DbieMka1Lop0v1LsW1TdLq0yCBnnAzROKDUY09CICY8TCijZlo0fk+wHZZkV5NpVNw==}
+ peerDependencies:
+ react: ^16 || ^17.0.1 || ^18 || ^19.0.0-0
+ react-dom: ^16 || ^17.0.1 || ^18 || ^19.0.0-0
+ react-select: ^5.8.0
+
react-transition-group@4.4.5:
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies:
@@ -10708,6 +10721,9 @@ packages:
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+ spacetime@7.6.2:
+ resolution: {integrity: sha512-x5Qr2yiG5wy/0IBeaNeYG9OLigePKTj1/liF9j5xWNbb1psi5Q2AuZTh9SpIT+QZqpKCWX0OCdvwJIm9FlJouA==}
+
spdx-correct@3.2.0:
resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
@@ -11083,6 +11099,9 @@ packages:
thunky@1.1.0:
resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==}
+ timezone-soft@1.5.2:
+ resolution: {integrity: sha512-BUr+CfBfeWXJwFAuEzPO9uF+v6sy3pL5SKLkDg4vdEhsyXgbBnpFoBCW8oEKSNTqNq9YHbVOjNb31xE7WyGmrA==}
+
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
@@ -24844,6 +24863,14 @@ snapshots:
react: 18.3.1
refractor: 3.6.0
+ react-timezone-select@3.2.8(react-dom@18.3.1(react@18.3.1))(react-select@5.8.3(@types/react@18.2.61)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-select: 5.8.3(@types/react@18.2.61)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ spacetime: 7.6.2
+ timezone-soft: 1.5.2
+
react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.26.0
@@ -25645,6 +25672,8 @@ snapshots:
space-separated-tokens@2.0.2: {}
+ spacetime@7.6.2: {}
+
spdx-correct@3.2.0:
dependencies:
spdx-expression-parse: 3.0.1
@@ -26099,6 +26128,8 @@ snapshots:
thunky@1.1.0: {}
+ timezone-soft@1.5.2: {}
+
tiny-invariant@1.3.3: {}
tiny-queue@0.2.1: {}