| @@ -35,11 +35,24 @@ | ||
| 35 | 35 | |
| 36 | 36 | return known.includes( requested ) ? requested : tabs[ 0 ]?.id; |
| 37 | 37 | } |
| 38 | 38 | |
| 39 | -export default function App() { | |
| 39 | +export default function App( { scope = 'site' } ) { | |
| 40 | + const isNetwork = scope === 'network'; | |
| 41 | + | |
| 42 | + // One route or the other. Everything below is the same screen either way -- | |
| 43 | + // the network route hands back a schema without the two settings that mean | |
| 44 | + // something different on every site, so there is no second field list here | |
| 45 | + // that could fall behind the first. | |
| 46 | + const route = isNetwork | |
| 47 | + ? '/cryptx/v1/network-defaults' | |
| 48 | + : '/cryptx/v1/settings'; | |
| 49 | + | |
| 40 | 50 | const [ schema, setSchema ] = useState( null ); |
| 41 | 51 | const [ values, setValues ] = useState( null ); |
| 52 | + // Only used by the secrets card on the Advanced tab, and read once: a | |
| 53 | + // rotation nobody meant to trigger is otherwise invisible. | |
| 54 | + const [ rotatedAt, setRotatedAt ] = useState( '' ); | |
| 42 | 55 | const [ savedValues, setSavedValues ] = useState( null ); |
| 43 | 56 | const [ activeTab, setActiveTab ] = useState( null ); |
| 44 | 57 | const [ notice, setNotice ] = useState( null ); |
| 45 | 58 | const [ isSaving, setSaving ] = useState( false ); |
| @@ -46,13 +59,14 @@ | ||
| 46 | 59 | const [ loadError, setLoadError ] = useState( null ); |
| 47 | 60 | const [ confirmReset, setConfirmReset ] = useState( false ); |
| 48 | 61 | |
| 49 | 62 | useEffect( () => { |
| 50 | - apiFetch( { path: '/cryptx/v1/settings' } ) | |
| 63 | + apiFetch( { path: route } ) | |
| 51 | 64 | .then( ( response ) => { |
| 52 | 65 | setSchema( response.schema ); |
| 53 | 66 | setValues( response.values ); |
| 54 | 67 | setSavedValues( response.values ); |
| 68 | + setRotatedAt( response.secretsRotatedAt || '' ); | |
| 55 | 69 | setActiveTab( tabFromLocation( response.schema.tabs ) ); |
| 56 | 70 | } ) |
| 57 | 71 | .catch( ( error ) => { |
| 58 | 72 | setLoadError( |
| @@ -59,9 +73,9 @@ | ||
| 59 | 73 | error?.message || |
| 60 | 74 | __( 'The settings could not be loaded.', 'cryptx' ) |
| 61 | 75 | ); |
| 62 | 76 | } ); |
| 63 | - }, [] ); | |
| 77 | + }, [ route ] ); | |
| 64 | 78 | |
| 65 | 79 | const isDirty = useMemo( () => { |
| 66 | 80 | if ( ! values || ! savedValues ) { |
| 67 | 81 | return false; |
| @@ -118,9 +132,9 @@ | ||
| 118 | 132 | setSaving( true ); |
| 119 | 133 | setNotice( null ); |
| 120 | 134 | |
| 121 | 135 | apiFetch( { |
| 122 | - path: '/cryptx/v1/settings', | |
| 136 | + path: route, | |
| 123 | 137 | method: 'POST', |
| 124 | 138 | data: { values }, |
| 125 | 139 | } ) |
| 126 | 140 | .then( ( response ) => { |
| @@ -136,9 +150,9 @@ | ||
| 136 | 150 | __( 'The settings could not be saved.', 'cryptx' ), |
| 137 | 151 | } ); |
| 138 | 152 | } ) |
| 139 | 153 | .finally( () => setSaving( false ) ); |
| 140 | - }, [ values ] ); | |
| 154 | + }, [ values, route ] ); | |
| 141 | 155 | |
| 142 | 156 | const reset = useCallback( () => { |
| 143 | 157 | setConfirmReset( false ); |
| 144 | 158 | setSaving( true ); |
| @@ -193,14 +207,23 @@ | ||
| 193 | 207 | |
| 194 | 208 | return ( |
| 195 | 209 | <div className="cryptx-settings"> |
| 196 | 210 | <header className="cryptx-settings__header"> |
| 197 | - <h1>{ __( 'CryptX', 'cryptx' ) }</h1> | |
| 211 | + <h1> | |
| 212 | + { isNetwork | |
| 213 | + ? __( 'CryptX network defaults', 'cryptx' ) | |
| 214 | + : __( 'CryptX', 'cryptx' ) } | |
| 215 | + </h1> | |
| 198 | 216 | <p className="cryptx-settings__intro"> |
| 199 | - { __( | |
| 200 | - 'CryptX hides email addresses in your pages from spam bots while keeping them usable for your visitors.', | |
| 201 | - 'cryptx' | |
| 202 | - ) } | |
| 217 | + { isNetwork | |
| 218 | + ? __( | |
| 219 | + 'What a newly created site starts with. Sites that already exist are never changed by this — every site keeps its own settings, and a site administrator can change theirs at any time. Two settings are missing here on purpose: excluded posts and the uploaded image refer to things that exist on one site only. One deserves a second look before you set it: an address on the list under Exceptions is left readable, and a network default puts it on every site created from now on.', | |
| 220 | + 'cryptx' | |
| 221 | + ) | |
| 222 | + : __( | |
| 223 | + 'CryptX hides email addresses in your pages from spam bots while keeping them usable for your visitors.', | |
| 224 | + 'cryptx' | |
| 225 | + ) } | |
| 203 | 226 | </p> |
| 204 | 227 | </header> |
| 205 | 228 | |
| 206 | 229 | <Tabs tabs={ tabs } active={ current.id } onSelect={ selectTab } /> |
| @@ -226,8 +249,10 @@ | ||
| 226 | 249 | tab={ current.id } |
| 227 | 250 | fields={ schema.fields } |
| 228 | 251 | values={ values } |
| 229 | 252 | onChange={ setValue } |
| 253 | + rotatedAt={ rotatedAt } | |
| 254 | + isNetwork={ isNetwork } | |
| 230 | 255 | /> |
| 231 | 256 | ) } |
| 232 | 257 | </main> |
| 233 | 258 | |
| @@ -249,19 +274,25 @@ | ||
| 249 | 274 | > |
| 250 | 275 | { __( 'Save changes', 'cryptx' ) } |
| 251 | 276 | </Button> |
| 252 | 277 | </FlexItem> |
| 253 | - <FlexItem> | |
| 254 | - <Button | |
| 255 | - variant="tertiary" | |
| 256 | - isDestructive | |
| 257 | - onClick={ () => setConfirmReset( true ) } | |
| 258 | - disabled={ isSaving } | |
| 259 | - __next40pxDefaultSize | |
| 260 | - > | |
| 261 | - { __( 'Restore defaults', 'cryptx' ) } | |
| 262 | - </Button> | |
| 263 | - </FlexItem> | |
| 278 | + { /* Only on the site screen: this route resets the | |
| 279 | + settings of one site, which is not what a network | |
| 280 | + administrator looking at the defaults would | |
| 281 | + expect it to mean. */ } | |
| 282 | + { ! isNetwork && ( | |
| 283 | + <FlexItem> | |
| 284 | + <Button | |
| 285 | + variant="tertiary" | |
| 286 | + isDestructive | |
| 287 | + onClick={ () => setConfirmReset( true ) } | |
| 288 | + disabled={ isSaving } | |
| 289 | + __next40pxDefaultSize | |
| 290 | + > | |
| 291 | + { __( 'Restore defaults', 'cryptx' ) } | |
| 292 | + </Button> | |
| 293 | + </FlexItem> | |
| 294 | + ) } | |
| 264 | 295 | </Flex> |
| 265 | 296 | { /* The state is said in words as well as shown by the |
| 266 | 297 | button, because a disabled button on its own reads as |
| 267 | 298 | "broken" rather than as "nothing to do". */ } |