diff --git a/Components/UI/Switch/Switch.module.css b/Components/UI/Switch/Switch.module.css index 40f7632..90a0166 100644 --- a/Components/UI/Switch/Switch.module.css +++ b/Components/UI/Switch/Switch.module.css @@ -1,157 +1,84 @@ +/* Wrapper styles */ .switchWrapper { display: flex; flex-direction: column; + gap: 8px; margin-bottom: 20px; } +/* Switch container */ .switch { display: flex; + align-items: center; + gap: 10px; } -.switchDescription { - display: flex; - margin: 8px 0px 0px 0px; - color: #6c737f; - font-size: 0.875rem; -} - -.pureMaterialSwitch { - z-index: 0; +/* Label */ +.switchLabel { + display: inline-flex; + align-items: center; + gap: 10px; + cursor: pointer; position: relative; - display: inline-block; - color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.87); - font-family: var( - --pure-material-font, - 'Roboto', - 'Segoe UI', - BlinkMacSystemFont, - system-ui, - -apple-system - ); - font-size: 16px; - line-height: 1.5; + user-select: none; } /* Input */ -.pureMaterialSwitch > input { - appearance: none; - -moz-appearance: none; - -webkit-appearance: none; - z-index: -1; - position: absolute; - right: 6px; - top: -8px; - display: block; - margin: 0; - border-radius: 50%; +.switchLabel input { + display: none; +} + +/* Slider */ +.switchSlider { + position: relative; width: 40px; - height: 40px; - background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38); - outline: none; - opacity: 0; - transform: scale(1); - pointer-events: none; - transition: - opacity 0.3s 0.1s, - transform 0.2s 0.1s; + height: 20px; + background: #ccc; + border-radius: 12px; + transition: #ccc 0.3s ease; } -/* Span */ -.pureMaterialSwitch > span { - display: inline-block; - width: 100%; - cursor: pointer; - font-size: 1rem; - font-weight: 500; - color: #494b7a; -} - -/* Track */ -.pureMaterialSwitch > span::before { - content: ''; - float: right; - display: inline-block; - margin: 5px 0 5px 30px; - border-radius: 7px; - width: 36px; - height: 14px; - background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38); - vertical-align: top; - transition: - background-color 0.2s, - opacity 0.2s; -} - -/* Thumb */ -.pureMaterialSwitch > span::after { +.switchSlider::after { content: ''; position: absolute; top: 2px; - right: 16px; + left: 2px; + width: 16px; + height: 16px; + background: #fff; border-radius: 50%; - width: 20px; - height: 20px; - background-color: rgb(var(--pure-material-onprimary-rgb, 255, 255, 255)); - box-shadow: - 0 3px 1px -2px rgba(0, 0, 0, 0.2), - 0 2px 2px 0 rgba(0, 0, 0, 0.14), - 0 1px 5px 0 rgba(0, 0, 0, 0.12); - transition: - background-color 0.2s, - transform 0.2s; + transition: transform 0.3s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } -/* Checked */ -.pureMaterialSwitch > input:checked { - right: -10px; - background-color: rgb(var(--pure-material-primary-rgb, 109, 74, 255)); +/* Checked styles */ +.switchLabel input:checked + .switchSlider { + background: #704dff; } -.pureMaterialSwitch > input:checked + span::before { - background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6); +.switchLabel input:checked + .switchSlider::after { + transform: translateX(20px); } -.pureMaterialSwitch > input:checked + span::after { - background-color: rgb(var(--pure-material-primary-rgb, 109, 74, 255)); - transform: translateX(16px); +/* Disabled styles */ +.switchLabel input:disabled + .switchSlider { + background: #e0e0e0; } -/* Active */ -.pureMaterialSwitch > input:active { - opacity: 1; - transform: scale(0); - transition: - transform 0s, - opacity 0s; +.switchLabel input:disabled + .switchSlider::after { + background: #bdbdbd; } -.pureMaterialSwitch > input:active + span::before { - background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6); +/* Switch text */ +.switchText { + font-size: 1rem; + color: #494b7a; + font-weight: 500; } -.pureMaterialSwitch > input:checked:active + span::before { - background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38); +/* Description */ +.switchDescription { + font-size: 0.875rem; + color: #6c737f; + margin-top: 4px; } - -/* Disabled */ -.pureMaterialSwitch > input:disabled + span { - cursor: wait; -} - -/* .pureMaterialSwitch > input:disabled { - opacity: 0; -} - -.pureMaterialSwitch > input:disabled + span { - color: rgb(var(--pure-material-onsurface-rgb, 0, 0, 0)); - opacity: 0.38; - cursor: default; -} - -.pureMaterialSwitch > input:disabled + span::before { - background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38); -} - -.pureMaterialSwitch > input:checked:disabled + span::before { - background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6); -} */ diff --git a/Components/UI/Switch/Switch.tsx b/Components/UI/Switch/Switch.tsx index 89efae1..a4f2909 100644 --- a/Components/UI/Switch/Switch.tsx +++ b/Components/UI/Switch/Switch.tsx @@ -1,35 +1,45 @@ //Lib import { Optional } from '~/types'; import classes from './Switch.module.css'; +import { SpinnerCircularFixed } from 'spinners-react'; type SwitchProps = { switchName: string; switchDescription: string; checked: Optional; disabled: boolean; + loading?: boolean; onChange: (checked: boolean) => void; }; export default function Switch(props: SwitchProps) { return ( - <> -
-
-
); } diff --git a/Containers/UserSettings/AppriseAlertSettings/AppriseAlertSettings.tsx b/Containers/UserSettings/AppriseAlertSettings/AppriseAlertSettings.tsx index c55a0e2..b3dcd80 100644 --- a/Containers/UserSettings/AppriseAlertSettings/AppriseAlertSettings.tsx +++ b/Containers/UserSettings/AppriseAlertSettings/AppriseAlertSettings.tsx @@ -1,22 +1,27 @@ //Lib -import { useEffect } from 'react'; -import { toast } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; -import classes from '../UserSettings.module.css'; -import { useState } from 'react'; -import { SpinnerCircularFixed } from 'spinners-react'; import { IconExternalLink } from '@tabler/icons-react'; import Link from 'next/link'; +import { useEffect, useState } from 'react'; +import { toast, ToastOptions } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; +import { SpinnerCircularFixed } from 'spinners-react'; +import classes from '../UserSettings.module.css'; //Components -import Error from '../../../Components/UI/Error/Error'; -import Switch from '../../../Components/UI/Switch/Switch'; -import AppriseURLs from './AppriseURLs/AppriseURLs'; +import Error from '~/Components/UI/Error/Error'; +import Switch from '~/Components/UI/Switch/Switch'; +import { useFormStatus } from '~/hooks/useFormStatus'; +import { Optional } from '~/types'; import AppriseMode from './AppriseMode/AppriseMode'; +import AppriseURLs from './AppriseURLs/AppriseURLs'; + +type AppriseAlertDataForm = { + appriseAlert: boolean; +}; export default function AppriseAlertSettings() { //Var - const toastOptions = { + const toastOptions: ToastOptions = { position: 'top-right', autoClose: 5000, hideProgressBar: false, @@ -24,16 +29,14 @@ export default function AppriseAlertSettings() { pauseOnHover: true, draggable: true, progress: undefined, - //Callback > re-enabled button after notification. - onClose: () => setDisabled(false), }; + const { error, handleError, clearError } = useFormStatus(); + ////State - const [checkIsLoading, setCheckIsLoading] = useState(true); - const [error, setError] = useState(); - const [disabled, setDisabled] = useState(false); - const [checked, setChecked] = useState(); - const [testIsLoading, setTestIsLoading] = useState(false); + const [isSendingTestNotification, setIsSendingTestNotification] = useState(false); + const [isSwitchDisabled, setIsSwitchDisabled] = useState(true); + const [isAlertEnabled, setIsAlertEnabled] = useState>(undefined); const [info, setInfo] = useState(false); ////LifeCycle @@ -48,12 +51,14 @@ export default function AppriseAlertSettings() { 'Content-type': 'application/json', }, }); - setChecked((await response.json()).appriseAlert); - setCheckIsLoading(false); + + const data: AppriseAlertDataForm = await response.json(); + setIsAlertEnabled(data.appriseAlert); + setIsSwitchDisabled(false); } catch (error) { - setError('Fetching apprise alert setting failed. Contact your administrator.'); - console.log('Fetching apprise alert setting failed.'); - setCheckIsLoading(false); + setIsSwitchDisabled(true); + setIsAlertEnabled(false); + handleError('Fetching apprise alert setting failed.'); } }; getAppriseAlert(); @@ -61,11 +66,9 @@ export default function AppriseAlertSettings() { ////Functions //Switch to enable/disable Apprise notifications - const onChangeSwitchHandler = async (data) => { - //Remove old error - setError(); - //Disabled button - setDisabled(true); + const onChangeSwitchHandler = async (data: AppriseAlertDataForm) => { + clearError(); + setIsSwitchDisabled(true); await fetch('/api/account/updateAppriseAlert', { method: 'PUT', headers: { @@ -77,36 +80,28 @@ export default function AppriseAlertSettings() { console.log(response); if (response.ok) { if (data.appriseAlert) { - setChecked(!checked); + setIsAlertEnabled(!isAlertEnabled); + setIsSwitchDisabled(false); toast.success('Apprise notifications enabled.', toastOptions); } else { - setChecked(!checked); + setIsAlertEnabled(!isAlertEnabled); + setIsSwitchDisabled(false); toast.success('Apprise notifications disabled.', toastOptions); } } else { - setError('Update apprise alert setting failed.'); - setTimeout(() => { - setError(); - setDisabled(false); - }, 4000); + handleError('Update apprise alert setting failed.'); } }) .catch((error) => { + handleError('Update Apprise failed. Contact your administrator.'); console.log(error); - setError('Update Apprise failed. Contact your administrator.'); - setTimeout(() => { - setError(); - setDisabled(false); - }, 4000); }); }; //Send Apprise test notification to services const onSendTestAppriseHandler = async () => { - //Loading - setTestIsLoading(true); - //Remove old error - setError(); + clearError(); + setIsSendingTestNotification(true); try { const response = await fetch('/api/account/sendTestApprise', { method: 'POST', @@ -116,23 +111,21 @@ export default function AppriseAlertSettings() { body: JSON.stringify({ sendTestApprise: true }), }); const result = await response.json(); + if (!response.ok) { - setTestIsLoading(false); - setError(result.message); + setIsSendingTestNotification(false); + handleError(result.message); } else { - setTestIsLoading(false); + setIsSendingTestNotification(false); setInfo(true); setTimeout(() => { setInfo(false); }, 4000); } } catch (error) { - setTestIsLoading(false); + setIsSendingTestNotification(false); console.log(error); - setError('Send notification failed. Contact your administrator.'); - setTimeout(() => { - setError(); - }, 4000); + handleError('Send notification failed. Contact your administrator.'); } }; @@ -153,51 +146,42 @@ export default function AppriseAlertSettings() {
- {/* NOTIFY SWITCH */} - {checkIsLoading ? ( - - ) : ( - onChangeSwitchHandler({ appriseAlert: e })} - /> - )} - {/* APPRISE SERVICES URLS */} - - {/* APPRISE MODE SELECTION */} - - {/* APPRISE TEST BUTTON */} - {testIsLoading ? ( - - ) : ( - - )} - {info && ( - - Notification successfully sent. - + onChangeSwitchHandler({ appriseAlert: e })} + /> + {isAlertEnabled && ( + <> + + + {isSendingTestNotification ? ( + + ) : ( + + )} + {info && ( + + Notification successfully sent. + + )} + )} {error && }
diff --git a/Containers/UserSettings/UserSettings.module.css b/Containers/UserSettings/UserSettings.module.css index 459e774..9a3859a 100644 --- a/Containers/UserSettings/UserSettings.module.css +++ b/Containers/UserSettings/UserSettings.module.css @@ -387,7 +387,7 @@ .headerFormAppriseUrls { font-weight: 500; color: #494b7a; - margin: 40px 0px 10px 0px; + margin-bottom: 10px; display: flex; padding-right: 5px; }