feat: add first day of week user setting (#1249)
* feat: add first day of week user setting * fix: add missing migrations * fix: format and test issues * fix: deepsource issue * refactor: rename first-day-of-week procedure
This commit is contained in:
@@ -259,6 +259,7 @@ describe("editProfile shoud update user", () => {
|
||||
homeBoardId: null,
|
||||
provider: "credentials",
|
||||
colorScheme: "auto",
|
||||
firstDayOfWeek: 1,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -299,6 +300,7 @@ describe("editProfile shoud update user", () => {
|
||||
homeBoardId: null,
|
||||
provider: "credentials",
|
||||
colorScheme: "auto",
|
||||
firstDayOfWeek: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -323,6 +325,7 @@ describe("delete should delete user", () => {
|
||||
homeBoardId: null,
|
||||
provider: "ldap" as const,
|
||||
colorScheme: "auto" as const,
|
||||
firstDayOfWeek: 1 as const,
|
||||
},
|
||||
{
|
||||
id: defaultOwnerId,
|
||||
@@ -334,6 +337,7 @@ describe("delete should delete user", () => {
|
||||
salt: null,
|
||||
homeBoardId: null,
|
||||
colorScheme: "auto" as const,
|
||||
firstDayOfWeek: 1 as const,
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
@@ -346,6 +350,7 @@ describe("delete should delete user", () => {
|
||||
homeBoardId: null,
|
||||
provider: "oidc" as const,
|
||||
colorScheme: "auto" as const,
|
||||
firstDayOfWeek: 1 as const,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -208,6 +208,7 @@ export const userRouter = createTRPCRouter({
|
||||
image: true,
|
||||
provider: true,
|
||||
homeBoardId: true,
|
||||
firstDayOfWeek: true,
|
||||
},
|
||||
where: eq(users.id, input.userId),
|
||||
});
|
||||
@@ -375,6 +376,53 @@ export const userRouter = createTRPCRouter({
|
||||
})
|
||||
.where(eq(users.id, ctx.session.user.id));
|
||||
}),
|
||||
getFirstDayOfWeekForUserOrDefault: publicProcedure.query(async ({ ctx }) => {
|
||||
if (!ctx.session?.user) {
|
||||
return 1 as const;
|
||||
}
|
||||
|
||||
const user = await ctx.db.query.users.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
firstDayOfWeek: true,
|
||||
},
|
||||
where: eq(users.id, ctx.session.user.id),
|
||||
});
|
||||
|
||||
return user?.firstDayOfWeek ?? (1 as const);
|
||||
}),
|
||||
changeFirstDayOfWeek: protectedProcedure
|
||||
.input(validation.user.firstDayOfWeek.and(validation.common.byId))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
// Only admins can change other users' passwords
|
||||
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "User not found",
|
||||
});
|
||||
}
|
||||
|
||||
const dbUser = await ctx.db.query.users.findFirst({
|
||||
columns: {
|
||||
id: true,
|
||||
},
|
||||
where: eq(users.id, input.id),
|
||||
});
|
||||
|
||||
if (!dbUser) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "User not found",
|
||||
});
|
||||
}
|
||||
|
||||
await ctx.db
|
||||
.update(users)
|
||||
.set({
|
||||
firstDayOfWeek: input.firstDayOfWeek,
|
||||
})
|
||||
.where(eq(users.id, ctx.session.user.id));
|
||||
}),
|
||||
});
|
||||
|
||||
const createUserAsync = async (db: Database, input: z.infer<typeof validation.user.create>) => {
|
||||
|
||||
1
packages/db/migrations/mysql/0010_melted_pestilence.sql
Normal file
1
packages/db/migrations/mysql/0010_melted_pestilence.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `user` ADD `firstDayOfWeek` tinyint DEFAULT 1 NOT NULL;
|
||||
1489
packages/db/migrations/mysql/meta/0010_snapshot.json
Normal file
1489
packages/db/migrations/mysql/meta/0010_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -71,6 +71,13 @@
|
||||
"when": 1728074730696,
|
||||
"tag": "0009_wakeful_tenebrous",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "5",
|
||||
"when": 1728142597094,
|
||||
"tag": "0010_melted_pestilence",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1
packages/db/migrations/sqlite/0010_gorgeous_stingray.sql
Normal file
1
packages/db/migrations/sqlite/0010_gorgeous_stingray.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `user` ADD `firstDayOfWeek` integer DEFAULT 1 NOT NULL;
|
||||
1422
packages/db/migrations/sqlite/meta/0010_snapshot.json
Normal file
1422
packages/db/migrations/sqlite/meta/0010_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -71,6 +71,13 @@
|
||||
"when": 1728074724956,
|
||||
"tag": "0009_stale_roulette",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "6",
|
||||
"when": 1728142590232,
|
||||
"tag": "0010_gorgeous_stingray",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { AdapterAccount } from "@auth/core/adapters";
|
||||
import type { DayOfWeek } from "@mantine/dates";
|
||||
import { relations } from "drizzle-orm";
|
||||
import type { AnyMySqlColumn } from "drizzle-orm/mysql-core";
|
||||
import { boolean, index, int, mysqlTable, primaryKey, text, timestamp, varchar } from "drizzle-orm/mysql-core";
|
||||
import { boolean, index, int, mysqlTable, primaryKey, text, timestamp, tinyint, varchar } from "drizzle-orm/mysql-core";
|
||||
|
||||
import type {
|
||||
BackgroundImageAttachment,
|
||||
@@ -43,6 +44,7 @@ export const users = mysqlTable("user", {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
colorScheme: varchar("colorScheme", { length: 5 }).$type<ColorScheme>().default("auto").notNull(),
|
||||
firstDayOfWeek: tinyint("firstDayOfWeek").$type<DayOfWeek>().default(1).notNull(), // Defaults to Monday
|
||||
});
|
||||
|
||||
export const accounts = mysqlTable(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AdapterAccount } from "@auth/core/adapters";
|
||||
import type { DayOfWeek } from "@mantine/dates";
|
||||
import type { InferSelectModel } from "drizzle-orm";
|
||||
import { relations } from "drizzle-orm";
|
||||
import type { AnySQLiteColumn } from "drizzle-orm/sqlite-core";
|
||||
@@ -44,6 +45,7 @@ export const users = sqliteTable("user", {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
colorScheme: text("colorScheme").$type<ColorScheme>().default("auto").notNull(),
|
||||
firstDayOfWeek: int("firstDayOfWeek").$type<DayOfWeek>().default(1).notNull(), // Defaults to Monday
|
||||
});
|
||||
|
||||
export const accounts = sqliteTable(
|
||||
|
||||
@@ -106,6 +106,16 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
changeFirstDayOfWeek: {
|
||||
notification: {
|
||||
success: {
|
||||
message: "First day of week changed successfully",
|
||||
},
|
||||
error: {
|
||||
message: "Unable to change first day of week",
|
||||
},
|
||||
},
|
||||
},
|
||||
manageAvatar: {
|
||||
changeImage: {
|
||||
label: "Change image",
|
||||
@@ -1692,6 +1702,7 @@ export default {
|
||||
item: {
|
||||
language: "Language & Region",
|
||||
board: "Home board",
|
||||
firstDayOfWeek: "First day of the week",
|
||||
},
|
||||
},
|
||||
security: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { DayOfWeek } from "@mantine/dates";
|
||||
import { z } from "zod";
|
||||
|
||||
import { colorSchemes } from "@homarr/definitions";
|
||||
@@ -103,6 +104,10 @@ const changeColorSchemeSchema = z.object({
|
||||
colorScheme: zodEnumFromArray(colorSchemes),
|
||||
});
|
||||
|
||||
const firstDayOfWeekSchema = z.object({
|
||||
firstDayOfWeek: z.custom<DayOfWeek>((value) => z.number().min(0).max(6).safeParse(value).success),
|
||||
});
|
||||
|
||||
export const userSchemas = {
|
||||
signIn: signInSchema,
|
||||
registration: registrationSchema,
|
||||
@@ -115,4 +120,5 @@ export const userSchemas = {
|
||||
changeHomeBoard: changeHomeBoardSchema,
|
||||
changePasswordApi: changePasswordApiSchema,
|
||||
changeColorScheme: changeColorSchemeSchema,
|
||||
firstDayOfWeek: firstDayOfWeekSchema,
|
||||
};
|
||||
|
||||
@@ -5,6 +5,8 @@ import { useParams } from "next/navigation";
|
||||
import { Calendar } from "@mantine/dates";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import { CalendarDay } from "./calender-day";
|
||||
import classes from "./component.module.css";
|
||||
@@ -13,6 +15,7 @@ export default function CalendarWidget({ isEditMode, serverData }: WidgetCompone
|
||||
const [month, setMonth] = useState(new Date());
|
||||
const params = useParams();
|
||||
const locale = params.locale as string;
|
||||
const [firstDayOfWeek] = clientApi.user.getFirstDayOfWeekForUserOrDefault.useSuspenseQuery();
|
||||
|
||||
return (
|
||||
<Calendar
|
||||
@@ -23,6 +26,7 @@ export default function CalendarWidget({ isEditMode, serverData }: WidgetCompone
|
||||
hideWeekdays={false}
|
||||
date={month}
|
||||
maxLevel="month"
|
||||
firstDayOfWeek={firstDayOfWeek}
|
||||
w="100%"
|
||||
h="100%"
|
||||
static={isEditMode}
|
||||
|
||||
Reference in New Issue
Block a user