/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ import React, { useEffect, useState, useCallback } from "react"; import { Hub } from "@aws-amplify/core"; import Auth from "@aws-amplify/auth"; import { Settings, PublicSettings } from "../models"; import BackendService from "../services/BackendService"; /** * Default settings to start with while we fetch the actual * Settings from the Backend. */ const defaultSettings: Settings = { dateTimeFormat: { date: "YYYY-MM-DD", time: "HH:mm", }, publishingGuidance: "I acknowledge that I have reviewed the dashboard" + " and it is ready to publish", navbarTitle: "", topicAreaLabels: { singular: "Topic Area", plural: "Topic Areas", }, customLogoS3Key: undefined, customFaviconS3Key: undefined, colors: { primary: "#005EA2", secondary: "#54278f", }, contactUsContent: "Please, collect all the relevant information of your issue and contact us using the following email address:", }; interface SettingsContextProps { settings: Settings | PublicSettings; reloadSettings: Function; loadingSettings: boolean; } export const SettingsContext = React.createContext({ reloadSettings: () => {}, settings: defaultSettings, loadingSettings: false, }); /** * The settings reducer takes the settings object coming from the * backend and returns a Settings object containing those values * and fallbacks to default values for any missing field. * * This protects the frontend from crashing in the event where the * backend returns a Settings object that is missing certain fields. */ const settingsReducer = (backendSettings: Settings): Settings => { return { ...backendSettings, // add all values to start with // Check if we need to fallback to default values dateTimeFormat: backendSettings.dateTimeFormat ? backendSettings.dateTimeFormat : defaultSettings.dateTimeFormat, publishingGuidance: backendSettings.publishingGuidance ? backendSettings.publishingGuidance : defaultSettings.publishingGuidance, }; }; const publicSettingsReducer = (backendSettings: PublicSettings): PublicSettings => { return { ...backendSettings, // add all values to start with // Check if we need to fallback to default values dateTimeFormat: backendSettings.dateTimeFormat ? backendSettings.dateTimeFormat : defaultSettings.dateTimeFormat, contactUsContent: backendSettings.contactUsContent ? backendSettings.contactUsContent : defaultSettings.contactUsContent, }; }; /** * This provider wraps the root of our component's tree in * to provide Settings to all the children components in the tree. It * uses React Context so that settings are kept in a global state instead * of fetching them over and over on every screen. */ function SettingsProvider(props: { children: React.ReactNode }) { const [settings, setSettings] = useState({ reloadSettings: () => {}, settings: defaultSettings, loadingSettings: false, }); const fetchSettings = useCallback(async (isUserAuthenticated: boolean) => { return isUserAuthenticated ? BackendService.fetchSettings() : BackendService.fetchPublicSettings(); }, []); const loadSettings = useCallback(async () => { let isUserAuthenticated: boolean; try { await Auth.currentSession(); isUserAuthenticated = true; } catch (err) { isUserAuthenticated = false; } try { /** * useCallback is used because it is important that the * reloadSettings function does not change, because it * causes an infinite loop due to the settings-hook * having a useEffect on it. */ setSettings({ reloadSettings: loadSettings, settings: defaultSettings, loadingSettings: true, }); const data = await fetchSettings(isUserAuthenticated); setSettings({ settings: isUserAuthenticated ? settingsReducer(data) : publicSettingsReducer(data), reloadSettings: loadSettings, loadingSettings: false, }); } catch (err) { console.log("Failed to load settings from backend"); setSettings({ reloadSettings: loadSettings, settings: defaultSettings, loadingSettings: false, }); } }, [fetchSettings]); /** * Listen for authentication events so that when users * signIn or their token is refreshed, we refetch the * Settings. This covers an edge case in which we fail * to fetch Settings the first time because the user was * not authenticated yet. */ const listenAuthEvents = useCallback( (event: any) => { const { payload } = event; switch (payload.event) { case "signIn": case "tokenRefresh": loadSettings(); break; default: break; } }, [loadSettings], ); useEffect(() => { loadSettings(); Hub.listen("auth", listenAuthEvents); return () => Hub.remove("auth", listenAuthEvents); }, [loadSettings, listenAuthEvents]); return {props.children}; } export default SettingsProvider;