Task #28: Let users pick a 12-hour (AM/PM) clock instead of 24-hour
Add a per-user 12/24-hour clock preference that complements the existing
clock-style preference and is honored by every clock variant on the home
screen.
Schema & API
- Added `clockHour12 boolean` (nullable) column to `users` table
(lib/db/src/schema/users.ts) and synced via direct `ALTER TABLE`
because `drizzle-kit push` was blocked by an unrelated interactive
rename prompt for the existing `app_opens` table.
- Extended OpenAPI `AuthUser` and `UserProfile` with `clockHour12`,
added `UpdateClockHour12Body` schema, and a new
`PATCH /auth/me/clock-hour12` endpoint. Regenerated zod + react-query
clients via `pnpm --filter @workspace/api-spec run codegen`.
- Implemented the new route handler in
`artifacts/api-server/src/routes/auth.ts`; `buildAuthUser` now
surfaces `clockHour12`.
Frontend
- `lib/i18n-format.ts` no longer hard-codes `hour12: false`; callers
may pass `hour12` in options. Default remains 24-hour to keep all
other timestamps unchanged (chat etc. left as-is — see follow-up).
- `components/clock.tsx` exports `resolveClockHour12` and threads a new
`hour12` prop through `Clock` and `AnalogClockWidget`. All five
variants (full/digital/digital-no-seconds/analog/minimal) plus the
large analog widget now honor the choice.
- `components/clock-style-picker.tsx` gained a 12-hour / 24-hour
segmented toggle that calls the new endpoint with optimistic cache
updates. The variant previews also reflect the active hour format.
- `pages/home.tsx` passes `user.clockHour12` to the header clock,
widget, and picker.
- Added `home.clockStyle.hourFormat.{label,h12,h24}` strings in EN and
AR. Arabic uses Latin digits and "ص/م" via Intl's localized
dayPeriod.
Verification
- `pnpm -w run typecheck` passes.
- E2E test: logged in, switched to 12-hour, verified AM/PM in header
and previews, reloaded to confirm persistence, switched back to
24-hour, reloaded again — all green.
Replit-Task-Id: 03ea8cbe-ace7-4d36-afc5-49ebc9706c67
This commit is contained in:
@@ -83,6 +83,14 @@ export interface UpdateClockStyleBody {
|
||||
clockStyle: ClockStyle | null;
|
||||
}
|
||||
|
||||
export interface UpdateClockHour12Body {
|
||||
/**
|
||||
* Whether to use 12-hour (AM/PM) clock format; null clears and uses the default (24-hour)
|
||||
* @nullable
|
||||
*/
|
||||
clockHour12: boolean | null;
|
||||
}
|
||||
|
||||
export interface AuthUser {
|
||||
id: number;
|
||||
username: string;
|
||||
@@ -94,6 +102,8 @@ export interface AuthUser {
|
||||
preferredLanguage: string;
|
||||
clockStyle?: ClockStyle | null;
|
||||
/** @nullable */
|
||||
clockHour12?: boolean | null;
|
||||
/** @nullable */
|
||||
avatarUrl?: string | null;
|
||||
isActive: boolean;
|
||||
roles: string[];
|
||||
@@ -111,6 +121,8 @@ export interface UserProfile {
|
||||
preferredLanguage: string;
|
||||
clockStyle?: ClockStyle | null;
|
||||
/** @nullable */
|
||||
clockHour12?: boolean | null;
|
||||
/** @nullable */
|
||||
avatarUrl?: string | null;
|
||||
isActive: boolean;
|
||||
roles: string[];
|
||||
|
||||
@@ -45,6 +45,7 @@ import type {
|
||||
SuccessResponse,
|
||||
UpdateAppBody,
|
||||
UpdateAppSettingsBody,
|
||||
UpdateClockHour12Body,
|
||||
UpdateClockStyleBody,
|
||||
UpdateLanguageBody,
|
||||
UpdateMyAppOrderBody,
|
||||
@@ -969,6 +970,92 @@ export const useUpdateClockStyle = <
|
||||
return useMutation(getUpdateClockStyleMutationOptions(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Update preferred 12/24-hour clock format
|
||||
*/
|
||||
export const getUpdateClockHour12Url = () => {
|
||||
return `/api/auth/me/clock-hour12`;
|
||||
};
|
||||
|
||||
export const updateClockHour12 = async (
|
||||
updateClockHour12Body: UpdateClockHour12Body,
|
||||
options?: RequestInit,
|
||||
): Promise<AuthUser> => {
|
||||
return customFetch<AuthUser>(getUpdateClockHour12Url(), {
|
||||
...options,
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json", ...options?.headers },
|
||||
body: JSON.stringify(updateClockHour12Body),
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateClockHour12MutationOptions = <
|
||||
TError = ErrorType<ErrorResponse>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateClockHour12>>,
|
||||
TError,
|
||||
{ data: BodyType<UpdateClockHour12Body> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateClockHour12>>,
|
||||
TError,
|
||||
{ data: BodyType<UpdateClockHour12Body> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ["updateClockHour12"];
|
||||
const { mutation: mutationOptions, request: requestOptions } = options
|
||||
? options.mutation &&
|
||||
"mutationKey" in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey }, request: undefined };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateClockHour12>>,
|
||||
{ data: BodyType<UpdateClockHour12Body> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return updateClockHour12(data, requestOptions);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateClockHour12MutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateClockHour12>>
|
||||
>;
|
||||
export type UpdateClockHour12MutationBody = BodyType<UpdateClockHour12Body>;
|
||||
export type UpdateClockHour12MutationError = ErrorType<ErrorResponse>;
|
||||
|
||||
/**
|
||||
* @summary Update preferred 12/24-hour clock format
|
||||
*/
|
||||
export const useUpdateClockHour12 = <
|
||||
TError = ErrorType<ErrorResponse>,
|
||||
TContext = unknown,
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateClockHour12>>,
|
||||
TError,
|
||||
{ data: BodyType<UpdateClockHour12Body> },
|
||||
TContext
|
||||
>;
|
||||
request?: SecondParameter<typeof customFetch>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateClockHour12>>,
|
||||
TError,
|
||||
{ data: BodyType<UpdateClockHour12Body> },
|
||||
TContext
|
||||
> => {
|
||||
return useMutation(getUpdateClockHour12MutationOptions(options));
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary List all active apps
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user