mirror of
https://github.com/Ravinou/borgwarehouse
synced 2026-03-14 14:25:46 +01:00
refactor: ⚡ remove spinners for nprogress
This commit is contained in:
parent
799bf03cd5
commit
daa199dfb0
12 changed files with 202 additions and 242 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { Optional } from '~/types';
|
||||
import classes from './Switch.module.css';
|
||||
import { SpinnerCircularFixed } from 'spinners-react';
|
||||
import { useLoader } from '~/contexts/LoaderContext';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type SwitchProps = {
|
||||
switchName: string;
|
||||
|
|
@ -12,29 +13,29 @@ type SwitchProps = {
|
|||
};
|
||||
|
||||
export default function Switch(props: SwitchProps) {
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
useEffect(() => {
|
||||
if (props.loading) {
|
||||
start();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}, [props.loading, start, stop]);
|
||||
|
||||
return (
|
||||
<div className={classes.switchWrapper}>
|
||||
<div className={classes.switch}>
|
||||
<label className={classes.switchLabel}>
|
||||
{props.loading ? (
|
||||
<SpinnerCircularFixed
|
||||
size={24}
|
||||
thickness={120}
|
||||
speed={100}
|
||||
color='#704dff'
|
||||
secondaryColor='#e0dcfc'
|
||||
<>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={props.checked || false}
|
||||
disabled={props.disabled}
|
||||
onChange={(e) => props.onChange(e.target.checked)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={props.checked || false}
|
||||
disabled={props.disabled}
|
||||
onChange={(e) => props.onChange(e.target.checked)}
|
||||
/>
|
||||
<span className={classes.switchSlider}></span>
|
||||
</>
|
||||
)}
|
||||
<span className={classes.switchSlider}></span>
|
||||
</>
|
||||
<span className={classes.switchText}>{props.switchName}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import classes from './RepoManage.module.css';
|
||||
import { IconAlertCircle, IconX } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { IconAlertCircle, IconExternalLink, IconX } from '@tabler/icons-react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import Select from 'react-select';
|
||||
import { toast, ToastOptions } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { SpinnerDotted } from 'spinners-react';
|
||||
import Select from 'react-select';
|
||||
import Link from 'next/link';
|
||||
import { IconExternalLink } from '@tabler/icons-react';
|
||||
import { alertOptions, Repository, Optional } from '~/types';
|
||||
import { useLoader } from '~/contexts/LoaderContext';
|
||||
import { alertOptions, Optional, Repository } from '~/types';
|
||||
import classes from './RepoManage.module.css';
|
||||
|
||||
type RepoManageProps = {
|
||||
mode: 'add' | 'edit';
|
||||
|
|
@ -53,20 +52,25 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
|
||||
const [deleteDialog, setDeleteDialog] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
//router.query.slug is undefined for few milliseconds on first render for a direct URL access (https://github.com/vercel/next.js/discussions/11484).
|
||||
//If I call repoManage with edit mode (props), i'm firstly waiting that router.query.slug being available before rendering.
|
||||
if (props.mode === 'edit') {
|
||||
if (!router.query.slug) {
|
||||
return <SpinnerDotted size={30} thickness={100} speed={180} color='rgba(109, 74, 255, 1)' />;
|
||||
start();
|
||||
return;
|
||||
} else if (!targetRepo) {
|
||||
stop();
|
||||
router.push('/404');
|
||||
}
|
||||
}
|
||||
|
||||
//Delete a repo
|
||||
const deleteHandler = async (repositoryName?: string) => {
|
||||
start();
|
||||
if (!repositoryName) {
|
||||
stop();
|
||||
toast.error('Repository name not found', toastOptions);
|
||||
router.replace('/');
|
||||
return;
|
||||
|
|
@ -105,6 +109,9 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
toast.error('An error has occurred', toastOptions);
|
||||
router.replace('/');
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
stop();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -141,10 +148,11 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
|
||||
//Form submit Handler for ADD or EDIT a repo
|
||||
const formSubmitHandler = async (dataForm: DataForm) => {
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
start();
|
||||
//Verify that the SSH key is unique
|
||||
if (!(await isSSHKeyUnique(dataForm.sshkey))) {
|
||||
stop();
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
|
@ -182,6 +190,10 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
toast.error('An error has occurred', toastOptions);
|
||||
router.replace('/');
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
stop();
|
||||
setIsLoading(false);
|
||||
});
|
||||
//EDIT a repo
|
||||
} else if (props.mode == 'edit') {
|
||||
|
|
@ -219,6 +231,10 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
toast.error('An error has occurred', toastOptions);
|
||||
router.replace('/');
|
||||
console.log(error);
|
||||
})
|
||||
.finally(() => {
|
||||
stop();
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -254,24 +270,24 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
<div>The data will not be recoverable and it will not be possible to go back.</div>
|
||||
</div>
|
||||
<div className={classes.deleteDialogButtonWrapper}>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={30} thickness={150} speed={100} color='#6d4aff' />
|
||||
) : (
|
||||
<>
|
||||
<button onClick={props.closeHandler} className={classes.cancelButton}>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
deleteHandler(targetRepo?.repositoryName);
|
||||
setIsLoading(true);
|
||||
}}
|
||||
className={classes.deleteButton}
|
||||
>
|
||||
Yes, delete it !
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<button
|
||||
onClick={props.closeHandler}
|
||||
disabled={isLoading}
|
||||
className={classes.cancelButton}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
deleteHandler(targetRepo?.repositoryName);
|
||||
setIsLoading(true);
|
||||
}}
|
||||
className={classes.deleteButton}
|
||||
>
|
||||
Yes, delete it !
|
||||
</button>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -450,21 +466,15 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
marginTop: '2rem',
|
||||
}}
|
||||
>
|
||||
<SpinnerDotted size={40} thickness={150} speed={100} color='#6d4aff' />
|
||||
</div>
|
||||
) : (
|
||||
<button type='submit' className='defaultButton' disabled={!isValid || isSubmitting}>
|
||||
{props.mode == 'edit' && 'Edit'}
|
||||
{props.mode == 'add' && 'Add'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
type='submit'
|
||||
className='defaultButton'
|
||||
disabled={!isValid || isSubmitting || isLoading}
|
||||
>
|
||||
{props.mode == 'edit' && 'Edit'}
|
||||
{props.mode == 'add' && 'Add'}
|
||||
</button>
|
||||
</form>
|
||||
{props.mode == 'edit' ? (
|
||||
<button className={classes.littleDeleteButton} onClick={() => setDeleteDialog(true)}>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ 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 { useFormStatus } from '~/hooks';
|
||||
import { useLoader } from '~/contexts/LoaderContext';
|
||||
import { Optional } from '~/types';
|
||||
import AppriseMode from './AppriseMode/AppriseMode';
|
||||
import AppriseURLs from './AppriseURLs/AppriseURLs';
|
||||
|
|
@ -29,7 +27,7 @@ export default function AppriseAlertSettings() {
|
|||
progress: undefined,
|
||||
};
|
||||
|
||||
const { error, handleError, clearError } = useFormStatus();
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
////State
|
||||
const [isSendingTestNotification, setIsSendingTestNotification] = useState(false);
|
||||
|
|
@ -56,7 +54,7 @@ export default function AppriseAlertSettings() {
|
|||
} catch (error) {
|
||||
setIsSwitchDisabled(true);
|
||||
setIsAlertEnabled(false);
|
||||
handleError('Fetching apprise alert setting failed.');
|
||||
toast.error('Fetching Apprise alert setting failed', toastOptions);
|
||||
}
|
||||
};
|
||||
getAppriseAlert();
|
||||
|
|
@ -65,7 +63,7 @@ export default function AppriseAlertSettings() {
|
|||
////Functions
|
||||
//Switch to enable/disable Apprise notifications
|
||||
const onChangeSwitchHandler = async (data: AppriseAlertDataForm) => {
|
||||
clearError();
|
||||
start();
|
||||
setIsSwitchDisabled(true);
|
||||
await fetch('/api/v1/notif/apprise/alert', {
|
||||
method: 'PUT',
|
||||
|
|
@ -82,20 +80,21 @@ export default function AppriseAlertSettings() {
|
|||
toastOptions
|
||||
);
|
||||
} else {
|
||||
handleError('Update apprise alert setting failed.');
|
||||
toast.error('Update Apprise failed', toastOptions);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
handleError('Update Apprise failed. Contact your administrator.');
|
||||
.catch(() => {
|
||||
toast.error('Update Apprise failed', toastOptions);
|
||||
})
|
||||
.finally(() => {
|
||||
stop();
|
||||
setIsSwitchDisabled(false);
|
||||
});
|
||||
};
|
||||
|
||||
//Send Apprise test notification to services
|
||||
const onSendTestAppriseHandler = async () => {
|
||||
clearError();
|
||||
start();
|
||||
setIsSendingTestNotification(true);
|
||||
try {
|
||||
const response = await fetch('/api/v1/notif/apprise/test', {
|
||||
|
|
@ -104,18 +103,18 @@ export default function AppriseAlertSettings() {
|
|||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setIsSendingTestNotification(false);
|
||||
handleError(result.message);
|
||||
toast.error(result.message, toastOptions);
|
||||
} else {
|
||||
setIsSendingTestNotification(false);
|
||||
setInfo(true);
|
||||
setTimeout(() => {
|
||||
setInfo(false);
|
||||
}, 4000);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Sending test notification failed', toastOptions);
|
||||
} finally {
|
||||
stop();
|
||||
setIsSendingTestNotification(false);
|
||||
handleError('Send notification failed');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -148,24 +147,14 @@ export default function AppriseAlertSettings() {
|
|||
<>
|
||||
<AppriseURLs />
|
||||
<AppriseMode />
|
||||
{isSendingTestNotification ? (
|
||||
<SpinnerCircularFixed
|
||||
style={{ marginTop: '20px' }}
|
||||
size={30}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
style={{ marginTop: '20px' }}
|
||||
className='defaultButton'
|
||||
onClick={() => onSendTestAppriseHandler()}
|
||||
>
|
||||
Send a test notification
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
disabled={isSendingTestNotification}
|
||||
style={{ marginTop: '20px' }}
|
||||
className='defaultButton'
|
||||
onClick={() => onSendTestAppriseHandler()}
|
||||
>
|
||||
Send a test notification
|
||||
</button>
|
||||
{info && (
|
||||
<span style={{ marginLeft: '10px', color: '#119300' }}>
|
||||
Notification successfully sent.
|
||||
|
|
@ -173,7 +162,6 @@ export default function AppriseAlertSettings() {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
{error && <Error message={error} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { useEffect } from 'react';
|
||||
import classes from '../../UserSettings.module.css';
|
||||
import { useState } from 'react';
|
||||
import { SpinnerCircularFixed } from 'spinners-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Optional, AppriseModeEnum, AppriseModeDTO } from '~/types';
|
||||
import { AppriseModeDTO, AppriseModeEnum, Optional } from '~/types';
|
||||
import classes from '../../UserSettings.module.css';
|
||||
|
||||
//Components
|
||||
import Error from '~/Components/UI/Error/Error';
|
||||
import { useLoader } from '~/contexts/LoaderContext';
|
||||
import { useFormStatus } from '~/hooks';
|
||||
|
||||
type AppriseModeDataForm = {
|
||||
|
|
@ -19,10 +18,10 @@ export default function AppriseMode() {
|
|||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<AppriseModeDataForm>({ mode: 'onBlur' });
|
||||
} = useForm<AppriseModeDataForm>({ mode: 'onChange' });
|
||||
|
||||
const { isLoading, isSaved, error, setIsLoading, handleSuccess, handleError, clearError } =
|
||||
useFormStatus();
|
||||
const { error, setIsLoading, handleSuccess, handleError, clearError } = useFormStatus();
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
const [displayStatelessURL, setDisplayStatelessURL] = useState<boolean>(false);
|
||||
const [appriseMode, setAppriseMode] = useState<Optional<AppriseModeEnum>>(
|
||||
|
|
@ -61,6 +60,7 @@ export default function AppriseMode() {
|
|||
const modeFormSubmitHandler = async (data: AppriseModeDataForm) => {
|
||||
clearError();
|
||||
setIsLoading(true);
|
||||
start();
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/v1/notif/apprise/mode', {
|
||||
|
|
@ -79,6 +79,9 @@ export default function AppriseMode() {
|
|||
}
|
||||
} catch (error) {
|
||||
handleError('The Apprise mode change has failed');
|
||||
} finally {
|
||||
stop();
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -87,23 +90,9 @@ export default function AppriseMode() {
|
|||
{/* APPRISE MODE SELECTION */}
|
||||
<div className={classes.headerFormAppriseUrls}>
|
||||
<div style={{ margin: '0px 10px 0px 0px' }}>Apprise mode</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
{isLoading && (
|
||||
<SpinnerCircularFixed
|
||||
size={18}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
)}
|
||||
{isSaved && (
|
||||
<div className={classes.formIsSavedMessage}>✅ Apprise mode has been saved.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{error && <Error message={error} />}
|
||||
<form className={classes.bwForm} onBlur={handleSubmit(modeFormSubmitHandler)}>
|
||||
<form className={classes.bwForm} onChange={handleSubmit(modeFormSubmitHandler)}>
|
||||
<div className='radio-group'>
|
||||
<label style={{ marginRight: '50px' }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { useEffect } from 'react';
|
||||
import classes from '../../UserSettings.module.css';
|
||||
import { useState } from 'react';
|
||||
import { SpinnerCircularFixed } from 'spinners-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Optional, AppriseServicesDTO } from '~/types';
|
||||
import { AppriseServicesDTO, Optional } from '~/types';
|
||||
import classes from '../../UserSettings.module.css';
|
||||
|
||||
//Components
|
||||
import Error from '~/Components/UI/Error/Error';
|
||||
import { useLoader } from '~/contexts/LoaderContext';
|
||||
import { useFormStatus } from '~/hooks';
|
||||
|
||||
type AppriseURLsDataForm = {
|
||||
|
|
@ -20,8 +19,8 @@ export default function AppriseURLs() {
|
|||
formState: { errors },
|
||||
} = useForm<AppriseURLsDataForm>({ mode: 'onBlur' });
|
||||
|
||||
const { isLoading, isSaved, error, setIsLoading, handleSuccess, handleError, clearError } =
|
||||
useFormStatus();
|
||||
const { isSaved, error, handleSuccess, handleError, clearError } = useFormStatus();
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
const [appriseServicesList, setAppriseServicesList] = useState<Optional<string>>();
|
||||
const [fetchError, setFetchError] = useState<Optional<boolean>>();
|
||||
|
|
@ -53,11 +52,12 @@ export default function AppriseURLs() {
|
|||
//Form submit handler to modify Apprise services
|
||||
const urlsFormSubmitHandler = async (data: AppriseURLsDataForm) => {
|
||||
clearError();
|
||||
start();
|
||||
if (fetchError) {
|
||||
handleError('Cannot update Apprise services. Failed to fetch the initial list.');
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/v1/notif/apprise/services', {
|
||||
|
|
@ -76,6 +76,8 @@ export default function AppriseURLs() {
|
|||
}
|
||||
} catch (error) {
|
||||
handleError('Failed to update your Apprise services.');
|
||||
} finally {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -85,15 +87,6 @@ export default function AppriseURLs() {
|
|||
<div className={classes.headerFormAppriseUrls}>
|
||||
<div style={{ marginRight: '10px' }}>Apprise URLs</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
{isLoading && (
|
||||
<SpinnerCircularFixed
|
||||
size={18}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
)}
|
||||
{isSaved && (
|
||||
<div className={classes.formIsSavedMessage}>
|
||||
✅ Apprise configuration has been saved.
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ 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 { useLoader } from '~/contexts/LoaderContext';
|
||||
import { EmailAlertDTO, Optional } from '~/types';
|
||||
import classes from '../UserSettings.module.css';
|
||||
import { Optional, EmailAlertDTO } from '~/types';
|
||||
|
||||
//Components
|
||||
import Error from '~/Components/UI/Error/Error';
|
||||
|
|
@ -26,6 +26,7 @@ export default function EmailAlertSettings() {
|
|||
};
|
||||
|
||||
const { error, handleError, clearError } = useFormStatus();
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
////State
|
||||
const [isSendingTestNotification, setIsSendingTestNotification] = useState(false);
|
||||
|
|
@ -61,6 +62,7 @@ export default function EmailAlertSettings() {
|
|||
//Switch to enable/disable Email notifications
|
||||
const onChangeSwitchHandler = async (data: EmailAlertDTO) => {
|
||||
clearError();
|
||||
start();
|
||||
setIsSwitchDisabled(true);
|
||||
await fetch('/api/v1/notif/email/alert', {
|
||||
method: 'PUT',
|
||||
|
|
@ -84,6 +86,7 @@ export default function EmailAlertSettings() {
|
|||
handleError('Update email alert setting failed.');
|
||||
})
|
||||
.finally(() => {
|
||||
stop();
|
||||
setIsSwitchDisabled(false);
|
||||
});
|
||||
};
|
||||
|
|
@ -91,6 +94,7 @@ export default function EmailAlertSettings() {
|
|||
//Send a test notification by email
|
||||
const onSendTestMailHandler = async () => {
|
||||
clearError();
|
||||
start();
|
||||
setIsSendingTestNotification(true);
|
||||
try {
|
||||
const response = await fetch('/api/v1/notif/email/test', {
|
||||
|
|
@ -114,6 +118,8 @@ export default function EmailAlertSettings() {
|
|||
} catch (error) {
|
||||
setIsSendingTestNotification(false);
|
||||
handleError('Send notification failed');
|
||||
} finally {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -142,19 +148,14 @@ export default function EmailAlertSettings() {
|
|||
switchDescription='You will receive an alert every 24H if you have a down status.'
|
||||
onChange={(e) => onChangeSwitchHandler({ emailAlert: e })}
|
||||
/>
|
||||
{isSendingTestNotification ? (
|
||||
<SpinnerCircularFixed
|
||||
size={30}
|
||||
thickness={150}
|
||||
speed={150}
|
||||
color='#704dff'
|
||||
secondaryColor='#c3b6fa'
|
||||
/>
|
||||
) : (
|
||||
<button className='defaultButton' onClick={onSendTestMailHandler}>
|
||||
Send a test mail
|
||||
</button>
|
||||
)}
|
||||
|
||||
<button
|
||||
className='defaultButton'
|
||||
disabled={isSendingTestNotification}
|
||||
onClick={onSendTestMailHandler}
|
||||
>
|
||||
Send a test mail
|
||||
</button>
|
||||
{info && (
|
||||
<span style={{ marginLeft: '10px', color: '#119300' }}>Mail successfully sent.</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast, ToastOptions } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import classes from '../UserSettings.module.css';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { SpinnerDotted } from 'spinners-react';
|
||||
|
||||
//Components
|
||||
import Error from '~/Components/UI/Error/Error';
|
||||
import Info from '~/Components/UI/Info/Info';
|
||||
import { useLoader } from '~/contexts/LoaderContext';
|
||||
import { useFormStatus } from '~/hooks';
|
||||
import { EmailSettingDTO } from '~/types/api/setting.types';
|
||||
|
||||
|
|
@ -26,16 +26,18 @@ export default function EmailSettings(props: EmailSettingDTO) {
|
|||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<EmailSettingDTO>({ mode: 'onChange' });
|
||||
|
||||
const { isLoading, error, setIsLoading, handleError, clearError } = useFormStatus();
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
////State
|
||||
const [info, setInfo] = useState(false);
|
||||
|
||||
////Functions
|
||||
const formSubmitHandler = async (data: EmailSettingDTO) => {
|
||||
start();
|
||||
clearError();
|
||||
setIsLoading(true);
|
||||
|
||||
|
|
@ -61,6 +63,9 @@ export default function EmailSettings(props: EmailSettingDTO) {
|
|||
} catch (error) {
|
||||
reset();
|
||||
handleError('Updating your email failed.');
|
||||
} finally {
|
||||
stop();
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
|
@ -102,13 +107,9 @@ export default function EmailSettings(props: EmailSettingDTO) {
|
|||
</p>
|
||||
<button
|
||||
className={classes.AccountSettingsButton}
|
||||
disabled={!isValid || isSubmitting}
|
||||
disabled={isSubmitting || isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
|
||||
) : (
|
||||
'Update your email'
|
||||
)}
|
||||
Update your email
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { toast, ToastOptions } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import classes from '../UserSettings.module.css';
|
||||
import { IconExternalLink, IconTrash } from '@tabler/icons-react';
|
||||
import { fromUnixTime } from 'date-fns';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { SpinnerDotted } from 'spinners-react';
|
||||
import { fromUnixTime } from 'date-fns';
|
||||
import { IconTrash, IconExternalLink } from '@tabler/icons-react';
|
||||
import Link from 'next/link';
|
||||
import { Optional, IntegrationTokenType, TokenPermissionEnum, TokenPermissionsType } from '~/types';
|
||||
import { toast, ToastOptions } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { useFormStatus } from '~/hooks';
|
||||
import { IntegrationTokenType, Optional, TokenPermissionEnum, TokenPermissionsType } from '~/types';
|
||||
import classes from '../UserSettings.module.css';
|
||||
|
||||
//Components
|
||||
import Error from '~/Components/UI/Error/Error';
|
||||
import CopyButton from '~/Components/UI/CopyButton/CopyButton';
|
||||
import Error from '~/Components/UI/Error/Error';
|
||||
import Info from '~/Components/UI/Info/Info';
|
||||
import { useLoader } from '~/contexts/LoaderContext';
|
||||
|
||||
type IntegrationsDataForm = {
|
||||
tokenName: string;
|
||||
|
|
@ -36,6 +36,7 @@ export default function Integrations() {
|
|||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
} = useForm<IntegrationsDataForm>({ mode: 'onChange' });
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
const { error, handleError, clearError, setIsLoading, isLoading } = useFormStatus();
|
||||
|
||||
|
|
@ -63,6 +64,7 @@ export default function Integrations() {
|
|||
});
|
||||
|
||||
const fetchTokenList = async () => {
|
||||
start();
|
||||
try {
|
||||
const response = await fetch('/api/v1/integration/token-manager', {
|
||||
method: 'GET',
|
||||
|
|
@ -74,6 +76,8 @@ export default function Integrations() {
|
|||
setTokenList(data);
|
||||
} catch (error) {
|
||||
handleError('Fetching token list failed.');
|
||||
} finally {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -104,6 +108,7 @@ export default function Integrations() {
|
|||
|
||||
//Form submit handler to ADD a new token
|
||||
const formSubmitHandler = async (data: IntegrationsDataForm) => {
|
||||
start();
|
||||
clearError();
|
||||
setIsLoading(true);
|
||||
|
||||
|
|
@ -123,22 +128,18 @@ export default function Integrations() {
|
|||
setLastGeneratedToken({ name: data.tokenName, value: result.token });
|
||||
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
resetPermissions();
|
||||
toast.error(result.message, toastOptions);
|
||||
} else {
|
||||
reset();
|
||||
resetPermissions();
|
||||
fetchTokenList();
|
||||
setIsLoading(false);
|
||||
toast.success('🔑 Token generated !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
reset();
|
||||
resetPermissions();
|
||||
setIsLoading(false);
|
||||
toast.error('Failed to generate a new token', toastOptions);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
resetPermissions();
|
||||
reset();
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -237,11 +238,7 @@ export default function Integrations() {
|
|||
className={classes.AccountSettingsButton}
|
||||
disabled={!isValid || isSubmitting || hasNoPermissionSelected()}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={15} thickness={150} speed={100} color='#fff' />
|
||||
) : (
|
||||
'Generate'
|
||||
)}
|
||||
Generate
|
||||
</button>
|
||||
</form>
|
||||
{errors.tokenName && errors.tokenName.type === 'maxLength' && (
|
||||
|
|
@ -311,9 +308,6 @@ export default function Integrations() {
|
|||
disabled={isDeleteLoading}
|
||||
>
|
||||
Confirm
|
||||
{isDeleteLoading && (
|
||||
<SpinnerDotted size={15} thickness={150} speed={100} color='#fff' />
|
||||
)}{' '}
|
||||
</button>
|
||||
{!isDeleteLoading && (
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { useForm } from 'react-hook-form';
|
||||
import { toast, ToastOptions } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import classes from '../UserSettings.module.css';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { SpinnerDotted } from 'spinners-react';
|
||||
import { useFormStatus } from '~/hooks';
|
||||
import { PasswordSettingDTO } from '~/types';
|
||||
import classes from '../UserSettings.module.css';
|
||||
|
||||
//Components
|
||||
import Error from '~/Components/UI/Error/Error';
|
||||
import { useLoader } from '~/contexts/LoaderContext';
|
||||
|
||||
export default function PasswordSettings() {
|
||||
const toastOptions: ToastOptions = {
|
||||
|
|
@ -24,14 +23,15 @@ export default function PasswordSettings() {
|
|||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
formState: { isSubmitting },
|
||||
} = useForm<PasswordSettingDTO>({ mode: 'onChange' });
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
const { isLoading, error, setIsLoading, handleError, clearError } = useFormStatus();
|
||||
const { isLoading, setIsLoading } = useFormStatus();
|
||||
|
||||
////Functions
|
||||
const formSubmitHandler = async (data: PasswordSettingDTO) => {
|
||||
clearError();
|
||||
start();
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
|
|
@ -45,18 +45,16 @@ export default function PasswordSettings() {
|
|||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
handleError(result.message);
|
||||
toast.error(result.message, toastOptions);
|
||||
} else {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
toast.success('🔑 Password edited !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Failed to update password. Please try again.', toastOptions);
|
||||
} finally {
|
||||
stop();
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
handleError('Failed to update password. Please try again.');
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
|
@ -69,7 +67,6 @@ export default function PasswordSettings() {
|
|||
<div className={classes.setting}>
|
||||
<div className={classes.bwFormWrapper}>
|
||||
<form onSubmit={handleSubmit(formSubmitHandler)} className={classes.bwForm}>
|
||||
{error && <Error message={error} />}
|
||||
<p>
|
||||
<input
|
||||
type='password'
|
||||
|
|
@ -78,9 +75,6 @@ export default function PasswordSettings() {
|
|||
required: true,
|
||||
})}
|
||||
/>
|
||||
{errors.oldPassword && errors.oldPassword.type === 'required' && (
|
||||
<small className={classes.errorMessage}>This field is required.</small>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<input
|
||||
|
|
@ -90,16 +84,12 @@ export default function PasswordSettings() {
|
|||
required: true,
|
||||
})}
|
||||
/>
|
||||
{errors.newPassword && (
|
||||
<small className={classes.errorMessage}>This field is required.</small>
|
||||
)}
|
||||
</p>
|
||||
<button className={classes.AccountSettingsButton} disabled={!isValid || isSubmitting}>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
|
||||
) : (
|
||||
'Update your password'
|
||||
)}
|
||||
<button
|
||||
className={classes.AccountSettingsButton}
|
||||
disabled={isLoading || isSubmitting}
|
||||
>
|
||||
Update your password
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import { toast, ToastOptions } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import classes from '../UserSettings.module.css';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { SpinnerDotted } from 'spinners-react';
|
||||
import { toast, ToastOptions } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { useFormStatus } from '~/hooks';
|
||||
import { UsernameSettingDTO } from '~/types';
|
||||
import classes from '../UserSettings.module.css';
|
||||
|
||||
//Components
|
||||
import Error from '~/Components/UI/Error/Error';
|
||||
import Info from '~/Components/UI/Info/Info';
|
||||
import { useLoader } from '~/contexts/LoaderContext';
|
||||
|
||||
export default function UsernameSettings(props: UsernameSettingDTO) {
|
||||
const toastOptions: ToastOptions = {
|
||||
|
|
@ -26,17 +25,18 @@ export default function UsernameSettings(props: UsernameSettingDTO) {
|
|||
register,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<UsernameSettingDTO>({ mode: 'onChange' });
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
const { isLoading, error, setIsLoading, handleError, clearError } = useFormStatus();
|
||||
const { isLoading, setIsLoading } = useFormStatus();
|
||||
|
||||
////State
|
||||
const [info, setInfo] = useState(false);
|
||||
|
||||
////Functions
|
||||
const formSubmitHandler = async (data: UsernameSettingDTO) => {
|
||||
clearError();
|
||||
start();
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
|
|
@ -50,19 +50,17 @@ export default function UsernameSettings(props: UsernameSettingDTO) {
|
|||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
setIsLoading(false);
|
||||
reset();
|
||||
handleError(result.message);
|
||||
toast.error(result.message, toastOptions);
|
||||
} else {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
setInfo(true);
|
||||
toast.success('Username edited !', toastOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Failed to update username. Please try again.', toastOptions);
|
||||
} finally {
|
||||
reset();
|
||||
stop();
|
||||
setIsLoading(false);
|
||||
handleError('Failed to update username. Please try again.');
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
|
@ -86,7 +84,6 @@ export default function UsernameSettings(props: UsernameSettingDTO) {
|
|||
className={classes.bwForm + ' ' + classes.currentSetting}
|
||||
>
|
||||
<p>
|
||||
{error && <Error message={error} />}
|
||||
<input
|
||||
type='text'
|
||||
placeholder={props.username}
|
||||
|
|
@ -112,13 +109,9 @@ export default function UsernameSettings(props: UsernameSettingDTO) {
|
|||
</p>
|
||||
<button
|
||||
className={classes.AccountSettingsButton}
|
||||
disabled={!isValid || isSubmitting}
|
||||
disabled={isLoading || isSubmitting}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
|
||||
) : (
|
||||
'Update your username'
|
||||
)}
|
||||
Update your username
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,6 @@ export default async function handler(
|
|||
|
||||
return res.status(200).json({ message: 'Notifications successfully sent.' });
|
||||
} catch (error) {
|
||||
return res.status(500).json({ message: `Error: ${error}` });
|
||||
return res.status(500).json({ message: `${error}` });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { signIn, useSession } from 'next-auth/react';
|
|||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { SpinnerDotted } from 'spinners-react';
|
||||
import { useFormStatus } from '~/hooks';
|
||||
import { authOptions } from '~/pages/api/auth/[...nextauth]';
|
||||
|
||||
|
|
@ -11,6 +10,7 @@ import { authOptions } from '~/pages/api/auth/[...nextauth]';
|
|||
import { GetServerSidePropsContext } from 'next';
|
||||
import Image from 'next/image';
|
||||
import { ToastOptions, toast } from 'react-toastify';
|
||||
import { useLoader } from '~/contexts/LoaderContext';
|
||||
|
||||
type LoginForm = {
|
||||
username: string;
|
||||
|
|
@ -37,6 +37,7 @@ export default function Login() {
|
|||
};
|
||||
|
||||
const { isLoading, setIsLoading, handleError, clearError } = useFormStatus();
|
||||
const { start, stop } = useLoader();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'authenticated') {
|
||||
|
|
@ -53,6 +54,7 @@ export default function Login() {
|
|||
|
||||
//Functions
|
||||
const formSubmitHandler = async (data: LoginForm) => {
|
||||
start();
|
||||
setIsLoading(true);
|
||||
clearError();
|
||||
const resultat = await signIn('credentials', {
|
||||
|
|
@ -62,11 +64,13 @@ export default function Login() {
|
|||
});
|
||||
|
||||
if (resultat?.error) {
|
||||
stop();
|
||||
setFocus('username');
|
||||
reset();
|
||||
toast.info('Incorrect credentials', toastOptions);
|
||||
handleError(resultat.error);
|
||||
} else {
|
||||
stop();
|
||||
setIsLoading(false);
|
||||
router.replace('/');
|
||||
}
|
||||
|
|
@ -148,12 +152,8 @@ export default function Login() {
|
|||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<button className='signInButton' disabled={isLoading || !isFormComplete}>
|
||||
{isLoading ? (
|
||||
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
|
||||
) : (
|
||||
'Sign in'
|
||||
)}
|
||||
<button className='signInButton' disabled={isLoading}>
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue