mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
Added save states for setting groups in admin-x
refs https://github.com/TryGhost/Team/issues/3151 - adds Save -> Saving -> Saved sequence to save buttons on all setting groups - uses common hook state to manage state across all Settings
This commit is contained in:
parent
19b2112f8b
commit
9ca82490c2
13 changed files with 56 additions and 10 deletions
|
@ -4,12 +4,14 @@ import SettingGroupHeader from './SettingGroupHeader';
|
|||
import {IButton} from '../global/Button';
|
||||
|
||||
export type TSettingGroupStates = 'view' | 'edit' | 'unsaved';
|
||||
export type SaveState = 'saving' | 'saved' | 'error' | '';
|
||||
|
||||
interface SettingGroupProps {
|
||||
navid?:string;
|
||||
title?: string;
|
||||
description?: React.ReactNode;
|
||||
state?: TSettingGroupStates;
|
||||
saveState?: SaveState;
|
||||
customHeader?: React.ReactNode;
|
||||
customButtons?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
|
@ -35,6 +37,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||
title,
|
||||
description,
|
||||
state,
|
||||
saveState,
|
||||
customHeader,
|
||||
customButtons,
|
||||
children,
|
||||
|
@ -59,7 +62,6 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||
|
||||
const handleSave = () => {
|
||||
onSave?.();
|
||||
onStateChange?.('view');
|
||||
};
|
||||
|
||||
switch (state) {
|
||||
|
@ -79,9 +81,13 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||
let viewButtons = [];
|
||||
|
||||
if (!hideEditButton) {
|
||||
let label = 'Edit';
|
||||
if (saveState === 'saved') {
|
||||
label = 'Saved';
|
||||
}
|
||||
viewButtons.push(
|
||||
{
|
||||
label: 'Edit',
|
||||
label,
|
||||
key: 'edit',
|
||||
color: 'green',
|
||||
onClick: handleEdit
|
||||
|
@ -98,9 +104,13 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||
];
|
||||
|
||||
if (state === 'unsaved' || alwaysShowSaveButton) {
|
||||
let label = 'Save';
|
||||
if (saveState === 'saving') {
|
||||
label = 'Saving...';
|
||||
}
|
||||
editButtons.push(
|
||||
{
|
||||
label: 'Save',
|
||||
label,
|
||||
key: 'save',
|
||||
color: 'green',
|
||||
onClick: handleSave
|
||||
|
@ -112,7 +122,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||
<div className={`flex flex-col gap-6 rounded ${border && 'border p-5 md:p-7'} ${styles}`} id={navid && navid}>
|
||||
{customHeader ? customHeader :
|
||||
<SettingGroupHeader description={description} title={title!}>
|
||||
{customButtons ? customButtons :
|
||||
{customButtons ? customButtons :
|
||||
(onStateChange && <ButtonGroup buttons={state === 'view' ? viewButtons : editButtons} link={true} />)}
|
||||
</SettingGroupHeader>
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import {settingsApi} from '../../utils/api';
|
|||
// Define the Settings Context
|
||||
interface SettingsContextProps {
|
||||
settings: Setting[] | null;
|
||||
saveSettings: (updatedSettings: Setting[]) => void;
|
||||
saveSettings: (updatedSettings: Setting[]) => Promise<void>;
|
||||
}
|
||||
|
||||
interface SettingsProviderProps {
|
||||
|
@ -14,7 +14,7 @@ interface SettingsProviderProps {
|
|||
|
||||
const SettingsContext = createContext<SettingsContextProps>({
|
||||
settings: null,
|
||||
saveSettings: () => []
|
||||
saveSettings: async () => {}
|
||||
});
|
||||
|
||||
// Create a Settings Provider component
|
||||
|
|
|
@ -15,6 +15,7 @@ const MAILGUN_REGIONS = [
|
|||
const MailGun: React.FC = () => {
|
||||
const {
|
||||
currentState,
|
||||
saveState,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
updateSetting,
|
||||
|
@ -96,6 +97,7 @@ const MailGun: React.FC = () => {
|
|||
return (
|
||||
<SettingGroup
|
||||
description={groupDescription}
|
||||
saveState={saveState}
|
||||
state={currentState}
|
||||
title='Mailgun'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -7,6 +7,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup';
|
|||
const Facebook: React.FC = () => {
|
||||
const {
|
||||
currentState,
|
||||
saveState,
|
||||
focusRef,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
|
@ -51,6 +52,7 @@ const Facebook: React.FC = () => {
|
|||
<SettingGroup
|
||||
description='Customize structured data of your site'
|
||||
navid='facebook'
|
||||
saveState={saveState}
|
||||
state={currentState}
|
||||
title='Facebook card'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -10,6 +10,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup';
|
|||
const LockSite: React.FC = () => {
|
||||
const {
|
||||
currentState,
|
||||
saveState,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
updateSetting,
|
||||
|
@ -76,6 +77,7 @@ const LockSite: React.FC = () => {
|
|||
return (
|
||||
<SettingGroup
|
||||
description='Enable protection with a simple shared password.'
|
||||
saveState={saveState}
|
||||
state={currentState}
|
||||
title='Make site private'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {ReactComponent as MagnifyingGlass} from '../../../admin-x-ds/assets/icon
|
|||
const Metadata: React.FC = () => {
|
||||
const {
|
||||
currentState,
|
||||
saveState,
|
||||
focusRef,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
|
@ -79,6 +80,7 @@ const Metadata: React.FC = () => {
|
|||
<SettingGroup
|
||||
description='Extra content for search engines'
|
||||
navid='metadata'
|
||||
saveState={saveState}
|
||||
state={currentState}
|
||||
title='Metadata'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -7,6 +7,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup';
|
|||
const PublicationLanguage: React.FC = () => {
|
||||
const {
|
||||
currentState,
|
||||
saveState,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
updateSetting,
|
||||
|
@ -54,6 +55,7 @@ const PublicationLanguage: React.FC = () => {
|
|||
return (
|
||||
<SettingGroup
|
||||
description="Set the language/locale which is used on your site"
|
||||
saveState={saveState}
|
||||
state={currentState}
|
||||
title="Publication Language"
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -37,6 +37,7 @@ const Hint: React.FC<HintProps> = ({timezone}) => {
|
|||
const TimeZone: React.FC = () => {
|
||||
const {
|
||||
currentState,
|
||||
saveState,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
updateSetting,
|
||||
|
@ -83,6 +84,7 @@ const TimeZone: React.FC = () => {
|
|||
return (
|
||||
<SettingGroup
|
||||
description='Set the time and date of your publication, used for all published posts'
|
||||
saveState={saveState}
|
||||
state={currentState}
|
||||
title='Site timezone'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -8,6 +8,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup';
|
|||
const TitleAndDescription: React.FC = () => {
|
||||
const {
|
||||
currentState,
|
||||
saveState,
|
||||
focusRef,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
|
@ -67,6 +68,7 @@ const TitleAndDescription: React.FC = () => {
|
|||
<SettingGroup
|
||||
description='The details used to identify your publication around the web'
|
||||
navid='title-and-description'
|
||||
saveState={saveState}
|
||||
state={currentState}
|
||||
title='Title & description'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -7,6 +7,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup';
|
|||
const Twitter: React.FC = () => {
|
||||
const {
|
||||
currentState,
|
||||
saveState,
|
||||
focusRef,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
|
@ -51,6 +52,7 @@ const Twitter: React.FC = () => {
|
|||
<SettingGroup
|
||||
description='Customize structured data of your site'
|
||||
navid='twitter'
|
||||
saveState={saveState}
|
||||
state={currentState}
|
||||
title='Twitter card'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -27,6 +27,7 @@ const COMMENTS_ENABLED_OPTIONS = [
|
|||
const Access: React.FC = () => {
|
||||
const {
|
||||
currentState,
|
||||
saveState,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
updateSetting,
|
||||
|
@ -99,6 +100,7 @@ const Access: React.FC = () => {
|
|||
return (
|
||||
<SettingGroup
|
||||
description='Set up default access options for subscription and posts'
|
||||
saveState={saveState}
|
||||
state={currentState}
|
||||
title='Access'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -8,6 +8,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup';
|
|||
const Analytics: React.FC = () => {
|
||||
const {
|
||||
currentState,
|
||||
saveState,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
updateSetting,
|
||||
|
@ -72,6 +73,7 @@ const Analytics: React.FC = () => {
|
|||
<SettingGroup
|
||||
description='Decide what data you collect from your members'
|
||||
hideEditButton={true}
|
||||
saveState={saveState}
|
||||
state={currentState}
|
||||
title='Analytics'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, {useEffect} from 'react';
|
||||
import {SaveState, TSettingGroupStates} from '../admin-x-ds/settings/SettingGroup';
|
||||
import {Setting, SettingValue} from '../types/api';
|
||||
import {SettingsContext} from '../components/providers/SettingsProvider';
|
||||
import {TSettingGroupStates} from '../admin-x-ds/settings/SettingGroup';
|
||||
import {useContext, useReducer, useRef, useState} from 'react';
|
||||
|
||||
interface LocalSetting extends Setting {
|
||||
|
@ -10,6 +10,7 @@ interface LocalSetting extends Setting {
|
|||
|
||||
export interface SettingGroupHook {
|
||||
currentState: TSettingGroupStates;
|
||||
saveState: SaveState;
|
||||
focusRef: React.RefObject<HTMLInputElement>;
|
||||
handleSave: () => void;
|
||||
handleCancel: () => void;
|
||||
|
@ -66,6 +67,8 @@ const useSettingGroup = (): SettingGroupHook => {
|
|||
// create a state to track the current state of the setting group
|
||||
const [currentState, setCurrentState] = useState<TSettingGroupStates>('view');
|
||||
|
||||
const [saveState, setSaveState] = useState<SaveState>('');
|
||||
|
||||
// focus the input field when the state changes to edit
|
||||
useEffect(() => {
|
||||
if (currentState === 'edit' && focusRef.current) {
|
||||
|
@ -73,8 +76,17 @@ const useSettingGroup = (): SettingGroupHook => {
|
|||
}
|
||||
}, [currentState]);
|
||||
|
||||
// Reset saved state after 2 seconds
|
||||
useEffect(() => {
|
||||
if (saveState === 'saved') {
|
||||
setTimeout(() => {
|
||||
setSaveState('');
|
||||
}, 2000);
|
||||
}
|
||||
}, [saveState]);
|
||||
|
||||
// function to save the changed settings via API
|
||||
const handleSave = () => {
|
||||
const handleSave = async () => {
|
||||
const changedSettings = localSettings?.filter(setting => setting.dirty)
|
||||
?.map((setting) => {
|
||||
return {
|
||||
|
@ -82,9 +94,12 @@ const useSettingGroup = (): SettingGroupHook => {
|
|||
value: setting.value
|
||||
};
|
||||
});
|
||||
if (changedSettings.length) {
|
||||
saveSettings?.(changedSettings);
|
||||
if (!changedSettings?.length) {
|
||||
return;
|
||||
}
|
||||
setSaveState('saving');
|
||||
await saveSettings?.(changedSettings);
|
||||
setSaveState('saved');
|
||||
setCurrentState('view');
|
||||
};
|
||||
|
||||
|
@ -123,6 +138,7 @@ const useSettingGroup = (): SettingGroupHook => {
|
|||
|
||||
return {
|
||||
currentState,
|
||||
saveState,
|
||||
focusRef,
|
||||
handleSave,
|
||||
handleCancel,
|
||||
|
|
Loading…
Add table
Reference in a new issue