From c94da293058e4d8f83a849bf424a8d265aca2c20 Mon Sep 17 00:00:00 2001 From: kamieniarz <9063907+kamieniarz@users.noreply.github.com> Date: Tue, 30 Sep 2025 01:00:00 +0200 Subject: [PATCH 01/10] Update index.jsx --- src/features/profile/index.jsx | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/features/profile/index.jsx b/src/features/profile/index.jsx index 564e1e113..c93bec3e2 100644 --- a/src/features/profile/index.jsx +++ b/src/features/profile/index.jsx @@ -15,6 +15,7 @@ import { Header } from '@components/dialogs/Header' import { Footer } from '@components/dialogs/Footer' import { DialogWrapper } from '@components/dialogs/DialogWrapper' import { useAnalytics } from '@hooks/useAnalytics' +import { getSettings } from '@services/fetches' import { UserBackups } from './Backups' import { UserPermissions } from './Permissions' @@ -32,6 +33,33 @@ export function UserProfile() { const [tab, setTab] = React.useState('profile') const [tabsHeight, setTabsHeight] = React.useState(0) + const isOpen = useLayoutStore((s) => s.userProfile) + React.useEffect(() => { + if (!isOpen) return + let active = true + ;(async () => { + try { + const data = await getSettings() + if (active && data && !('error' in data) && data.user) { + const parsed = data.user?.data + ? typeof data.user.data === 'string' + ? JSON.parse(data.user.data) + : data.user.data + : {} + useMemory.setState((prev) => ({ + auth: { + ...prev.auth, + data: parsed, + }, + })) + } + } catch { + } + })() + return () => { + active = false + } + }, [isOpen]) const handleTabChange = (_event, newValue) => { setTab(newValue) From 4c247a68420ebf06ddc1f6ab9bc6830177808255 Mon Sep 17 00:00:00 2001 From: kamieniarz <9063907+kamieniarz@users.noreply.github.com> Date: Tue, 30 Sep 2025 01:00:51 +0200 Subject: [PATCH 02/10] Update rootRouter.js --- server/src/routes/rootRouter.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/src/routes/rootRouter.js b/server/src/routes/rootRouter.js index a2feb1428..7987ab26e 100644 --- a/server/src/routes/rootRouter.js +++ b/server/src/routes/rootRouter.js @@ -180,6 +180,9 @@ rootRouter.get('/api/settings', async (req, res, next) => { req.session.save() } } + if (user.data !== undefined) { + req.user.data = user.data + } } } catch (e) { log.warn(TAGS.session, 'Issue finding user, User ID:', req?.user?.id, e) @@ -214,6 +217,12 @@ rootRouter.get('/api/settings', async (req, res, next) => { } } + res.set({ + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + Pragma: 'no-cache', + Expires: '0', + 'Surrogate-Control': 'no-store', + }) res.status(200).json(settings) } catch (error) { res.status(500).json({ error: error.message, status: 500 }) From a364dbd7ee077b48d46b76bef5d09d3be72caf3f Mon Sep 17 00:00:00 2001 From: kamieniarz <9063907+kamieniarz@users.noreply.github.com> Date: Tue, 30 Sep 2025 01:01:20 +0200 Subject: [PATCH 03/10] Update fetches.js --- src/services/fetches.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/fetches.js b/src/services/fetches.js index 89b8187d4..95d1a86a4 100644 --- a/src/services/fetches.js +++ b/src/services/fetches.js @@ -10,7 +10,9 @@ */ export async function getSettings() { try { - const response = await fetch('/api/settings') + const response = await fetch(`/api/settings?t=${Date.now()}`, { + cache: 'no-store', + }) if (!response.ok) { throw new Error(`${response.status} (${response.statusText})`) } From ca08ddd4d0955c373e246ac42b8cde394b2ee27c Mon Sep 17 00:00:00 2001 From: kamieniarz <9063907+kamieniarz@users.noreply.github.com> Date: Tue, 30 Sep 2025 08:41:25 +0200 Subject: [PATCH 04/10] Update fetches.js --- src/services/fetches.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/fetches.js b/src/services/fetches.js index 95d1a86a4..7117bdb15 100644 --- a/src/services/fetches.js +++ b/src/services/fetches.js @@ -10,8 +10,7 @@ */ export async function getSettings() { try { - const response = await fetch(`/api/settings?t=${Date.now()}`, { - cache: 'no-store', + const response = await fetch('/api/settings') }) if (!response.ok) { throw new Error(`${response.status} (${response.statusText})`) From 1f703a7c9833254191e4b7bdcd0c112d9e28031a Mon Sep 17 00:00:00 2001 From: kamieniarz <9063907+kamieniarz@users.noreply.github.com> Date: Tue, 30 Sep 2025 08:43:24 +0200 Subject: [PATCH 05/10] Update fetches.js --- src/services/fetches.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/fetches.js b/src/services/fetches.js index 7117bdb15..89b8187d4 100644 --- a/src/services/fetches.js +++ b/src/services/fetches.js @@ -11,7 +11,6 @@ export async function getSettings() { try { const response = await fetch('/api/settings') - }) if (!response.ok) { throw new Error(`${response.status} (${response.statusText})`) } From 835f9427d3864d3c74f9ae9d44bd96ce1271abed Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 1 Oct 2025 13:01:20 -0700 Subject: [PATCH 06/10] fix: misc --- server/src/routes/rootRouter.js | 6 ------ src/features/profile/index.jsx | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/server/src/routes/rootRouter.js b/server/src/routes/rootRouter.js index 7987ab26e..4148f8bfa 100644 --- a/server/src/routes/rootRouter.js +++ b/server/src/routes/rootRouter.js @@ -217,12 +217,6 @@ rootRouter.get('/api/settings', async (req, res, next) => { } } - res.set({ - 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', - Pragma: 'no-cache', - Expires: '0', - 'Surrogate-Control': 'no-store', - }) res.status(200).json(settings) } catch (error) { res.status(500).json({ error: error.message, status: 500 }) diff --git a/src/features/profile/index.jsx b/src/features/profile/index.jsx index c93bec3e2..df265d218 100644 --- a/src/features/profile/index.jsx +++ b/src/features/profile/index.jsx @@ -53,7 +53,8 @@ export function UserProfile() { }, })) } - } catch { + } catch (error) { + // Swallow network/parsing errors to avoid interrupting the profile dialog } })() return () => { From 5fba21dab82621e0ecbff88ddbd6285c2af294e0 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 1 Oct 2025 13:07:56 -0700 Subject: [PATCH 07/10] fix: no swallow error --- src/features/profile/index.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/features/profile/index.jsx b/src/features/profile/index.jsx index df265d218..4e76eef4f 100644 --- a/src/features/profile/index.jsx +++ b/src/features/profile/index.jsx @@ -54,7 +54,10 @@ export function UserProfile() { })) } } catch (error) { - // Swallow network/parsing errors to avoid interrupting the profile dialog + if (active) { + // eslint-disable-next-line no-console + console.error('Failed to refresh user profile data', error) + } } })() return () => { From b841b59b2955eccdfcacb31fd6070869b3e76b6f Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 1 Oct 2025 13:18:35 -0700 Subject: [PATCH 08/10] fix: incorporate fix --- src/features/profile/ExtraFields.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/features/profile/ExtraFields.jsx b/src/features/profile/ExtraFields.jsx index 3a5daef79..04d808ac1 100644 --- a/src/features/profile/ExtraFields.jsx +++ b/src/features/profile/ExtraFields.jsx @@ -42,19 +42,20 @@ export function FieldValue({ field }) { label={label} value={value} onChange={({ target }) => { + const nextValue = target.value useMemory.setState((prev) => ({ auth: { ...prev.auth, data: { ...prev.auth.data, - [key]: target.value, + [key]: nextValue, }, }, })) setField({ variables: { key, - value, + value: nextValue, }, }) }} From 1a0ebc7d327e70d0e5297437d0b4c54383c6d3ca Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 1 Oct 2025 14:22:35 -0700 Subject: [PATCH 09/10] fix: only update readonly fields --- src/features/profile/index.jsx | 50 ++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/features/profile/index.jsx b/src/features/profile/index.jsx index 4e76eef4f..1b61f3718 100644 --- a/src/features/profile/index.jsx +++ b/src/features/profile/index.jsx @@ -46,12 +46,50 @@ export function UserProfile() { ? JSON.parse(data.user.data) : data.user.data : {} - useMemory.setState((prev) => ({ - auth: { - ...prev.auth, - data: parsed, - }, - })) + const store = useMemory.getState() + const readOnlyKeys = new Set( + (store.extraUserFields || []) + .map((field) => + typeof field === 'string' + ? null + : field.disabled + ? field.database + : null, + ) + .filter(Boolean), + ) + if (!readOnlyKeys.size) return + + const incoming = + parsed && typeof parsed === 'object' + ? parsed + : /** @type {Record} */ ({}) + const currentData = store.auth.data || {} + const updates = {} + let hasUpdates = false + + readOnlyKeys.forEach((key) => { + if (Object.prototype.hasOwnProperty.call(incoming, key)) { + const nextValue = incoming[key] + if (currentData[key] !== nextValue) { + updates[key] = nextValue + hasUpdates = true + } + } + }) + + if (hasUpdates) { + // Only refresh disabled fields so live edits stay intact. + useMemory.setState((prev) => ({ + auth: { + ...prev.auth, + data: { + ...prev.auth.data, + ...updates, + }, + }, + })) + } } } catch (error) { if (active) { From e6e8c6528f068266dd14a1ad8531713c109ebb96 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 1 Oct 2025 14:29:53 -0700 Subject: [PATCH 10/10] fix: better way --- src/features/profile/ExtraFields.jsx | 15 +++++--- src/features/profile/index.jsx | 54 ++++++++-------------------- 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/src/features/profile/ExtraFields.jsx b/src/features/profile/ExtraFields.jsx index 04d808ac1..b08684cbc 100644 --- a/src/features/profile/ExtraFields.jsx +++ b/src/features/profile/ExtraFields.jsx @@ -8,7 +8,8 @@ import { useTranslation } from 'react-i18next' import { useMemory } from '@store/useMemory' import { Query } from '@services/queries' -export function ExtraUserFields() { +/** @param {{ refreshing?: boolean }} props */ +export function ExtraUserFields({ refreshing = false } = {}) { const fields = useMemory((s) => s.extraUserFields) return fields?.length ? ( @@ -16,14 +17,20 @@ export function ExtraUserFields() { ))} ) : null } -/** @param {{ field: import('@rm/types').ExtraField | string}} props */ -export function FieldValue({ field }) { +/** + * @param {{ + * field: import('@rm/types').ExtraField | string, + * refreshing?: boolean, + * }} props + */ +export function FieldValue({ field, refreshing = false }) { const { i18n } = useTranslation() const label = typeof field === 'string' ? field : field[i18n.language] || field.name @@ -37,7 +44,7 @@ export function FieldValue({ field }) { return ( s.userProfile) React.useEffect(() => { if (!isOpen) return let active = true + setRefreshing(true) ;(async () => { try { const data = await getSettings() - if (active && data && !('error' in data) && data.user) { + if (data && !('error' in data) && data.user) { const parsed = data.user?.data ? typeof data.user.data === 'string' ? JSON.parse(data.user.data) : data.user.data : {} - const store = useMemory.getState() - const readOnlyKeys = new Set( - (store.extraUserFields || []) - .map((field) => - typeof field === 'string' - ? null - : field.disabled - ? field.database - : null, - ) - .filter(Boolean), - ) - if (!readOnlyKeys.size) return - - const incoming = - parsed && typeof parsed === 'object' - ? parsed - : /** @type {Record} */ ({}) - const currentData = store.auth.data || {} - const updates = {} - let hasUpdates = false - - readOnlyKeys.forEach((key) => { - if (Object.prototype.hasOwnProperty.call(incoming, key)) { - const nextValue = incoming[key] - if (currentData[key] !== nextValue) { - updates[key] = nextValue - hasUpdates = true - } - } - }) - - if (hasUpdates) { - // Only refresh disabled fields so live edits stay intact. + if (active) { useMemory.setState((prev) => ({ auth: { ...prev.auth, - data: { - ...prev.auth.data, - ...updates, - }, + data: + parsed && typeof parsed === 'object' + ? parsed + : /** @type {Record} */ ({}), }, })) } @@ -96,10 +65,15 @@ export function UserProfile() { // eslint-disable-next-line no-console console.error('Failed to refresh user profile data', error) } + } finally { + if (active) { + setRefreshing(false) + } } })() return () => { active = false + setRefreshing(false) } }, [isOpen]) @@ -137,7 +111,7 @@ export function UserProfile() { > - +