-
Notifications
You must be signed in to change notification settings - Fork 0
Added visual cueing when submitting forms #52
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,17 @@ | ||
export default function Loading() { | ||
type LoadingProps = { | ||
isPrimary?: boolean; | ||
}; | ||
|
||
export default function Loading(props: LoadingProps) { | ||
const { isPrimary } = props; | ||
|
||
return ( | ||
<div className="flex h-screen items-center justify-center"> | ||
<div className="h-10 w-10 animate-spin rounded-full border-b-2 border-violet-300"></div> | ||
<div className="flex items-center justify-center"> | ||
<div | ||
className={`h-5 w-5 ${ | ||
isPrimary ? 'border-white-300' : 'border-violet-300' | ||
} animate-spin rounded-full border-b-2`} | ||
></div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { createContext, useState } from 'react'; | ||
|
||
type ErrorContext = { | ||
error: Error | null; | ||
setError(_error: Error | null): void; | ||
clearError(): void; | ||
}; | ||
|
||
type SuccessContext = { | ||
success: string | null; | ||
setSuccess(_success: string | null): void; | ||
clearSuccess(): void; | ||
}; | ||
|
||
export const NotificationContext = createContext<ErrorContext & SuccessContext>( | ||
{ | ||
error: null as Error | null, | ||
setError(_error: Error | null) {}, | ||
clearError() {}, | ||
success: null as string | null, | ||
setSuccess(_success: string | null) {}, | ||
clearSuccess() {} | ||
} | ||
); | ||
|
||
export function NotificationProvider({ | ||
children | ||
}: { | ||
children: React.ReactNode; | ||
}) { | ||
const [error, setError] = useState<Error | null>(null); | ||
const [success, setSuccess] = useState<string | null>(null); | ||
|
||
return ( | ||
<NotificationContext.Provider | ||
value={{ | ||
error, | ||
setError, | ||
clearError: () => setError(null), | ||
success, | ||
setSuccess, | ||
clearSuccess: () => setSuccess(null) | ||
}} | ||
> | ||
{children} | ||
</NotificationContext.Provider> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { Transition } from '@headlessui/react'; | ||
import { CheckCircleIcon, XMarkIcon } from '@heroicons/react/20/solid'; | ||
import { useEffect } from 'react'; | ||
import { useSuccess } from '~/data/hooks/success'; | ||
|
||
export default function SuccessNotification() { | ||
const { success, clearSuccess } = useSuccess(); | ||
|
||
// Close the notification after 3 seconds | ||
useEffect(() => { | ||
if (success !== null) { | ||
setTimeout(() => clearSuccess(), 3000); | ||
} | ||
}, [success, clearSuccess]); | ||
|
||
return ( | ||
<Transition show={success !== null}> | ||
<div className="max-w-s fixed bottom-0 right-2 z-10 m-4"> | ||
<div className="rounded-md bg-green-50 p-4"> | ||
<div className="flex"> | ||
<div className="flex-shrink-0"> | ||
<CheckCircleIcon | ||
className="h-5 w-5 text-green-400" | ||
aria-hidden="true" | ||
/> | ||
</div> | ||
<div className="ml-3"> | ||
<p className="text-sm font-medium text-green-800">{success}</p> | ||
</div> | ||
<div className="ml-auto pl-3"> | ||
<div className="-mx-1.5 -my-1.5"> | ||
<button | ||
type="button" | ||
onClick={() => clearSuccess()} | ||
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-offset-2 focus:ring-offset-green-50" | ||
> | ||
<span className="sr-only">Dismiss</span> | ||
<XMarkIcon className="h-5 w-5" aria-hidden="true" /> | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</Transition> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,10 @@ import TextArea from '~/components/forms/TextArea'; | |
import MoreInfo from '~/components/MoreInfo'; | ||
import { createVariant, updateVariant } from '~/data/api'; | ||
import { useError } from '~/data/hooks/error'; | ||
import { useSuccess } from '~/data/hooks/success'; | ||
import { jsonValidation, keyValidation } from '~/data/validations'; | ||
import { IVariant, IVariantBase } from '~/types/Variant'; | ||
import Loading from '../Loading'; | ||
|
||
type VariantFormProps = { | ||
setOpen: (open: boolean) => void; | ||
|
@@ -22,7 +24,9 @@ export default function VariantForm(props: VariantFormProps) { | |
const { setOpen, flagKey, variant, onSuccess } = props; | ||
const isNew = variant === undefined; | ||
const title = isNew ? 'New Variant' : 'Edit Variant'; | ||
const submitPhrase = isNew ? 'Create' : 'Update'; | ||
const { setError, clearError } = useError(); | ||
const { setSuccess } = useSuccess(); | ||
|
||
const handleSubmit = async (values: IVariantBase) => { | ||
if (isNew) { | ||
|
@@ -44,6 +48,9 @@ export default function VariantForm(props: VariantFormProps) { | |
handleSubmit(values) | ||
.then(() => { | ||
clearError(); | ||
setSuccess( | ||
`Successfully ${submitPhrase.toLocaleLowerCase()}d variant.` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in some cases we end this message with a We should probably be consistent in the messaging/punctation, I'm just not sure which one I prefer (with the period or without). Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I actually like without the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the |
||
); | ||
onSuccess(); | ||
}) | ||
.catch((err) => { | ||
|
@@ -155,10 +162,13 @@ export default function VariantForm(props: VariantFormProps) { | |
<Button onClick={() => setOpen(false)}>Cancel</Button> | ||
<Button | ||
primary | ||
className="min-w-[80px]" | ||
type="submit" | ||
disabled={!(formik.dirty && formik.isValid)} | ||
disabled={ | ||
!(formik.dirty && formik.isValid && !formik.isSubmitting) | ||
} | ||
> | ||
{isNew ? 'Create' : 'Update'} | ||
{formik.isSubmitting ? <Loading isPrimary /> : submitPhrase} | ||
</Button> | ||
</div> | ||
</div> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we make the success notification clear itself after a short period? say 2-3 seconds? i think success notifications should be short-lived, while error notifications should persist until manually closed/acknowledged. wyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good thought, I wasn't sure if this was a desired functionality. Set timeout to be 3 seconds for now.