Skip to content

feat: persist user frontend theme preference#2

Merged
Starry-Sky-World merged 1 commit into
CuzTeam:nightlyfrom
dext-stu:feat/user-frontend-theme-sync
May 4, 2026
Merged

feat: persist user frontend theme preference#2
Starry-Sky-World merged 1 commit into
CuzTeam:nightlyfrom
dext-stu:feat/user-frontend-theme-sync

Conversation

@dext-stu
Copy link
Copy Markdown

@dext-stu dext-stu commented May 4, 2026

Summary

  • add new/classic UI switching entry on both profile pages
  • persist frontend_theme into user settings for cross-device sync
  • sync frontend theme cookie during login, self fetch, and cross-theme navigation

Testing

  • classic profile switch to default UI and refresh
  • default profile switch to classic UI and refresh
  • login/refresh follows saved theme

@Starry-Sky-World Starry-Sky-World self-requested a review May 4, 2026 19:11
@Starry-Sky-World
Copy link
Copy Markdown

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a frontend theme switching feature, allowing users to choose between "classic" and "default" UI styles. The implementation includes backend logic for theme normalization, cookie management, and persistence in user settings, alongside frontend components for theme selection and automatic redirection. Feedback identifies performance concerns regarding redundant database queries in the theme resolution and user update processes, and recommends using the standard API utility in the classic UI for consistency and better error handling.

Comment thread router/web-router.go
Comment on lines +65 to +87
func resolveFrontendTheme(c *gin.Context) string {
session := sessions.Default(c)
if sessionID := session.Get("id"); sessionID != nil {
if userID, ok := sessionID.(int); ok && userID > 0 {
user, err := model.GetUserById(userID, false)
if err == nil {
theme := common.NormalizeFrontendTheme(user.GetSetting().FrontendTheme)
if theme != "" {
common.SetFrontendThemeCookie(c, theme)
return theme
}
}
}
}
themeCookie, err := c.Cookie(common.FrontendThemeCookieName)
if err == nil {
theme := common.NormalizeFrontendTheme(themeCookie)
if theme != "" {
return theme
}
}
return common.GetTheme()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of resolveFrontendTheme performs a database query (model.GetUserById) on every request that hits NoRoute, which includes all frontend routes and static assets. This will cause significant performance degradation under load.

It is highly recommended to check the frontend_theme cookie first. If the cookie is present and valid, use it to avoid the database lookup. Only query the database if the cookie is missing and a valid session exists.

func resolveFrontendTheme(c *gin.Context) string {
	if themeCookie, err := c.Cookie(common.FrontendThemeCookieName); err == nil {
		if theme := common.NormalizeFrontendTheme(themeCookie); theme != "" {
			return theme
		}
	}
	session := sessions.Default(c)
	if sessionID := session.Get("id"); sessionID != nil {
		if userID, ok := sessionID.(int); ok && userID > 0 {
			user, err := model.GetUserById(userID, false)
			if err == nil {
				theme := common.NormalizeFrontendTheme(user.GetSetting().FrontendTheme)
				if theme != "" {
					common.SetFrontendThemeCookie(c, theme)
					return theme
				}
			}
		}
	}
	return common.GetTheme()
}

Comment thread model/user.go
Comment on lines +514 to +516
if err = DB.First(user, user.Id).Error; err != nil {
return err
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Adding a DB.First call inside the general Update method introduces an extra database query for every user update operation across the entire application. This is inefficient and usually unnecessary as GORM's Updates already modifies the fields in the local struct instance. If a specific caller needs to ensure the object is fully synced with database-side changes (like triggers or default values), they should perform the reload explicitly.

Comment on lines +64 to +83
const updateFrontendThemePreference = async (theme, userId) => {
const response = await fetch('/api/user/self', {
method: 'PUT',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
'New-API-User': String(userId ?? -1),
},
body: JSON.stringify({
frontend_theme: theme,
}),
});

const data = await response.json();
if (!response.ok || !data?.success) {
throw new Error(data?.message || 'save frontend theme failed');
}
return data;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The updateFrontendThemePreference function uses the native fetch API with hardcoded paths and custom headers, bypassing the application's standard API utility. This leads to inconsistency, bypasses global error handling/interceptors, and may fail if the application is hosted on a subpath or requires specific authentication headers. It should be removed in favor of using the existing API helper.

const previousTheme = currentFrontendTheme;

try {
await updateFrontendThemePreference(theme, userState?.user?.id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Replace the custom fetch call with the standard API utility for consistency and better error handling.

Suggested change
await updateFrontendThemePreference(theme, userState?.user?.id);
const res = await API.put('/api/user/self', { frontend_theme: theme });
if (!res.data.success) throw new Error(res.data.message || t('界面风格切换失败'));

@Starry-Sky-World Starry-Sky-World changed the base branch from main to nightly May 4, 2026 19:42
@Starry-Sky-World Starry-Sky-World merged commit cff6da1 into CuzTeam:nightly May 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants