diff --git a/packages/nicolium/src/actions/frontend-config.ts b/packages/nicolium/src/actions/frontend-config.ts index c2f19e54e..6446a1da8 100644 --- a/packages/nicolium/src/actions/frontend-config.ts +++ b/packages/nicolium/src/actions/frontend-config.ts @@ -1,9 +1,5 @@ -import { createSelector } from 'reselect'; -import * as v from 'valibot'; - import { getHost } from '@/actions/instance'; import { getClient, staticFetch } from '@/api'; -import { frontendConfigSchema } from '@/schemas/frontend-config'; import KVStore from '@/storage/kv-store'; import { useSettingsStore } from '@/stores/settings'; @@ -15,18 +11,14 @@ const FRONTEND_CONFIG_REQUEST_FAIL = 'FRONTEND_CONFIG_REQUEST_FAIL' as const; const FRONTEND_CONFIG_REMEMBER_SUCCESS = 'FRONTEND_CONFIG_REMEMBER_SUCCESS' as const; -const getFrontendConfig = createSelector( - [ - (state: RootState) => state.frontendConfig, - // Do some additional normalization with the state - ], - (frontendConfig) => v.parse(frontendConfigSchema, frontendConfig), -); - const rememberFrontendConfig = (host: string | null) => (dispatch: AppDispatch) => KVStore.getItemOrError(`plfe_config:${host}`) .then((frontendConfig) => { - dispatch({ type: FRONTEND_CONFIG_REMEMBER_SUCCESS, host, frontendConfig }); + dispatch({ + type: FRONTEND_CONFIG_REMEMBER_SUCCESS, + host, + frontendConfig, + }); return true; }) .catch(() => false); @@ -101,11 +93,20 @@ const frontendConfigFail = (error: unknown, host: string | null) => ({ // https://stackoverflow.com/a/46663081 const isObject = (o: any) => o instanceof Object && o.constructor === Object; +type FrontendConfigAction = + | ReturnType + | ReturnType + | { + type: typeof FRONTEND_CONFIG_REMEMBER_SUCCESS; + frontendConfig: APIEntity; + host: string | null; + }; + export { FRONTEND_CONFIG_REQUEST_SUCCESS, FRONTEND_CONFIG_REQUEST_FAIL, FRONTEND_CONFIG_REMEMBER_SUCCESS, - getFrontendConfig, fetchFrontendConfig, loadFrontendConfig, + type FrontendConfigAction, }; diff --git a/packages/nicolium/src/hooks/use-frontend-config.ts b/packages/nicolium/src/hooks/use-frontend-config.ts index 4bf920596..92758eb1d 100644 --- a/packages/nicolium/src/hooks/use-frontend-config.ts +++ b/packages/nicolium/src/hooks/use-frontend-config.ts @@ -1,8 +1,17 @@ -import { getFrontendConfig } from '@/actions/frontend-config'; +import { useMemo } from 'react'; +import * as v from 'valibot'; + +import { frontendConfigSchema } from '@/schemas/frontend-config'; import { useAppSelector } from './use-app-selector'; +const defaultFrontendConfig = v.parse(frontendConfigSchema, {}); + /** Get the Nicolium config from the store */ -const useFrontendConfig = () => useAppSelector((state) => getFrontendConfig(state)); +const useFrontendConfig = () => { + const partialConfig = useAppSelector((state) => state.frontendConfig); + + return useMemo(() => ({ ...defaultFrontendConfig, ...partialConfig }), [partialConfig]); +}; export { useFrontendConfig }; diff --git a/packages/nicolium/src/modals/report-modal/steps/confirmation-step.tsx b/packages/nicolium/src/modals/report-modal/steps/confirmation-step.tsx index 6967b93fb..8d764ba46 100644 --- a/packages/nicolium/src/modals/report-modal/steps/confirmation-step.tsx +++ b/packages/nicolium/src/modals/report-modal/steps/confirmation-step.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { getFrontendConfig } from '@/actions/frontend-config'; import Stack from '@/components/ui/stack'; import Text from '@/components/ui/text'; import { useAppSelector } from '@/hooks/use-app-selector'; @@ -30,23 +29,31 @@ const renderTermsOfServiceLink = (href: string) => ( const ConfirmationStep: React.FC = () => { const intl = useIntl(); - const links = useAppSelector((state) => getFrontendConfig(state).links); + const links = useAppSelector((state) => state.frontendConfig.links); const entity = intl.formatMessage(messages.accountEntity); return ( + {intl.formatMessage(messages.title)} - {intl.formatMessage(messages.content, { - entity, - link: links.termsOfService - ? renderTermsOfServiceLink(links.termsOfService) - : termsOfServiceText, - })} + ); diff --git a/packages/nicolium/src/reducers/frontend-config.ts b/packages/nicolium/src/reducers/frontend-config.ts index c71f5a151..d0dd466d7 100644 --- a/packages/nicolium/src/reducers/frontend-config.ts +++ b/packages/nicolium/src/reducers/frontend-config.ts @@ -1,19 +1,22 @@ -import { ADMIN_CONFIG_UPDATE_SUCCESS } from '@/actions/admin'; +import * as v from 'valibot'; + +import { ADMIN_CONFIG_UPDATE_SUCCESS, type AdminActions } from '@/actions/admin'; import { FRONTEND_CONFIG_REMEMBER_SUCCESS, FRONTEND_CONFIG_REQUEST_SUCCESS, FRONTEND_CONFIG_REQUEST_FAIL, + type FrontendConfigAction, } from '@/actions/frontend-config'; -import { PLEROMA_PRELOAD_IMPORT } from '@/actions/preload'; +import { PLEROMA_PRELOAD_IMPORT, type PreloadAction } from '@/actions/preload'; +import { type PartialFrontendConfig, partialFrontendConfigSchema } from '@/schemas/frontend-config'; import KVStore from '@/storage/kv-store'; import ConfigDB from '@/utils/config-db'; -import type { FrontendConfig } from '@/schemas/frontend-config'; import type { PleromaConfig } from 'pl-api'; -const initialState: Partial = {}; +const initialState: PartialFrontendConfig = {}; -const fallbackState: Partial = { +const fallbackState: PartialFrontendConfig = { brandColor: '#d80482', }; @@ -39,32 +42,42 @@ const preloadImport = (state: Record, action: Record) } }; -const persistFrontendConfig = (frontendConfig: Record, host: string) => { +const persistFrontendConfig = (frontendConfig: PartialFrontendConfig, host: string) => { if (host) { KVStore.setItem(`plfe_config:${host}`, frontendConfig).catch(console.error); } }; -const importFrontendConfig = (frontendConfig: FrontendConfig, host: string) => { - persistFrontendConfig(frontendConfig, host); - return frontendConfig; +const importFrontendConfig = (frontendConfig: unknown, host: string) => { + const parsedFrontendConfig = v.parse(partialFrontendConfigSchema, frontendConfig); + persistFrontendConfig(parsedFrontendConfig, host); + return parsedFrontendConfig; +}; + +const parseFrontendConfig = (frontendConfig: unknown) => { + try { + return v.parse(partialFrontendConfigSchema, frontendConfig); + } catch (e) { + console.error('Failed to parse frontend config', e); + return null; + } }; const frontendConfig = ( state = initialState, - action: Record, -): Partial => { + action: PreloadAction | FrontendConfigAction | AdminActions, +): PartialFrontendConfig => { switch (action.type) { case PLEROMA_PRELOAD_IMPORT: - return preloadImport(state, action); + return parseFrontendConfig(preloadImport(state, action)) || state; case FRONTEND_CONFIG_REMEMBER_SUCCESS: - return action.frontendConfig; + return parseFrontendConfig(action.frontendConfig) || state; case FRONTEND_CONFIG_REQUEST_SUCCESS: - return importFrontendConfig(action.frontendConfig ?? {}, action.host); + return importFrontendConfig(action.frontendConfig ?? {}, action.host || '') || state; case FRONTEND_CONFIG_REQUEST_FAIL: return { ...fallbackState, ...state }; case ADMIN_CONFIG_UPDATE_SUCCESS: - return updateFromAdmin(state, action.configs ?? []); + return parseFrontendConfig(updateFromAdmin(state, action.configs ?? [])) || state; default: return state; } diff --git a/packages/nicolium/src/reducers/index.ts b/packages/nicolium/src/reducers/index.ts index 11b628f8f..71d1d42e4 100644 --- a/packages/nicolium/src/reducers/index.ts +++ b/packages/nicolium/src/reducers/index.ts @@ -37,7 +37,7 @@ const logOut = (state: AppState): ReturnType => { location.href = '/login'; } - const newState = rootReducer(undefined, { type: '' }); + const newState = rootReducer(undefined, { type: '' } as any); const { instance, frontendConfig, auth } = state; return { ...newState, instance, frontendConfig, auth }; diff --git a/packages/nicolium/src/schemas/frontend-config.ts b/packages/nicolium/src/schemas/frontend-config.ts index 76bbc42ce..4287530cb 100644 --- a/packages/nicolium/src/schemas/frontend-config.ts +++ b/packages/nicolium/src/schemas/frontend-config.ts @@ -41,7 +41,7 @@ const cryptoAddressSchema = v.pipe( type CryptoAddress = v.InferOutput; -const frontendConfigSchema = coerceObject({ +const frontendConfigSchemaShape = { appleAppId: v.fallback(v.nullable(v.string()), null), logo: v.fallback(v.string(), ''), logoDarkMode: v.fallback(v.nullable(v.string()), null), @@ -117,18 +117,26 @@ const frontendConfigSchema = coerceObject({ */ mediaPreview: v.fallback(v.boolean(), false), sentryDsn: v.fallback(v.optional(v.string()), undefined), -}); +}; + +const frontendConfigSchema = coerceObject(frontendConfigSchemaShape); type FrontendConfig = v.InferOutput; +const partialFrontendConfigSchema = v.partial(v.object(frontendConfigSchemaShape)); + +type PartialFrontendConfig = v.InferOutput; + export { promoPanelItemSchema, footerItemSchema, cryptoAddressSchema, frontendConfigSchema, + partialFrontendConfigSchema, type PromoPanelItem, type PromoPanel, type FooterItem, type CryptoAddress, type FrontendConfig, + type PartialFrontendConfig, }; diff --git a/packages/nicolium/src/stores/settings.ts b/packages/nicolium/src/stores/settings.ts index 41ba5c6bb..31341b469 100644 --- a/packages/nicolium/src/stores/settings.ts +++ b/packages/nicolium/src/stores/settings.ts @@ -50,8 +50,8 @@ type State = { settings: Settings; actions: { - loadDefaultSettings: (settings: APIEntity) => void; - loadUserSettings: (settings: APIEntity) => void; + loadDefaultSettings: (settings: unknown) => void; + loadUserSettings: (settings: unknown) => void; userSettingsSaving: () => void; changeSetting: (path: string[], value: any) => void; rememberEmojiUse: (emoji: Emoji) => void; @@ -146,7 +146,7 @@ const useSettingsStore = create()( settings: v.parse(settingsSchema, { locale: navigator.language }), actions: { - loadDefaultSettings: (settings: APIEntity) => { + loadDefaultSettings: (settings: unknown) => { set((state: State) => { if (typeof settings !== 'object') return; @@ -155,7 +155,7 @@ const useSettingsStore = create()( }); }, - loadUserSettings: (settings?: APIEntity) => { + loadUserSettings: (settings?: unknown) => { set((state: State) => { if (typeof settings !== 'object') return; diff --git a/packages/nicolium/src/utils/state.ts b/packages/nicolium/src/utils/state.ts index 56e141a84..15c9a8d4c 100644 --- a/packages/nicolium/src/utils/state.ts +++ b/packages/nicolium/src/utils/state.ts @@ -3,7 +3,6 @@ * @module @/utils/state */ -import { getFrontendConfig } from '@/actions/frontend-config'; import * as BuildConfig from '@/build-config'; import { isPrerendered } from '@/precheck'; import { selectOwnAccount } from '@/queries/accounts/selectors'; @@ -12,7 +11,7 @@ import { isURL } from '@/utils/auth'; import type { RootState } from '@/store'; /** Whether to display the fqn instead of the acct. */ -const displayFqn = (state: RootState): boolean => getFrontendConfig(state).displayFqn; +const displayFqn = (state: RootState): boolean => state.frontendConfig.displayFqn ?? true; /** Whether the instance exposes instance blocks through the API. */ const federationRestrictionsDisclosed = (state: RootState): boolean =>