nicolium: improve frontend config handling
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -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<FrontendConfigAction>({
|
||||
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<typeof importFrontendConfig>
|
||||
| ReturnType<typeof frontendConfigFail>
|
||||
| {
|
||||
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,
|
||||
};
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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 (
|
||||
<Stack space={1}>
|
||||
<Text weight='semibold' tag='h1' size='xl'>
|
||||
<FormattedMessage
|
||||
id='report.confirmation.title'
|
||||
defaultMessage='Thanks for submitting your report.'
|
||||
/>
|
||||
{intl.formatMessage(messages.title)}
|
||||
</Text>
|
||||
|
||||
<Text>
|
||||
{intl.formatMessage(messages.content, {
|
||||
entity,
|
||||
link: links.termsOfService
|
||||
? renderTermsOfServiceLink(links.termsOfService)
|
||||
: termsOfServiceText,
|
||||
})}
|
||||
<FormattedMessage
|
||||
id='report.confirmation.content'
|
||||
defaultMessage='If we find that this {entity} is violating the {link} we will take further action on the matter.'
|
||||
values={{
|
||||
entity,
|
||||
link: links?.termsOfService
|
||||
? renderTermsOfServiceLink(links.termsOfService)
|
||||
: termsOfServiceText,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@ -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<FrontendConfig> = {};
|
||||
const initialState: PartialFrontendConfig = {};
|
||||
|
||||
const fallbackState: Partial<FrontendConfig> = {
|
||||
const fallbackState: PartialFrontendConfig = {
|
||||
brandColor: '#d80482',
|
||||
};
|
||||
|
||||
@ -39,32 +42,42 @@ const preloadImport = (state: Record<string, any>, action: Record<string, any>)
|
||||
}
|
||||
};
|
||||
|
||||
const persistFrontendConfig = (frontendConfig: Record<string, any>, 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<string, any>,
|
||||
): Partial<FrontendConfig> => {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ const logOut = (state: AppState): ReturnType<typeof appReducer> => {
|
||||
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 };
|
||||
|
||||
@ -41,7 +41,7 @@ const cryptoAddressSchema = v.pipe(
|
||||
|
||||
type CryptoAddress = v.InferOutput<typeof cryptoAddressSchema>;
|
||||
|
||||
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<typeof frontendConfigSchema>;
|
||||
|
||||
const partialFrontendConfigSchema = v.partial(v.object(frontendConfigSchemaShape));
|
||||
|
||||
type PartialFrontendConfig = v.InferOutput<typeof partialFrontendConfigSchema>;
|
||||
|
||||
export {
|
||||
promoPanelItemSchema,
|
||||
footerItemSchema,
|
||||
cryptoAddressSchema,
|
||||
frontendConfigSchema,
|
||||
partialFrontendConfigSchema,
|
||||
type PromoPanelItem,
|
||||
type PromoPanel,
|
||||
type FooterItem,
|
||||
type CryptoAddress,
|
||||
type FrontendConfig,
|
||||
type PartialFrontendConfig,
|
||||
};
|
||||
|
||||
@ -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<State>()(
|
||||
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<State>()(
|
||||
});
|
||||
},
|
||||
|
||||
loadUserSettings: (settings?: APIEntity) => {
|
||||
loadUserSettings: (settings?: unknown) => {
|
||||
set((state: State) => {
|
||||
if (typeof settings !== 'object') return;
|
||||
|
||||
|
||||
@ -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 =>
|
||||
|
||||
Reference in New Issue
Block a user