-
Notifications
You must be signed in to change notification settings - Fork 8.4k
feat: increase support for multiple time zones #6839
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
WalkthroughIntroduces a comprehensive timezone selection feature enabling users to set and persist their preferred timezone across the application. Includes mock data, backend API routes, frontend UI components, a Pinia store, date utilities integration, and localization support. Changes
Sequence DiagramsequenceDiagram
actor User
participant UI as Timezone Button
participant Modal as Modal Dialog
participant Store as Timezone Store
participant API as Backend API
participant DayJS as DayJS
User->>UI: Click timezone button
UI->>Modal: Open modal
Modal->>Store: Get current timezone
Modal->>API: Fetch timezone options
API-->>Modal: Return options list
Modal->>User: Display radio options
User->>Modal: Select new timezone
Modal->>Modal: Update selection
User->>Modal: Confirm selection
Modal->>API: POST /timezone/setTimezone
API->>Store: setTimezone(selected)
Store->>DayJS: Update dayjs default
API-->>Modal: Success response
Modal->>UI: Close modal
Modal-->>User: Show success message
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 17
♻️ Duplicate comments (1)
apps/web-antd/src/api/core/user-profile.ts (1)
1-3: Same as web‑naive API: confirm TimezoneOption uses the same key ('timezone').See the verification script added in apps/web-naive/src/api/core/user-profile.ts.
Also applies to: 8-16, 21-23
🧹 Nitpick comments (14)
packages/@core/base/shared/src/utils/date.ts (1)
37-39: Consider simplifying the timezone setter.The ternary operator works correctly but could be more explicit for better readability.
export const setDefaultTimezone = (timezone?: string) => { - timezone ? dayjs.tz.setDefault(timezone) : dayjs.tz.setDefault(); + if (timezone) { + dayjs.tz.setDefault(timezone); + } else { + dayjs.tz.setDefault(); + } };apps/backend-mock/api/user/setTimezone.ts (1)
11-12: Consider adding input validation for the timezone parameter.The timezone value from the request body is used directly without validation. While this is a mock endpoint, adding basic validation (e.g., checking if timezone exists and is a non-empty string) would improve robustness and catch potential issues during development.
Example validation:
const { timezone } = await readBody(event); + if (!timezone || typeof timezone !== 'string') { + setResponseStatus(event, 400); + return useResponseError('Invalid timezone', 'Timezone is required and must be a string'); + } setTimezone(timezone);apps/web-ele/src/api/core/user-profile.ts (1)
14-16: Consider simplifying the return type.The return type
Promise<null | string | undefined>suggests three distinct states (null, undefined, string), which may be unnecessarily complex. Consider whetherPromise<string | null>orPromise<string | undefined>would suffice.apps/web-antd/src/store/user-profile.ts (1)
41-41: Consider deferring auto-initialization.Calling
initTimezone()during store creation is a side effect that executes immediately when the module loads. This could cause issues if:
- The API is not yet ready
- Authentication is not yet established
- Multiple components trigger store creation simultaneously
Consider one of these approaches:
- Let consuming components call
initTimezone()explicitly when appropriate- Use a lazy initialization pattern that triggers on first access
- Add a flag to track initialization state and prevent duplicate calls
apps/web-ele/src/store/user-profile.ts (2)
41-41: Consider deferring auto-initialization.Calling
initTimezone()during store creation creates a side effect that executes immediately when the module loads. This could cause issues if the API or authentication is not yet ready.Consider:
- Explicit initialization by consuming components
- Lazy initialization on first access
- Initialization state tracking to prevent duplicate calls
1-56: Consider extracting shared store logic.This store is nearly identical to
apps/web-antd/src/store/user-profile.ts. Consider extracting the common timezone management logic into a shared composable or utility to maintain consistency and reduce duplication across projects.apps/web-ele/src/layouts/basic.vue (3)
77-89: Add error feedback on save failure.Currently only finally resets loading. Surface failures to users.
const handleSetTimezone = async ( timezone: string, modalApi: ExtendedModalApi, ) => { try { modalApi.setState({ confirmLoading: true }); await userProfileStore.setTimezone(timezone); ElMessage.success($t('ui.widgets.timezone.setSuccess')); modalApi.close(); + } catch (err) { + ElMessage.error($t?.('common.error') || 'Failed to update timezone'); } finally { modalApi.setState({ confirmLoading: false }); } };
178-185: Minor: remove unused name attr; rely on unwrapped ref.name="out" is unused and leaks to DOM. Also, after fixing computedTimezone, this remains valid.
<TimezoneButton :ok-handler="handleSetTimezone" :timezone="computedTimezone" :timezone-options="timezoneOptions" - name="out" />
72-76: Defensive mapping for backend key drift (timezone vs timeZone).Until backend/type is unified, tolerate both keys to avoid undefined values.
- timezoneOptions.value = ((await getTimezoneOptionsApi()) || []).map( - (item) => item.timezone, - ); + const list = (await getTimezoneOptionsApi()) || []; + timezoneOptions.value = list.map((item: any) => item.timezone ?? item.timeZone).filter(Boolean);packages/effects/layouts/src/widgets/timezone/timezone-button.vue (1)
70-78: Minor: fix label a11y and style class.Bind label to control and align class name with CSS.
- <div + <div class="flex cursor-pointer items-center gap-2" v-for="item in props.timezoneOptions" :key="`container${item}`" @click="handleClickItem(item)" > - <RadioGroupItem :id="item" :value="item" /> - <label class="cursor-pointer">{{ item }}</label> + <RadioGroupItem :id="item" :value="item" /> + <label class="cursor-pointer" :for="item">{{ item }}</label> </div> @@ -<style scoped> -.container { - padding-left: 20px; -} -</style> +<style scoped> +.timezone-container { + padding-left: 20px; +} +</style>Also applies to: 85-89
apps/web-naive/src/layouts/basic.vue (2)
78-90: Add error feedback on failure.try { modalApi.setState({ confirmLoading: true }); await userProfileStore.setTimezone(timezone); message.success($t('ui.widgets.timezone.setSuccess')); modalApi.close(); + } catch (err) { + message.error($t?.('common.error') || 'Failed to update timezone'); } finally { modalApi.setState({ confirmLoading: false }); }
181-187: Minor: remove unused name attr.<TimezoneButton :ok-handler="handleSetTimezone" :timezone="computedTimezone" :timezone-options="timezoneOptions" - name="out" />apps/web-antd/src/layouts/basic.vue (2)
77-89: Add error feedback on failure.try { modalApi.setState({ confirmLoading: true }); await userProfileStore.setTimezone(timezone); message.success($t('ui.widgets.timezone.setSuccess')); modalApi.close(); + } catch (err) { + message.error($t?.('common.error') || 'Failed to update timezone'); } finally { modalApi.setState({ confirmLoading: false }); }
179-185: Minor: remove unused name attr.<TimezoneButton :ok-handler="handleSetTimezone" :timezone="computedTimezone" :timezone-options="timezoneOptions" - name="out" />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (32)
apps/backend-mock/api/profile/timezone.ts(1 hunks)apps/backend-mock/api/user/setTimezone.ts(1 hunks)apps/backend-mock/api/user/timezone.ts(1 hunks)apps/backend-mock/utils/mock-data.ts(2 hunks)apps/backend-mock/utils/timezone-utils.ts(1 hunks)apps/web-antd/src/api/core/index.ts(1 hunks)apps/web-antd/src/api/core/user-profile.ts(1 hunks)apps/web-antd/src/layouts/basic.vue(4 hunks)apps/web-antd/src/store/index.ts(1 hunks)apps/web-antd/src/store/user-profile.ts(1 hunks)apps/web-ele/src/api/core/index.ts(1 hunks)apps/web-ele/src/api/core/user-profile.ts(1 hunks)apps/web-ele/src/layouts/basic.vue(4 hunks)apps/web-ele/src/store/index.ts(1 hunks)apps/web-ele/src/store/user-profile.ts(1 hunks)apps/web-naive/src/api/core/index.ts(1 hunks)apps/web-naive/src/api/core/user-profile.ts(1 hunks)apps/web-naive/src/layouts/basic.vue(4 hunks)apps/web-naive/src/store/index.ts(1 hunks)apps/web-naive/src/store/user-profile.ts(1 hunks)packages/@core/base/shared/src/utils/date.ts(2 hunks)packages/@core/base/typings/src/index.ts(1 hunks)packages/@core/base/typings/src/user-profile.d.ts(1 hunks)packages/@core/preferences/src/config.ts(1 hunks)packages/@core/preferences/src/types.ts(1 hunks)packages/effects/layouts/src/basic/header/header.vue(1 hunks)packages/effects/layouts/src/basic/layout.vue(1 hunks)packages/effects/layouts/src/widgets/index.ts(1 hunks)packages/effects/layouts/src/widgets/timezone/index.ts(1 hunks)packages/effects/layouts/src/widgets/timezone/timezone-button.vue(1 hunks)packages/locales/src/langs/en-US/ui.json(1 hunks)packages/locales/src/langs/zh-CN/ui.json(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
apps/web-ele/src/api/core/user-profile.ts (3)
apps/web-antd/src/api/core/user-profile.ts (3)
getTimezoneOptionsApi(8-10)getUserTimezoneApi(14-16)setUserTimezoneApi(21-23)apps/web-naive/src/api/core/user-profile.ts (3)
getTimezoneOptionsApi(8-10)getUserTimezoneApi(14-16)setUserTimezoneApi(21-23)packages/@core/base/typings/src/user-profile.d.ts (1)
TimezoneOption(9-9)
apps/backend-mock/api/profile/timezone.ts (2)
apps/backend-mock/utils/response.ts (1)
useResponseSuccess(5-12)apps/backend-mock/utils/mock-data.ts (1)
TIME_ZONE_OPTIONS(400-421)
apps/backend-mock/api/user/setTimezone.ts (3)
apps/backend-mock/utils/jwt-utils.ts (1)
verifyAccessToken(29-58)apps/backend-mock/utils/response.ts (2)
unAuthorizedResponse(52-55)useResponseSuccess(5-12)apps/backend-mock/utils/timezone-utils.ts (1)
setTimezone(3-5)
apps/backend-mock/utils/timezone-utils.ts (1)
packages/@core/base/shared/src/utils/date.ts (1)
getTimezone(45-47)
apps/web-antd/src/store/user-profile.ts (3)
apps/backend-mock/utils/timezone-utils.ts (2)
getTimezone(7-10)setTimezone(3-5)packages/@core/base/shared/src/utils/date.ts (2)
getTimezone(45-47)setDefaultTimezone(37-39)apps/web-antd/src/api/core/user-profile.ts (2)
setUserTimezoneApi(21-23)getUserTimezoneApi(14-16)
apps/web-naive/src/api/core/user-profile.ts (3)
apps/web-antd/src/api/core/user-profile.ts (3)
getTimezoneOptionsApi(8-10)getUserTimezoneApi(14-16)setUserTimezoneApi(21-23)apps/web-ele/src/api/core/user-profile.ts (3)
getTimezoneOptionsApi(8-10)getUserTimezoneApi(14-16)setUserTimezoneApi(21-23)packages/@core/base/typings/src/user-profile.d.ts (1)
TimezoneOption(9-9)
apps/web-ele/src/store/user-profile.ts (2)
packages/@core/base/shared/src/utils/date.ts (2)
getTimezone(45-47)setDefaultTimezone(37-39)apps/web-ele/src/api/core/user-profile.ts (2)
setUserTimezoneApi(21-23)getUserTimezoneApi(14-16)
packages/@core/base/shared/src/utils/date.ts (1)
apps/backend-mock/utils/timezone-utils.ts (1)
getTimezone(7-10)
apps/backend-mock/api/user/timezone.ts (3)
apps/backend-mock/utils/jwt-utils.ts (1)
verifyAccessToken(29-58)apps/backend-mock/utils/response.ts (2)
unAuthorizedResponse(52-55)useResponseSuccess(5-12)apps/backend-mock/utils/timezone-utils.ts (1)
getTimezone(7-10)
apps/web-antd/src/api/core/user-profile.ts (3)
apps/web-ele/src/api/core/user-profile.ts (3)
getTimezoneOptionsApi(8-10)getUserTimezoneApi(14-16)setUserTimezoneApi(21-23)apps/web-naive/src/api/core/user-profile.ts (3)
getTimezoneOptionsApi(8-10)getUserTimezoneApi(14-16)setUserTimezoneApi(21-23)packages/@core/base/typings/src/user-profile.d.ts (1)
TimezoneOption(9-9)
apps/web-naive/src/store/user-profile.ts (3)
apps/backend-mock/utils/timezone-utils.ts (2)
getTimezone(7-10)setTimezone(3-5)packages/@core/base/shared/src/utils/date.ts (2)
getTimezone(45-47)setDefaultTimezone(37-39)apps/web-naive/src/api/core/user-profile.ts (2)
setUserTimezoneApi(21-23)getUserTimezoneApi(14-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Check (ubuntu-latest)
- GitHub Check: Lint (windows-latest)
- GitHub Check: Check (windows-latest)
- GitHub Check: post-update (ubuntu-latest)
- GitHub Check: post-update (windows-latest)
🔇 Additional comments (21)
packages/locales/src/langs/zh-CN/ui.json (1)
106-108: LGTM!The timezone localization strings are properly structured and translations appear appropriate for the Chinese locale.
packages/locales/src/langs/en-US/ui.json (1)
106-108: LGTM!The timezone localization strings are properly structured and consistent with the Chinese locale file.
packages/@core/preferences/src/types.ts (1)
278-279: LGTM!The timezone widget preference field is properly typed and follows the existing pattern for widget configuration flags.
packages/@core/base/shared/src/utils/date.ts (3)
2-6: LGTM!The dayjs timezone and UTC plugins are properly imported and extended in the correct order.
45-46: LGTM!The
getTimezonefunction correctly uses dayjs's built-in timezone guessing functionality.
14-14: Verify the semantic change in timezone handling is intentional.The
date.tz()method without arguments will use the default timezone if one is set, otherwise it falls back to the local timezone and does not throw an error. This means the code is safe and won't fail unexpectedly.However, changing from
date.format(format)todate.tz().format(format)shifts the function's behavior from timezone-unaware to timezone-aware (defaulting to local timezone). Confirm this semantic change aligns with the intended behavior offormatDate.apps/backend-mock/utils/timezone-utils.ts (1)
3-5: LGTM!The mock
setTimezonefunction is appropriately simple for backend testing purposes.apps/web-ele/src/api/core/index.ts (1)
4-4: LGTM!The user-profile module re-export follows the existing barrel export pattern.
apps/web-naive/src/store/index.ts (1)
2-2: LGTM!The user-profile store module re-export follows the existing barrel export pattern.
apps/web-naive/src/api/core/index.ts (1)
4-4: LGTM!The user-profile module re-export follows the existing barrel export pattern.
packages/effects/layouts/src/basic/layout.vue (1)
305-307: LGTM! Slot implementation follows existing patterns.The timezone slot is correctly implemented and follows the same pattern as the notification slot above it. This provides a clean extension point for timezone UI components.
packages/@core/preferences/src/config.ts (1)
137-137: LGTM! Widget configuration follows established pattern.The timezone widget flag is correctly added with a default value of
true, consistent with other widget preferences in the configuration object.apps/backend-mock/api/profile/timezone.ts (1)
5-6: LGTM! Appropriate implementation for a public options endpoint.This endpoint correctly returns the available timezone options without requiring authentication, which is appropriate for a resource that provides configuration options to all users.
packages/effects/layouts/src/widgets/timezone/index.ts (1)
1-1: LGTM!Standard barrel export pattern for the TimezoneButton component.
packages/@core/base/typings/src/user-profile.d.ts (1)
1-9: LGTM!Type definition is clear and appropriately structured for timezone options.
packages/effects/layouts/src/widgets/index.ts (1)
11-11: LGTM!Consistent barrel export pattern extending the public API to include timezone widgets.
packages/effects/layouts/src/basic/header/header.vue (1)
75-86: LGTM!Slot index adjustments are correct. Fullscreen moved to +50 and notification moved to +60 to accommodate the new timezone slot at +40.
apps/web-ele/src/api/core/user-profile.ts (2)
8-10: LGTM!API wrapper is correctly implemented with proper JSDoc documentation.
Ensure that the
requestClienthas appropriate error handling and retry logic for these timezone endpoints, especially forgetTimezoneOptionsApiwhich is critical for the timezone selection UI.
21-23: LGTM!Timezone persistence API is correctly implemented.
apps/web-antd/src/store/user-profile.ts (1)
10-12: LGTM!Fallback logic appropriately handles cases where
getTimezone()returns a falsy value by using the browser's default timezone.apps/backend-mock/api/user/timezone.ts (1)
6-12: LGTM!Mock endpoint correctly implements authentication check and returns timezone data. Implementation is clean and follows established patterns.
apps/web-antd/src/layouts/basic.vue
Outdated
| @@ -1,7 +1,8 @@ | |||
| <script lang="ts" setup> | |||
| import type { ExtendedModalApi } from '@vben/common-ui'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unify ExtendedModalApi source with widget (popup-ui).
-import type { ExtendedModalApi } from '@vben/common-ui';
+import type { ExtendedModalApi } from '@vben-core/popup-ui';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import type { ExtendedModalApi } from '@vben/common-ui'; | |
| import type { ExtendedModalApi } from '@vben-core/popup-ui'; |
🤖 Prompt for AI Agents
In apps/web-antd/src/layouts/basic.vue around line 2, the ExtendedModalApi type
is imported from '@vben/common-ui' but should be unified with the widget's
popup-ui; change the import source to the widget module (e.g.
'@vben/widget/popup-ui'), update the import statement accordingly, and ensure
TypeScript path/module resolution is correct so the new module compiles.
| async function setTimezone(timezone: string) { | ||
| timezoneRef.value = timezone; | ||
| // 设置dayjs默认时区 | ||
| setDefaultTimezone(timezone); | ||
| // 保存用户的时区设置 | ||
| await setUserTimezoneApi(timezone); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for timezone persistence.
The setTimezone function updates local state before confirming the server update succeeds. If setUserTimezoneApi fails, the local timezone will be out of sync with the server.
Consider this approach:
async function setTimezone(timezone: string) {
- timezoneRef.value = timezone;
- // 设置dayjs默认时区
- setDefaultTimezone(timezone);
- // 保存用户的时区设置
- await setUserTimezoneApi(timezone);
+ try {
+ // 保存用户的时区设置
+ await setUserTimezoneApi(timezone);
+ // 只有在服务器更新成功后才更新本地状态
+ timezoneRef.value = timezone;
+ // 设置dayjs默认时区
+ setDefaultTimezone(timezone);
+ } catch (error) {
+ console.error('Failed to set timezone:', error);
+ throw error; // Re-throw to allow caller to handle
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async function setTimezone(timezone: string) { | |
| timezoneRef.value = timezone; | |
| // 设置dayjs默认时区 | |
| setDefaultTimezone(timezone); | |
| // 保存用户的时区设置 | |
| await setUserTimezoneApi(timezone); | |
| } | |
| async function setTimezone(timezone: string) { | |
| try { | |
| // 保存用户的时区设置 | |
| await setUserTimezoneApi(timezone); | |
| // 只有在服务器更新成功后才更新本地状态 | |
| timezoneRef.value = timezone; | |
| // 设置dayjs默认时区 | |
| setDefaultTimezone(timezone); | |
| } catch (error) { | |
| console.error('Failed to set timezone:', error); | |
| throw error; // Re-throw to allow caller to handle | |
| } | |
| } |
🤖 Prompt for AI Agents
In apps/web-antd/src/store/user-profile.ts around lines 19–25, setTimezone
currently updates timezoneRef and calls setDefaultTimezone before awaiting
setUserTimezoneApi, which can leave local state inconsistent if the API fails;
change the flow to await the API call inside a try/catch and only update
timezoneRef and call setDefaultTimezone after a successful response, and in the
catch log the error (or show user feedback) and do not change local state (or if
you prefer optimistic update, perform the update first but revert it in the
catch and notify the user); ensure the function rethrows or returns a failure
indicator so callers can react.
| const timezoneRef = ref( | ||
| getTimezone() || new Intl.DateTimeFormat().resolvedOptions().timeZone, | ||
| ); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent init/save races and set default TZ immediately.
- initTimezone may overwrite a user’s selection if it resolves later.
- Default timezone isn’t set from the initial value until init or set.
- No rollback on failed save.
const timezoneRef = ref(
getTimezone() || new Intl.DateTimeFormat().resolvedOptions().timeZone,
);
+ // Ensure dayjs default timezone is set from initial value
+ setDefaultTimezone(timezoneRef.value);
@@
async function setTimezone(timezone: string) {
- timezoneRef.value = timezone;
- // 设置dayjs默认时区
- setDefaultTimezone(timezone);
- // 保存用户的时区设置
- await setUserTimezoneApi(timezone);
+ const prev = timezoneRef.value;
+ timezoneRef.value = timezone;
+ setDefaultTimezone(timezone);
+ try {
+ await setUserTimezoneApi(timezone);
+ } catch (err) {
+ // rollback on failure
+ timezoneRef.value = prev;
+ setDefaultTimezone(prev);
+ throw err;
+ }
}
@@
async function initTimezone() {
- // 从服务器获取用户时区
- const timezone = await getUserTimezoneApi();
- if (timezone) {
- timezoneRef.value = timezone;
- // 设置dayjs默认时区
- setDefaultTimezone(timezone);
- }
+ const snapshot = timezoneRef.value;
+ try {
+ const timezone = await getUserTimezoneApi();
+ if (timezone && timezoneRef.value === snapshot) {
+ timezoneRef.value = timezone;
+ setDefaultTimezone(timezone);
+ }
+ } catch {
+ // ignore init errors; keep local guess
+ }
}
@@
- initTimezone();
+ initTimezone();Also applies to: 21-25, 31-39, 41-41
🤖 Prompt for AI Agents
In apps/web-naive/src/store/user-profile.ts around lines 10-13 (and also
affecting 21-25, 31-39, 41), the timezone handling can race with async
init/save, doesn't apply the default TZ immediately, and lacks rollback on
failed saves; change the logic to: set the timezone ref synchronously to the
computed default immediately on creation, use a local incrementing
request/version token or abortable promise so any async initTimezone result is
ignored if a newer user selection happened, perform optimistic updates when
setTimezone is called (update ref/store and persist), and if the save/persist
call fails revert the ref/store to the prior value and surface the error; also
ensure initTimezone writes only when no user-selection/version supersedes it and
that all persistence calls return and handle errors for rollback.
packages/effects/layouts/src/widgets/timezone/timezone-button.vue
Outdated
Show resolved
Hide resolved
packages/effects/layouts/src/widgets/timezone/timezone-button.vue
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
apps/web-ele/src/store/user-profile.ts (2)
31-39: Add error handling for timezone initialization.Missing error handling could lead to unhandled promise rejections if the API call fails.
async function initTimezone() { - // 从服务器获取用户时区 - const timezone = await getUserTimezoneApi(); - if (timezone) { - timezoneRef.value = timezone; - // 设置dayjs默认时区 - setDefaultTimezone(timezone); - } + try { + // 从服务器获取用户时区 + const timezone = await getUserTimezoneApi(); + if (timezone) { + timezoneRef.value = timezone; + // 设置dayjs默认时区 + setDefaultTimezone(timezone); + } + } catch (error) { + console.error('Failed to initialize timezone:', error); + // Fallback to browser timezone already set in timezoneRef initialization + } }
19-25: Add error handling for timezone persistence.The function now correctly calls the API before updating local state, which is an improvement. However, error handling is still missing. If
setUserTimezoneApifails, the error will propagate silently without logging.Apply this diff to add error handling:
async function setTimezone(timezone: string) { - // 保存用户的时区设置 - await setUserTimezoneApi(timezone); - timezoneRef.value = timezone; - // 设置dayjs默认时区 - setDefaultTimezone(timezone); + try { + // 保存用户的时区设置 + await setUserTimezoneApi(timezone); + timezoneRef.value = timezone; + // 设置dayjs默认时区 + setDefaultTimezone(timezone); + } catch (error) { + console.error('Failed to set timezone:', error); + throw error; + } }packages/effects/layouts/src/widgets/timezone/timezone-button.vue (1)
45-51: Normalize timezoneValue to a plain string; avoid ref-of-ref and undefined.Initialize and sync as a string to prevent v-model edge cases and nested refs.
-const timezoneValue = ref<string | undefined>(unref(props.timezone)); +const timezoneValue = ref<string>(String(unref(props.timezone) ?? '')); watch( () => props.timezone, (newTimezone) => { - timezoneValue.value = unref(newTimezone); + timezoneValue.value = String(unref(newTimezone) ?? ''); }, );apps/web-antd/src/layouts/basic.vue (1)
2-2: Wrong source for ExtendedModalApi — switch to popup-ui.
@vben/common-uidoes not export ExtendedModalApi; use@vben-core/popup-uito match the widget and avoid TS errors.-import type { ExtendedModalApi } from '@vben/common-ui'; +import type { ExtendedModalApi } from '@vben-core/popup-ui';#!/bin/bash # Verify there are no remaining imports from @vben/common-ui for ExtendedModalApi rg -nP "import\s+type\s+\{\s*ExtendedModalApi\s*\}\s+from\s+'@vben/common-ui'" -g "apps/**/layouts/**.vue" -S
🧹 Nitpick comments (6)
packages/effects/layouts/src/widgets/timezone/timezone-button.vue (3)
69-79: Improve a11y: sanitize IDs and link label via :for.Timezone strings contain slashes; prefer safe IDs and associate label.
+const toId = (tz: string) => `tz-${tz.replace(/[^A-Za-z0-9_-]/g, '-')}`;(Place in <script setup>)
- <RadioGroupItem :id="item" :value="item" /> - <label class="cursor-pointer">{{ item }}</label> + <RadioGroupItem :id="toId(item)" :value="item" /> + <label class="cursor-pointer" :for="toId(item)">{{ item }}</label>
85-89: Fix style class mismatch.Template uses
.timezone-containerbut CSS targets.container. Align them.-<style scoped> -.container { - padding-left: 20px; -} -</style> +<style scoped> +.timezone-container { + padding-left: 20px; +} +</style>
52-55: Emit 'change' only if consumed (or document it).Parent in apps/web-antd/basic.vue doesn't listen to
change; either remove emit to avoid confusion or ensure parent registers@change.Do you intend consumers to react instantly on selection (before OK)? If yes, add
@changein parents; otherwise, drop the emit here.apps/web-antd/src/layouts/basic.vue (3)
71-76: Harden options mapping: filter invalids and dedupe.APIs may return null/duplicate entries; keep
string[]invariant.-onMounted(async () => { - timezoneOptions.value = ((await getTimezoneOptionsApi()) || []).map( - (item) => item.timezone, - ); -}); +onMounted(async () => { + const list = (await getTimezoneOptionsApi()) ?? []; + timezoneOptions.value = Array.from( + new Set( + list + .map((item) => item?.timezone) + .filter((tz): tz is string => typeof tz === 'string' && tz.length > 0), + ), + ); +});
77-92: Surface errors to users when setTimezone fails.Add an error toast in the catch to improve UX.
try { modalApi.setState({ confirmLoading: true }); await userProfileStore.setTimezone(timezone); message.success($t('ui.widgets.timezone.setSuccess')); modalApi.close(); +} catch (err) { + message.error(($t('ui.error.operationFailed') as string) || 'Failed to set timezone'); } finally { modalApi.setState({ confirmLoading: false }); }
181-188: Remove stray attribute on TimezoneButton.
name="out"isn’t a declared prop; it bubbles to DOM. Drop it unless required.- <TimezoneButton + <TimezoneButton :ok-handler="handleSetTimezone" :timezone="computedTimezone" :timezone-options="timezoneOptions" - name="out" />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (9)
apps/backend-mock/utils/timezone-utils.ts(1 hunks)apps/web-antd/src/layouts/basic.vue(4 hunks)apps/web-antd/src/store/user-profile.ts(1 hunks)apps/web-ele/src/layouts/basic.vue(4 hunks)apps/web-ele/src/store/user-profile.ts(1 hunks)apps/web-naive/src/layouts/basic.vue(4 hunks)apps/web-naive/src/store/user-profile.ts(1 hunks)packages/effects/layouts/src/widgets/timezone/timezone-button.vue(1 hunks)packages/effects/plugins/src/echarts/use-echarts.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- apps/web-naive/src/store/user-profile.ts
- apps/web-ele/src/layouts/basic.vue
- apps/web-antd/src/store/user-profile.ts
- apps/web-naive/src/layouts/basic.vue
🧰 Additional context used
🧬 Code graph analysis (2)
apps/backend-mock/utils/timezone-utils.ts (1)
packages/@core/base/shared/src/utils/date.ts (1)
getTimezone(45-47)
apps/web-ele/src/store/user-profile.ts (2)
packages/@core/base/shared/src/utils/date.ts (2)
getTimezone(45-47)setDefaultTimezone(37-39)apps/web-ele/src/api/core/user-profile.ts (2)
setUserTimezoneApi(21-23)getUserTimezoneApi(14-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Lint (windows-latest)
- GitHub Check: Test (windows-latest)
- GitHub Check: post-update (windows-latest)
- GitHub Check: post-update (ubuntu-latest)
🔇 Additional comments (5)
packages/effects/plugins/src/echarts/use-echarts.ts (1)
70-73: Trailing comma added—unrelated to timezone feature.The trailing comma is syntactically valid and follows modern formatting conventions. However, this change appears unrelated to the PR's stated objective of adding timezone support.
apps/backend-mock/utils/timezone-utils.ts (1)
1-9: LGTM! Clean mock implementation.The previous debug logging issue has been resolved. The implementation is clean and appropriate for a mock backend utility.
apps/web-ele/src/store/user-profile.ts (2)
1-12: LGTM: Solid initialization with fallback.The import structure and timezone initialization logic are well-designed. The fallback chain ensures a timezone is always available, even if
getTimezone()returns undefined.
43-56: LGTM: Proper store setup and HMR support.The store return statement correctly exposes the timezone state and actions. HMR support is properly configured for development experience.
apps/web-antd/src/layouts/basic.vue (1)
68-70: Nice fix: avoid nested Ref for timezone.Using
unref(userProfileStore.timezone)ensures a plain string for the child prop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/effects/layouts/src/widgets/timezone/timezone-button.vue (1)
69-74: Potential redundant state update from v-model and click handler.The
RadioGroupat line 69 is bound withv-model="timezoneValue", which automatically updatestimezoneValuewhen aRadioGroupItemis selected. The@click="handleClickItem(item)"handler at line 74 also manually updatestimezoneValue.value = timezone. This causes the value to be set twice for each selection.If you want the
changeevent to emit immediately on selection, keep the click handler but remove the manual assignment insidehandleClickItemsince v-model handles it. If you want changes to apply only on modal confirmation, consider removing the immediate click handler and emit logic.Apply this diff to remove the redundant manual assignment:
const handleClickItem = (timezone: string) => { - timezoneValue.value = timezone; emit('change', timezone); };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/effects/layouts/src/widgets/timezone/timezone-button.vue(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: post-update (ubuntu-latest)
- GitHub Check: post-update (windows-latest)
- GitHub Check: Lint (windows-latest)
- GitHub Check: Test (windows-latest)
packages/effects/layouts/src/widgets/timezone/timezone-button.vue
Outdated
Show resolved
Hide resolved
|
这个功能可以集成到框架层,用preferences控制开关, 处理逻辑可以放在内部处理 避免重复代码,参考一些别的功能组件 重新提交 |
这么做的原因是useUserProfileStore需要用到API,API只有apps里面才有,我改造一下吧,提供本地存储策略和服务端存储策略 |
在authStore扩展profile相关的内容 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
♻️ Duplicate comments (2)
packages/effects/layouts/src/basic/header/header.vue (1)
16-16: LGTM!The timezone slot is now correctly both registered in the
rightSlotscomputed property and rendered in the template. This resolves the issue flagged in the previous review where the slot was registered but never displayed.The implementation follows the same pattern as other widgets (language-toggle, theme-toggle) with consistent styling.
Also applies to: 70-75, 176-178
apps/backend-mock/utils/mock-data.ts (1)
10-13: Unify property name totimezoneto match usages and exports.Interface uses
timeZonebut data usestimezone. Standardize totimezoneto type‑check and match consumers (APIs/UI).Apply:
-export interface TimezoneOption { - offset: number; - timeZone: string; -} +export interface TimezoneOption { + offset: number; + timezone: string; +}TIME_ZONE_OPTIONS already uses
timezone, so no further data changes required.Also applies to: 400-421
🧹 Nitpick comments (3)
apps/backend-mock/api/timezone/setTimezone.ts (1)
6-14: Mock store is global; consider per-user scoping.Current write likely overrides a single in-memory value for all users. If feasible, key by username/userId to avoid cross-user interference, even in mocks.
packages/effects/layouts/src/widgets/timezone/timezone-button.vue (2)
28-47: Handle async errors and provide user feedback.Wrap API calls with try/catch and surface a toast/message; keep modal open on failure.
Apply:
onConfirm: async () => { - try { + try { modalApi.setState({ confirmLoading: true }); if (timezoneRef.value) { - await timezoneStore.setTimezone(unref(timezoneRef)); + await timezoneStore.setTimezone(unref(timezoneRef)); } modalApi.close(); } finally { modalApi.setState({ confirmLoading: false }); } }, async onOpenChange(isOpen) { if (isOpen) { - timezoneRef.value = unref(timezoneStore.timezone); - timezoneOptionsRef.value = await timezoneStore.getTimezoneOptions(); + try { + timezoneRef.value = unref(timezoneStore.timezone); + timezoneOptionsRef.value = await timezoneStore.getTimezoneOptions(); + } catch (e) { + console.error(e); + // TODO: inject and show a toast/error message here + } } },
71-74: Sanitize HTML ids for safety.Timezone strings contain slashes. While valid, escaping avoids selector quirks. Prefix and encode the id.
- <RadioGroupItem :id="item.value" :value="item.value" /> - <label :for="item.value" class="cursor-pointer">{{ item.label }}</label> + <RadioGroupItem :id="`tz-${encodeURIComponent(item.value)}`" :value="item.value" /> + <label :for="`tz-${encodeURIComponent(item.value)}`" class="cursor-pointer">{{ item.label }}</label>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
apps/backend-mock/api/timezone/getTimezone.ts(1 hunks)apps/backend-mock/api/timezone/getTimezoneOptions.ts(1 hunks)apps/backend-mock/api/timezone/setTimezone.ts(1 hunks)apps/backend-mock/utils/mock-data.ts(2 hunks)packages/@core/base/typings/src/app.d.ts(2 hunks)packages/@core/preferences/src/constants.ts(2 hunks)packages/effects/layouts/src/basic/header/header.vue(3 hunks)packages/effects/layouts/src/widgets/timezone/timezone-button.vue(1 hunks)packages/stores/src/modules/index.ts(1 hunks)packages/stores/src/modules/timezone.ts(1 hunks)playground/src/api/core/index.ts(1 hunks)playground/src/api/core/timezone.ts(1 hunks)playground/src/bootstrap.ts(2 hunks)playground/src/timezone-init.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
packages/@core/preferences/src/constants.ts (1)
apps/backend-mock/utils/mock-data.ts (1)
TimezoneOption(10-13)
playground/src/timezone-init.ts (2)
packages/stores/src/modules/timezone.ts (1)
setTimezoneHandler(119-119)playground/src/api/core/timezone.ts (3)
getTimezoneApi(21-23)setTimezoneApi(28-30)getTimezoneOptionsApi(8-17)
packages/stores/src/modules/timezone.ts (3)
packages/@core/preferences/src/constants.ts (1)
DEFAULT_TIME_ZONE_OPTIONS(117-117)playground/src/timezone-init.ts (4)
getTimezone(10-12)initTimezone(8-20)setTimezone(13-15)getTimezoneOptions(16-18)packages/@core/base/shared/src/utils/date.ts (1)
setDefaultTimezone(37-39)
apps/backend-mock/api/timezone/getTimezone.ts (2)
apps/backend-mock/utils/jwt-utils.ts (1)
verifyAccessToken(29-58)apps/backend-mock/utils/response.ts (2)
unAuthorizedResponse(52-55)useResponseSuccess(5-12)
apps/backend-mock/api/timezone/setTimezone.ts (3)
apps/backend-mock/utils/jwt-utils.ts (1)
verifyAccessToken(29-58)apps/backend-mock/utils/response.ts (2)
unAuthorizedResponse(52-55)useResponseSuccess(5-12)playground/src/timezone-init.ts (1)
setTimezone(13-15)
playground/src/api/core/timezone.ts (1)
apps/backend-mock/utils/mock-data.ts (1)
TimezoneOption(10-13)
packages/@core/base/typings/src/app.d.ts (1)
apps/backend-mock/utils/mock-data.ts (1)
TimezoneOption(10-13)
playground/src/bootstrap.ts (1)
playground/src/timezone-init.ts (1)
initTimezone(8-20)
apps/backend-mock/api/timezone/getTimezoneOptions.ts (2)
apps/backend-mock/utils/response.ts (1)
useResponseSuccess(5-12)apps/backend-mock/utils/mock-data.ts (1)
TIME_ZONE_OPTIONS(400-421)
apps/backend-mock/utils/mock-data.ts (1)
packages/@core/base/typings/src/app.d.ts (1)
TimezoneOption(120-120)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Test (windows-latest)
- GitHub Check: Lint (windows-latest)
- GitHub Check: Lint (ubuntu-latest)
- GitHub Check: post-update (windows-latest)
- GitHub Check: post-update (ubuntu-latest)
🔇 Additional comments (9)
playground/src/api/core/index.ts (1)
3-3: LGTM!The timezone module export follows the existing barrel export pattern consistently.
packages/stores/src/modules/index.ts (1)
3-3: LGTM!The timezone module export follows the existing pattern consistently.
playground/src/bootstrap.ts (1)
18-18: LGTM!The timezone initialization is correctly integrated into the bootstrap flow. The placement after store initialization and before directive registration ensures the timezone handler is available when needed.
Also applies to: 50-51
playground/src/timezone-init.ts (1)
8-20: LGTM!The timezone handler implementation uses a clean delegation pattern that decouples the store layer from API implementation details. The three handler methods correctly map to their corresponding API wrappers.
Note: Ensure the property name consistency issue flagged in
packages/@core/base/typings/src/app.d.tsis resolved, as it may affect the data flow through these API calls.packages/effects/layouts/src/basic/header/header.vue (1)
76-81: LGTM!The slot index adjustments correctly maintain sequential ordering to accommodate the new timezone widget between language-toggle and fullscreen.
Also applies to: 84-84
packages/@core/preferences/src/constants.ts (2)
1-1: LGTM!The import and export statements correctly integrate
TimezoneOptiontype andDEFAULT_TIME_ZONE_OPTIONSconstant into the preferences module.Also applies to: 117-117
84-113: No code changes needed—the codebase already uses timezone-aware date handling.The application properly uses dayjs with the timezone plugin (
dayjs.tz) for all time calculations, which automatically handles DST transitions. Theoffsetvalues inDEFAULT_TIME_ZONE_OPTIONSare informational labels only and are not used in calculation logic; thetimezonefield (e.g., 'America/New_York') is what dayjs uses, and this is DST-aware. The concern about manual offset calculations does not apply here.Likely an incorrect or invalid review comment.
apps/backend-mock/api/timezone/getTimezone.ts (1)
6-12: LGTM!The endpoint implementation follows the standard authentication pattern used throughout the mock API. Token verification and error handling are consistent with other protected endpoints.
playground/src/api/core/timezone.ts (1)
21-23: The return type is correct per the handler contract—no changes needed.The
Promise<null | string | undefined>return type ingetTimezoneApi()matches theTimezoneHandlerinterface contract defined inpackages/stores/src/modules/timezone.tsline 9, which explicitly requiresgetTimezone?: () => Promise<null | string | undefined>. The function implementation is correct and intentional.Likely an incorrect or invalid review comment.
| const { timezone } = await readBody(event); | ||
| setTimezone(timezone); | ||
| return useResponseSuccess({}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate input and return the saved value.
Guard against missing/invalid timezone and whitelist against supported options; return the applied timezone in the payload. This prevents bad writes and lets the client update state deterministically.
Apply:
-import { eventHandler, readBody } from 'h3';
+import { eventHandler, readBody, setResponseStatus } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
-import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
+import { unAuthorizedResponse, useResponseError, useResponseSuccess } from '~/utils/response';
+import { TIME_ZONE_OPTIONS } from '~/utils/mock-data';
@@
- const { timezone } = await readBody(event);
- setTimezone(timezone);
- return useResponseSuccess({});
+ const body = await readBody<{ timezone?: unknown }>(event);
+ const timezone = typeof body?.timezone === 'string' ? body.timezone : undefined;
+ const allowed = TIME_ZONE_OPTIONS.some((o) => o.timezone === timezone);
+ if (!timezone || !allowed) {
+ setResponseStatus(event, 400);
+ return useResponseError('Bad Request', 'Invalid timezone');
+ }
+ setTimezone(timezone);
+ return useResponseSuccess({ timezone });Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/backend-mock/api/timezone/setTimezone.ts around lines 11 to 13, the
handler reads a timezone and immediately persists it without validation or
returning the saved value; change it so you first validate that timezone exists
and is one of a whitelisted set of supported options (e.g., compare against a
supportedTimezones array or enum), return a 4xx error via your response helper
if missing/invalid, only call setTimezone after validation, and finally return
useResponseSuccess({ timezone: appliedTimezone }) where appliedTimezone is the
actual value saved.
| /** | ||
| * 时区选项 | ||
| */ | ||
| interface TimezoneOption { | ||
| label: string; | ||
| offset: number; | ||
| timezone: string; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Critical: Property name mismatch with backend mock definition.
The TimezoneOption interface defines the property as timezone (lowercase 'z'), but the backend mock definition in apps/backend-mock/utils/mock-data.ts uses timeZone (camelCase). This inconsistency will cause runtime errors when deserializing API responses, as the property won't be found.
Additionally, the backend mock definition is missing the label field entirely.
Ensure both definitions use the same property names. Apply this diff to align the property names:
interface TimezoneOption {
label: string;
offset: number;
- timezone: string;
+ timeZone: string;
}Or update the backend mock definition to match this interface (preferred if the backend can be changed consistently).
Run the following script to verify all usages of TimezoneOption across the codebase:
🏁 Script executed:
#!/bin/bash
# Description: Find all TimezoneOption interface definitions and usages to verify property name consistency
echo "=== TimezoneOption interface definitions ==="
ast-grep --pattern 'interface TimezoneOption {
$$$
}'
echo -e "\n=== Usage of timezone property ==="
rg -nP --type=ts --type=vue '\b(timezone|timeZone)\s*:' -C2
echo -e "\n=== TimezoneOption type annotations ==="
rg -nP --type=ts --type=vue 'TimezoneOption\[\]|: TimezoneOption' -C2Length of output: 719
I can see the script found two conflicting interface definitions. Let me search for how these interfaces are actually used in the codebase to verify if this causes runtime errors.
Let me search for how the backend mock is actually used and whether there's any data transformation layer:Now let me search for actual usages of the mock-data file to understand the impact:
Let me search for how these interfaces are actually used:
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Find imports and usages of backend-mock and TimezoneOption
echo "=== Imports of backend-mock utils ==="
rg -n 'from.*backend-mock|import.*backend-mock' --type=ts
echo -e "\n=== Usages of mock-data specifically ==="
rg -n 'mock-data|TimezoneOption' --type=ts -C2Length of output: 9191
Let me check the actual data transformation in the timezone API:
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Check the timezone API implementation and data transformation
echo "=== getTimezoneOptionsApi full implementation ==="
sed -n '8,20p' playground/src/api/core/timezone.ts
echo -e "\n=== TIME_ZONE_OPTIONS data sample ==="
sed -n '400,410p' apps/backend-mock/utils/mock-data.ts
echo -e "\n=== DEFAULT_TIME_ZONE_OPTIONS data sample ==="
sed -n '87,100p' packages/@core/preferences/src/constants.tsLength of output: 892
Let me verify the exact interface definition one more time to be completely certain:
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Get the exact backend mock interface definition and verify the property name
echo "=== Backend mock interface definition (exact lines) ==="
sed -n '10,13p' apps/backend-mock/utils/mock-data.ts
echo -e "\n=== Backend mock actual data (first item) ==="
sed -n '400,407p' apps/backend-mock/utils/mock-data.tsLength of output: 338
Fix the backend mock interface definition to match its actual data and frontend expectations.
The review comment correctly identifies a mismatch, but the analysis is incomplete and the suggested fix direction is wrong.
The actual issue:
- Backend mock interface defines
timeZone(camelCase), but the actualTIME_ZONE_OPTIONSdata usestimezone(lowercase) - Backend mock interface is missing the
labelfield - Frontend expects
timezone(lowercase) withlabel
Why the suggested fix is incorrect:
Changing the frontend from timezone to timeZone would break the code because:
- The actual backend data already uses
timezone(lowercase), nottimeZone - The frontend's
getTimezoneOptionsApi()function accessesitem.timezone, notitem.timeZone - This mismatch creates a TypeScript type safety violation that masks the real problem
What needs to be fixed:
Update apps/backend-mock/utils/mock-data.ts interface definition to match both the actual data and frontend expectations:
export interface TimezoneOption {
offset: number;
- timeZone: string;
+ timezone: string;
+ label: string;
}Then ensure the TIME_ZONE_OPTIONS data includes the label field for all entries.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
packages/@core/base/typings/src/app.d.ts around lines 96 to 103: the
TimezoneOption interface is incorrect for the real data and frontend—update the
interface to use lowercase "timezone" (not "timeZone") and include the missing
"label" field along with "offset" and "timezone"; then update
apps/backend-mock/utils/mock-data.ts to use that corrected interface and ensure
every entry in TIME_ZONE_OPTIONS includes a "label" property (matching the
frontend expectations) so types and runtime data align.
| interface TimezoneHandler { | ||
| getTimezone?: () => Promise<null | string | undefined>; | ||
| getTimezoneOptions?: () => Promise< | ||
| { | ||
| label: string; | ||
| value: string; | ||
| }[] | ||
| >; | ||
| onTimezoneChange?: (timezone: string) => Promise<void>; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handler contract mismatch and stale capture prevent server updates.
Store calls onTimezoneChange, but integrator provides setTimezone. Also, timezoneHandler is captured once; late setTimezoneHandler calls won't take effect. Use just‑in‑time handler retrieval and accept both names.
Apply:
interface TimezoneHandler {
getTimezone?: () => Promise<null | string | undefined>;
- getTimezoneOptions?: () => Promise<
+ getTimezoneOptions?: () => Promise<
{
label: string;
value: string;
}[]
>;
- onTimezoneChange?: (timezone: string) => Promise<void>;
+ // Preferred
+ setTimezone?: (timezone: string) => Promise<void>;
+ // Back-compat
+ onTimezoneChange?: (timezone: string) => Promise<void>;
}
@@
- const timezoneHandler = getTimezoneHandler();
-
@@
async function initTimezone() {
- const timezone = await timezoneHandler.getTimezone?.();
+ const handler = getTimezoneHandler();
+ const timezone = await handler.getTimezone?.();
if (timezone) {
timezoneRef.value = timezone;
}
// 设置dayjs默认时区
setDefaultTimezone(unref(timezoneRef));
}
@@
async function setTimezone(timezone: string) {
- await timezoneHandler.onTimezoneChange?.(timezone);
+ const handler = getTimezoneHandler();
+ // Prefer new name, fallback to old
+ if (handler.setTimezone) {
+ await handler.setTimezone(timezone);
+ } else {
+ await handler.onTimezoneChange?.(timezone);
+ }
timezoneRef.value = timezone;
// 设置dayjs默认时区
setDefaultTimezone(timezone);
}
@@
async function getTimezoneOptions() {
- return await timezoneHandler.getTimezoneOptions();
+ const handler = getTimezoneHandler();
+ const opts = await handler.getTimezoneOptions?.();
+ return (
+ opts ??
+ DEFAULT_TIME_ZONE_OPTIONS.map((item) => ({
+ label: item.label,
+ value: item.timezone,
+ }))
+ );
}This fixes remote persistence and honors late handler registration from app bootstrap.
Also applies to: 66-67, 72-79, 86-91, 97-99
🤖 Prompt for AI Agents
In packages/stores/src/modules/timezone.ts around lines 8-17 (and similarly at
66-67, 72-79, 86-91, 97-99), the handler interface and usage are mismatched and
the timezoneHandler is captured once so late calls to setTimezoneHandler are
ignored; update the interface to accept both onTimezoneChange and setTimezone
(both optional), and change all call sites to obtain the current handler lazily
(e.g., call a getCurrentHandler() or read the exported variable at invocation
time) so you invoke whichever function is provided (prefer onTimezoneChange but
fall back to setTimezone) and thus support late registration and remote
persistence.
| export async function getTimezoneOptionsApi() { | ||
| const dataList = | ||
| (await requestClient.get<TimezoneOption[]>( | ||
| '/timezone/getTimezoneOptions', | ||
| )) || []; | ||
| return dataList.map((item) => ({ | ||
| label: item.timezone, | ||
| value: item.timezone, | ||
| })); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Property name mismatch causes runtime error.
Line 14 references item.timezone (lowercase), but the TimezoneOption interface defines timeZone (camelCase). This will result in undefined values being mapped to label/value pairs.
Apply this diff to fix the property reference:
return dataList.map((item) => ({
- label: item.timezone,
- value: item.timezone,
+ label: item.timeZone,
+ value: item.timeZone,
}));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function getTimezoneOptionsApi() { | |
| const dataList = | |
| (await requestClient.get<TimezoneOption[]>( | |
| '/timezone/getTimezoneOptions', | |
| )) || []; | |
| return dataList.map((item) => ({ | |
| label: item.timezone, | |
| value: item.timezone, | |
| })); | |
| } | |
| export async function getTimezoneOptionsApi() { | |
| const dataList = | |
| (await requestClient.get<TimezoneOption[]>( | |
| '/timezone/getTimezoneOptions', | |
| )) || []; | |
| return dataList.map((item) => ({ | |
| label: item.timeZone, | |
| value: item.timeZone, | |
| })); | |
| } |
🤖 Prompt for AI Agents
In playground/src/api/core/timezone.ts around lines 8 to 17, the mapping uses
item.timezone (lowercase) but the TimezoneOption interface defines timeZone
(camelCase), causing undefined label/value pairs; update the mapping to
reference item.timeZone instead (and optionally provide a safe fallback like an
empty string) so label and value correctly come from item.timeZone.
playground/src/api/core/timezone.ts
Outdated
| export async function setTimezoneApi(timezone: string) { | ||
| return requestClient.post('/timezone/setTimezone', { timezone }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Improve return type specificity.
The return type Promise<any> bypasses TypeScript's type safety. If the response isn't needed, use Promise<void>. If it has a specific structure, define it explicitly.
Apply this diff if the response is not used:
-export async function setTimezoneApi(timezone: string) {
+export async function setTimezoneApi(timezone: string): Promise<void> {
return requestClient.post('/timezone/setTimezone', { timezone });
}Alternatively, if the response has a known structure, define and use that type instead.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function setTimezoneApi(timezone: string) { | |
| return requestClient.post('/timezone/setTimezone', { timezone }); | |
| } | |
| export async function setTimezoneApi(timezone: string): Promise<void> { | |
| return requestClient.post('/timezone/setTimezone', { timezone }); | |
| } |
🤖 Prompt for AI Agents
In playground/src/api/core/timezone.ts around lines 28-30, the function
setTimezoneApi currently returns Promise<any>, which weakens TypeScript safety;
if the HTTP response body is not used change the signature to return
Promise<void> and ensure the requestClient.post call is awaited or its result
ignored, otherwise define a specific response interface (e.g.
SetTimezoneResponse) that models the API shape and update the function signature
to return Promise<SetTimezoneResponse> and use that type on the
requestClient.post call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
apps/backend-mock/api/timezone/setTimezone.ts (2)
20-21: Return the saved timezone value in the response.As noted in the previous review, returning the applied timezone allows the client to update state deterministically and confirms the value was persisted correctly.
Apply this diff:
setTimezone(timezone); - return useResponseSuccess({}); + return useResponseSuccess({ timezone });
1-5: Critical: Missing required imports.The code references
setResponseStatus(line 17) anduseResponseError(line 18), but neither are imported. This will cause a runtime error.Apply this diff to add the missing imports:
-import { eventHandler, readBody } from 'h3'; +import { eventHandler, readBody, setResponseStatus } from 'h3'; import { verifyAccessToken } from '~/utils/jwt-utils'; import { TIME_ZONE_OPTIONS } from '~/utils/mock-data'; -import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; +import { unAuthorizedResponse, useResponseError, useResponseSuccess } from '~/utils/response'; import { setTimezone } from '~/utils/timezone-utils';
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/backend-mock/api/timezone/getTimezoneOptions.ts(1 hunks)apps/backend-mock/api/timezone/setTimezone.ts(1 hunks)apps/backend-mock/utils/mock-data.ts(2 hunks)packages/effects/layouts/src/widgets/timezone/timezone-button.vue(1 hunks)packages/stores/src/modules/timezone.ts(1 hunks)playground/src/api/core/timezone.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- apps/backend-mock/api/timezone/getTimezoneOptions.ts
- packages/stores/src/modules/timezone.ts
- apps/backend-mock/utils/mock-data.ts
- playground/src/api/core/timezone.ts
🧰 Additional context used
🧬 Code graph analysis (1)
apps/backend-mock/api/timezone/setTimezone.ts (3)
apps/backend-mock/utils/jwt-utils.ts (1)
verifyAccessToken(29-58)apps/backend-mock/utils/response.ts (3)
unAuthorizedResponse(52-55)useResponseError(35-42)useResponseSuccess(5-12)apps/backend-mock/utils/mock-data.ts (1)
TIME_ZONE_OPTIONS(400-421)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Check (windows-latest)
- GitHub Check: Test (windows-latest)
- GitHub Check: Lint (windows-latest)
- GitHub Check: post-update (ubuntu-latest)
- GitHub Check: post-update (windows-latest)
🔇 Additional comments (3)
apps/backend-mock/api/timezone/setTimezone.ts (3)
7-11: LGTM!The authentication flow correctly verifies the access token and returns a 401 Unauthorized response on failure.
12-14: LGTM!The body parsing is defensive and correctly uses type guards to safely extract the timezone string.
15-19: LGTM!The validation logic correctly implements the whitelist check against
TIME_ZONE_OPTIONSand returns a 400 Bad Request for invalid input, as requested in the previous review.
| async onOpenChange(isOpen) { | ||
| if (isOpen) { | ||
| timezoneRef.value = unref(timezoneStore.timezone); | ||
| timezoneOptionsRef.value = await timezoneStore.getTimezoneOptions(); | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for timezone options fetch.
The getTimezoneOptions() call is not wrapped in a try-catch block. If the fetch fails, the modal will open but remain empty or broken, with no user feedback.
Apply this diff to add error handling:
async onOpenChange(isOpen) {
if (isOpen) {
- timezoneRef.value = unref(timezoneStore.timezone);
- timezoneOptionsRef.value = await timezoneStore.getTimezoneOptions();
+ try {
+ timezoneRef.value = unref(timezoneStore.timezone);
+ timezoneOptionsRef.value = await timezoneStore.getTimezoneOptions();
+ } catch (error) {
+ console.error('Failed to load timezone options:', error);
+ modalApi.close();
+ }
}
},🤖 Prompt for AI Agents
In packages/effects/layouts/src/widgets/timezone/timezone-button.vue around
lines 42 to 47, wrap the await timezoneStore.getTimezoneOptions() call in a
try-catch so a failed fetch does not leave the modal empty or broken; on success
assign the options to timezoneOptionsRef, on error set timezoneOptionsRef to a
safe fallback (e.g., empty array), log the error (or report it to the app
logger) and surface user feedback (e.g., show a toast/notification or set an
error state visible in the modal) so the user knows the options failed to load
and the modal remains usable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
packages/effects/layouts/src/widgets/timezone/timezone-button.vue (1)
42-47: Add error handling for getTimezoneOptions() call.The
await timezoneStore.getTimezoneOptions()call at line 45 is not wrapped in try-catch. If the fetch fails, the modal opens with empty options and no user feedback. A previous review flagged this issue and it was marked as addressed, but the error handling is not present in the current code.Apply this diff to add error handling:
async onOpenChange(isOpen) { if (isOpen) { - timezoneRef.value = unref(timezoneStore.timezone); - timezoneOptionsRef.value = await timezoneStore.getTimezoneOptions(); + try { + timezoneRef.value = unref(timezoneStore.timezone); + timezoneOptionsRef.value = await timezoneStore.getTimezoneOptions(); + } catch (error) { + console.error('Failed to load timezone options:', error); + timezoneOptionsRef.value = []; + // Optionally close modal or show error state + } } },
🧹 Nitpick comments (1)
packages/effects/layouts/src/widgets/timezone/timezone-button.vue (1)
30-41: Consider adding user feedback for setTimezone errors.If
setTimezone()fails, the loading state is correctly reset and the modal remains open, but the user receives no feedback about what went wrong. Consider catching the error and displaying a notification or toast.Example enhancement:
onConfirm: async () => { try { modalApi.setState({ confirmLoading: true }); const timezone = unref(timezoneRef); if (timezone) { await timezoneStore.setTimezone(timezone); } modalApi.close(); } catch (error) { + console.error('Failed to save timezone:', error); + // Show user notification, e.g.: + // notification.error({ message: 'Failed to update timezone' }); + throw error; // Re-throw to prevent modal close } finally { modalApi.setState({ confirmLoading: false }); } },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/effects/layouts/src/widgets/timezone/timezone-button.vue(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: post-update (ubuntu-latest)
- GitHub Check: post-update (windows-latest)
- GitHub Check: Test (windows-latest)
- GitHub Check: Lint (windows-latest)
- GitHub Check: Lint (ubuntu-latest)
🔇 Additional comments (1)
packages/effects/layouts/src/widgets/timezone/timezone-button.vue (1)
55-87: Template and styles look good.The previously flagged issues (class name mismatch and label association) have been correctly addressed. The RadioGroup properly binds to
timezoneRef, labels are correctly associated with their inputs via:forand:idattributes, and the scoped styles apply as expected.
|
@jinmao88 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
apps/backend-mock/utils/mock-data.ts (1)
397-398: Consider using English for comments.The Chinese comment "时区选项" may reduce maintainability in an international codebase.
/** - * 时区选项 + * Timezone options */playground/src/api/core/timezone.ts (3)
6-13: Consider removing redundantawaitfor consistency.The
awaitkeyword on line 7 is unnecessary when immediately returning the promise. Lines 18 and 25 follow the cleaner pattern of returning the promise directly.Apply this diff for consistency:
export async function getTimezoneOptionsApi() { - return await requestClient.get< + return requestClient.get< { label: string; value: string; }[] >('/timezone/getTimezoneOptions'); }
17-19: Simplify return type by choosing eithernullorundefined.The return type includes both
nullandundefined, which is redundant. API responses typically use one or the other to represent "no value." Consider simplifying tostring | nullorstring | undefineddepending on what the backend actually returns.Example simplification:
-export async function getTimezoneApi(): Promise<null | string | undefined> { - return requestClient.get<null | string | undefined>('/timezone/getTimezone'); +export async function getTimezoneApi(): Promise<string | null> { + return requestClient.get<string | null>('/timezone/getTimezone'); }
24-26: Explicitly typerequestClient.postcall with<void>generic parameter.The
requestClient.post()method returnsPromise<T>whereTdefaults toany, making the actual return typePromise<any>. While TypeScript allows assigningPromise<any>toPromise<void>, explicitly specifying the generic type improves clarity and type safety:export async function setTimezoneApi(timezone: string): Promise<void> { return requestClient.post<void>('/timezone/setTimezone', { timezone }); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/backend-mock/utils/mock-data.ts(2 hunks)playground/src/api/core/timezone.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/backend-mock/utils/mock-data.ts (1)
packages/@core/base/typings/src/app.d.ts (1)
TimezoneOption(120-120)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Check (ubuntu-latest)
- GitHub Check: Lint (ubuntu-latest)
- GitHub Check: Lint (windows-latest)
- GitHub Check: Check (windows-latest)
- GitHub Check: Test (windows-latest)
- GitHub Check: post-update (windows-latest)
- GitHub Check: post-update (ubuntu-latest)
🔇 Additional comments (2)
apps/backend-mock/utils/mock-data.ts (1)
400-421: Addlabelfield to match the canonicalTimezoneOptiontype.The mock data entries lack the
labelfield expected by the canonical type definition. Downstream API handlers that consumeTIME_ZONE_OPTIONSexpect labeled timezone option data, so labels should be included in the mock data.export const TIME_ZONE_OPTIONS: TimezoneOption[] = [ { + label: 'New York (UTC-5)', offset: -5, timezone: 'America/New_York', }, { + label: 'London (UTC+0)', offset: 0, timezone: 'Europe/London', }, { + label: 'Shanghai (UTC+8)', offset: 8, timezone: 'Asia/Shanghai', }, { + label: 'Tokyo (UTC+9)', offset: 9, timezone: 'Asia/Tokyo', }, { + label: 'Seoul (UTC+9)', offset: 9, timezone: 'Asia/Seoul', }, ];Advisory note: The fixed
offsetvalues don't account for daylight saving time. If the application requires DST-aware offsets, consider computing them dynamically using a library likedate-fns-tzorluxon.Likely an incorrect or invalid review comment.
playground/src/api/core/timezone.ts (1)
1-26: Previous critical issues have been resolved.The property name mismatch issue flagged in the previous review (accessing
item.timezoneinstead ofitem.timeZone) appears to have been resolved—the current implementation correctly returns the API response directly without manual mapping. Additionally, thesetTimezoneApireturn type has been properly updated fromPromise<any>toPromise<void>.
| export interface TimezoneOption { | ||
| offset: number; | ||
| timezone: string; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interface definition is incomplete compared to the canonical type.
The TimezoneOption interface defined here lacks the label field present in the canonical type at packages/@core/base/typings/src/app.d.ts. This mismatch will cause type errors when the mock data is consumed by code expecting the complete type signature.
Option 1 (recommended): Import the canonical type instead of redefining it:
+import type { TimezoneOption } from '#/@core/base/typings';
+
export interface UserInfo {
id: number;
password: string;
realName: string;
roles: string[];
username: string;
homePath?: string;
}
-export interface TimezoneOption {
- offset: number;
- timezone: string;
-}Option 2: Add the missing label field to match the canonical type:
export interface TimezoneOption {
+ label: string;
offset: number;
timezone: string;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export interface TimezoneOption { | |
| offset: number; | |
| timezone: string; | |
| } | |
| export interface TimezoneOption { | |
| label: string; | |
| offset: number; | |
| timezone: string; | |
| } |
🤖 Prompt for AI Agents
In apps/backend-mock/utils/mock-data.ts around lines 10 to 13, the
TimezoneOption interface is missing the `label` field present in the canonical
type, causing type mismatches; fix by either importing the canonical
TimezoneOption type from packages/@core/base/typings/src/app.d.ts and replacing
this local definition, or add the missing `label: string` property to this
interface so it matches the canonical signature used elsewhere.
feat: increase support for multiple time zones

Summary by CodeRabbit