wrap up contact slideover; closes #41
This commit is contained in:
parent
4f3ff46267
commit
afe0397a9b
5 changed files with 254 additions and 298 deletions
222
src/components/ContactUsSlideover.tsx
Normal file
222
src/components/ContactUsSlideover.tsx
Normal file
|
@ -0,0 +1,222 @@
|
|||
import * as React from 'react';
|
||||
import { ModuleInfo } from '../module';
|
||||
import { divisionLabels } from '../../content/ordering';
|
||||
import SlideoverForm from './Slideover/SlideoverForm';
|
||||
import { useState } from 'react';
|
||||
import useStickyState from '../hooks/useStickyState';
|
||||
|
||||
const Field = ({ label, id, value, onChange }) => {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor={id}
|
||||
className="block text-sm font-medium leading-5 text-gray-900"
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<div className="relative rounded-md shadow-sm">
|
||||
<input
|
||||
id={id}
|
||||
className="form-input block w-full sm:text-sm sm:leading-5 transition ease-in-out duration-150"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function ContactUsSlideover({
|
||||
isOpen,
|
||||
onClose,
|
||||
activeModule,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onClose: any;
|
||||
activeModule: ModuleInfo;
|
||||
}) {
|
||||
const [name, setName] = useStickyState('', 'contact_form_name');
|
||||
const [email, setEmail] = useStickyState('', 'contact_form_email');
|
||||
const [location, setLocation] = useState('');
|
||||
const [topic, setTopic] = useStickyState('', 'contact_form_topic');
|
||||
const topics = [
|
||||
'Typo',
|
||||
'Broken Link',
|
||||
'Unclear Explanation',
|
||||
'Suggestion',
|
||||
'Website Bug',
|
||||
'Other',
|
||||
];
|
||||
const [message, setMessage] = useStickyState('', 'contact_form_message');
|
||||
const [showSuccess, setShowSuccess] = useState(false);
|
||||
const [submitEnabled, setSubmitEnabled] = useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (activeModule)
|
||||
setLocation(
|
||||
`${activeModule.title} - ${divisionLabels[activeModule.division]}`
|
||||
);
|
||||
else setLocation('');
|
||||
}, [activeModule]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isOpen) {
|
||||
setShowSuccess(false);
|
||||
setSubmitEnabled(true);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleSubmit = async e => {
|
||||
e.preventDefault();
|
||||
let data = new FormData();
|
||||
data.append('name', name);
|
||||
data.append('email', email);
|
||||
data.append('location', location);
|
||||
data.append('topic', topic);
|
||||
data.append('message', message);
|
||||
setSubmitEnabled(false);
|
||||
try {
|
||||
await fetch(
|
||||
'https://formsubmit.co/ajax/da14a047686367521c3c8c5a79b03c97',
|
||||
{
|
||||
method: 'POST',
|
||||
mode: 'no-cors',
|
||||
body: data,
|
||||
}
|
||||
);
|
||||
setTopic('');
|
||||
setMessage('');
|
||||
setShowSuccess(true);
|
||||
} catch (e) {
|
||||
setSubmitEnabled(true);
|
||||
alert('Form submission failed: ' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SlideoverForm
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title="Contact Us"
|
||||
subtitle="Contact us about anything: suggestions, bugs, assistance, and more! Don't submit any personal information through this form."
|
||||
footerButtons={
|
||||
<>
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<button
|
||||
type="button"
|
||||
className="py-2 px-4 border border-gray-300 rounded-md text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition duration-150 ease-in-out"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</span>
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!submitEnabled}
|
||||
className={`inline-flex justify-center py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white transition duration-150 ease-in-out ${
|
||||
submitEnabled
|
||||
? 'bg-blue-600 hover:bg-blue-500 focus:outline-none focus:border-blue-700 focus:shadow-outline-blue active:bg-blue-700'
|
||||
: 'bg-blue-400 focus:outline-none cursor-default'
|
||||
}`}
|
||||
>
|
||||
Contact Us
|
||||
</button>
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="px-4 sm:px-6">
|
||||
{showSuccess && (
|
||||
<>
|
||||
<p className="pt-6">
|
||||
We've received your message! We will try our best to respond (if
|
||||
one is needed) within a week.
|
||||
</p>
|
||||
<p className="pt-2">
|
||||
For urgent requests, please feel free to email{' '}
|
||||
<a
|
||||
href="mailto:nathan.r.wang@gmail.com"
|
||||
className="underline text-blue-600"
|
||||
>
|
||||
nathan.r.wang@gmail.com
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{!showSuccess && (
|
||||
<div className="space-y-6 pt-6 pb-5">
|
||||
<Field
|
||||
label="Name"
|
||||
id="contact_name"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
/>
|
||||
<Field
|
||||
label="Email"
|
||||
id="contact_email"
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
/>
|
||||
<Field
|
||||
label="Module (If Applicable)"
|
||||
id="contact_module"
|
||||
value={location}
|
||||
onChange={e => setLocation(e.target.value)}
|
||||
/>
|
||||
<fieldset className="space-y-2">
|
||||
<legend className="text-sm leading-5 font-medium text-gray-900">
|
||||
Topic
|
||||
</legend>
|
||||
<div className="space-y-3">
|
||||
{topics.map((t, idx) => (
|
||||
<div key={idx}>
|
||||
<div className="relative flex items-start">
|
||||
<div className="absolute flex items-center h-5">
|
||||
<input
|
||||
id={`contact_topic_${idx}`}
|
||||
type="radio"
|
||||
name="type"
|
||||
className="form-radio h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
checked={topic === t}
|
||||
onChange={() => setTopic(t)}
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-7 text-sm leading-5">
|
||||
<label
|
||||
htmlFor={`contact_topic_${idx}`}
|
||||
className="font-medium text-gray-900"
|
||||
>
|
||||
{t}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="contact_message"
|
||||
className="block text-sm font-medium leading-5 text-gray-900"
|
||||
>
|
||||
Message
|
||||
</label>
|
||||
<div className="relative rounded-md shadow-sm">
|
||||
<textarea
|
||||
id="contact_message"
|
||||
rows={4}
|
||||
className="form-input block w-full sm:text-sm sm:leading-5 transition ease-in-out duration-150"
|
||||
value={message}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SlideoverForm>
|
||||
);
|
||||
}
|
|
@ -11,7 +11,7 @@ import ModuleOrdering, {
|
|||
ModuleOrderingItem,
|
||||
} from '../../../content/ordering';
|
||||
import Dots from '../Dots';
|
||||
import ReportIssueSlideover from '../ReportIssueSlideover';
|
||||
import ContactUsSlideover from '../ContactUsSlideover';
|
||||
import MarkCompleteButton from './MarkCompleteButton';
|
||||
import ModuleConfetti from './ModuleConfetti';
|
||||
import Asterisk from '../tooltip/Asterisk';
|
||||
|
@ -107,7 +107,7 @@ const Breadcrumbs = ({
|
|||
</nav>
|
||||
);
|
||||
|
||||
const SidebarBottomButtons = ({ onReportIssue, onGetHelp }) => {
|
||||
const SidebarBottomButtons = ({ onContactUs }) => {
|
||||
const languages = {
|
||||
cpp: 'C++',
|
||||
java: 'Java',
|
||||
|
@ -145,7 +145,7 @@ const SidebarBottomButtons = ({ onReportIssue, onGetHelp }) => {
|
|||
<div className="flex-shrink-0 border-t border-gray-200 flex">
|
||||
<button
|
||||
className="group flex-1 flex items-center p-4 text-sm leading-5 font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-100 transition ease-in-out duration-150"
|
||||
onClick={onReportIssue}
|
||||
onClick={onContactUs}
|
||||
>
|
||||
<svg
|
||||
className="mr-4 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"
|
||||
|
@ -161,44 +161,6 @@ const SidebarBottomButtons = ({ onReportIssue, onGetHelp }) => {
|
|||
Contact Us
|
||||
</button>
|
||||
</div>
|
||||
{/*<div className="flex-shrink-0 border-t border-gray-200 flex">*/}
|
||||
{/* <button*/}
|
||||
{/* className="group flex-1 flex items-center p-4 text-sm leading-5 font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-100 transition ease-in-out duration-150"*/}
|
||||
{/* onClick={onReportIssue}*/}
|
||||
{/* >*/}
|
||||
{/* <svg*/}
|
||||
{/* className="mr-4 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"*/}
|
||||
{/* fill="none"*/}
|
||||
{/* viewBox="0 0 24 24"*/}
|
||||
{/* stroke="currentColor"*/}
|
||||
{/* strokeLinecap="round"*/}
|
||||
{/* strokeLinejoin="round"*/}
|
||||
{/* strokeWidth="2"*/}
|
||||
{/* >*/}
|
||||
{/* <path d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />*/}
|
||||
{/* </svg>*/}
|
||||
{/* Complain*/}
|
||||
{/* </button>*/}
|
||||
{/*</div>*/}
|
||||
{/*<div className="flex-shrink-0 border-t border-gray-200 flex">*/}
|
||||
{/* <button*/}
|
||||
{/* className="group flex-1 flex items-center p-4 text-sm leading-5 font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:bg-gray-100 transition ease-in-out duration-150"*/}
|
||||
{/* onClick={onGetHelp}*/}
|
||||
{/* >*/}
|
||||
{/* <svg*/}
|
||||
{/* className="mr-4 h-6 w-6 text-gray-400 group-hover:text-gray-500 group-focus:text-gray-500 transition ease-in-out duration-150"*/}
|
||||
{/* fill="none"*/}
|
||||
{/* viewBox="0 0 24 24"*/}
|
||||
{/* stroke="currentColor"*/}
|
||||
{/* strokeLinecap="round"*/}
|
||||
{/* strokeLinejoin="round"*/}
|
||||
{/* strokeWidth="2"*/}
|
||||
{/* >*/}
|
||||
{/* <path d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />*/}
|
||||
{/* </svg>*/}
|
||||
{/* Get Help*/}
|
||||
{/* </button>*/}
|
||||
{/*</div>*/}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -339,8 +301,7 @@ export default function ModuleLayout({
|
|||
children: React.ReactNode;
|
||||
}) {
|
||||
const [isMobileNavOpen, setIsMobileNavOpen] = useState(false);
|
||||
const [isReportIssueActive, setIsReportIssueActive] = useState(false);
|
||||
const [isGetHelpActive, setIsGetHelpActive] = useState(false);
|
||||
const [isContactUsActive, setIsContactUsActive] = useState(false);
|
||||
const [isConfettiActive, setIsConfettiActive] = useState(false);
|
||||
const [moduleProgress, setModuleProgress] = useState('Not Started');
|
||||
|
||||
|
@ -461,13 +422,9 @@ export default function ModuleLayout({
|
|||
</nav>
|
||||
</div>
|
||||
<SidebarBottomButtons
|
||||
onReportIssue={() => {
|
||||
onContactUs={() => {
|
||||
setIsMobileNavOpen(false);
|
||||
setIsReportIssueActive(true);
|
||||
}}
|
||||
onGetHelp={() => {
|
||||
setIsMobileNavOpen(false);
|
||||
setIsGetHelpActive(true);
|
||||
setIsContactUsActive(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -491,8 +448,7 @@ export default function ModuleLayout({
|
|||
</nav>
|
||||
</div>
|
||||
<SidebarBottomButtons
|
||||
onReportIssue={() => setIsReportIssueActive(true)}
|
||||
onGetHelp={() => setIsGetHelpActive(true)}
|
||||
onContactUs={() => setIsContactUsActive(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -588,9 +544,9 @@ export default function ModuleLayout({
|
|||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<ReportIssueSlideover
|
||||
isOpen={isReportIssueActive}
|
||||
onClose={() => setIsReportIssueActive(false)}
|
||||
<ContactUsSlideover
|
||||
isOpen={isContactUsActive}
|
||||
onClose={() => setIsContactUsActive(false)}
|
||||
activeModule={module}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -1,241 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import Transition from './Transition';
|
||||
import { ModuleInfo } from '../module';
|
||||
import { divisionLabels } from '../../content/ordering';
|
||||
import Slideover from './Slideover/Slideover';
|
||||
|
||||
export default function ReportIssueSlideover({
|
||||
isOpen,
|
||||
onClose,
|
||||
activeModule,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
onClose: any;
|
||||
activeModule: ModuleInfo;
|
||||
}) {
|
||||
const [issueLocation, setIssueLocation] = React.useState('');
|
||||
React.useEffect(() => {
|
||||
if (activeModule)
|
||||
setIssueLocation(
|
||||
`${activeModule.title} - ${divisionLabels[activeModule.division]}`
|
||||
);
|
||||
else setIssueLocation('');
|
||||
}, [activeModule]);
|
||||
return (
|
||||
<Slideover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title="Complaint Form"
|
||||
subtitle="If you encounter an error while using the website, please fill out the form below. Thank you!"
|
||||
footerButtons={
|
||||
<>
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<button
|
||||
type="button"
|
||||
className="py-2 px-4 border border-gray-300 rounded-md text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition duration-150 ease-in-out"
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</span>
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex justify-center py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-500 focus:outline-none focus:border-blue-700 focus:shadow-outline-blue active:bg-blue-700 transition duration-150 ease-in-out"
|
||||
>
|
||||
Submit Complaint
|
||||
</button>
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="px-4 divide-y divide-gray-200 sm:px-6">
|
||||
<div className="space-y-6 pt-6 pb-5">
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="issue_name"
|
||||
className="block text-sm font-medium leading-5 text-gray-900"
|
||||
>
|
||||
Name (Optional)
|
||||
</label>
|
||||
<div className="relative rounded-md shadow-sm">
|
||||
<input
|
||||
id="issue_name"
|
||||
className="form-input block w-full sm:text-sm sm:leading-5 transition ease-in-out duration-150"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="issue_email"
|
||||
className="block text-sm font-medium leading-5 text-gray-900"
|
||||
>
|
||||
Email (Optional)
|
||||
</label>
|
||||
<div className="relative rounded-md shadow-sm">
|
||||
<input
|
||||
id="issue_email"
|
||||
className="form-input block w-full sm:text-sm sm:leading-5 transition ease-in-out duration-150"
|
||||
type="email"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="issue_location"
|
||||
className="block text-sm font-medium leading-5 text-gray-900"
|
||||
>
|
||||
Issue Location
|
||||
</label>
|
||||
<div className="relative rounded-md shadow-sm">
|
||||
<input
|
||||
id="issue_location"
|
||||
className="form-input block w-full sm:text-sm sm:leading-5 transition ease-in-out duration-150"
|
||||
value={issueLocation}
|
||||
onChange={e => setIssueLocation(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset className="space-y-2">
|
||||
<legend className="text-sm leading-5 font-medium text-gray-900">
|
||||
Issue Type
|
||||
</legend>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="absolute flex items-center h-5">
|
||||
<input
|
||||
id="type_typo"
|
||||
type="radio"
|
||||
name="type"
|
||||
className="form-radio h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-7 text-sm leading-5">
|
||||
<label
|
||||
htmlFor="type_typo"
|
||||
className="font-medium text-gray-900"
|
||||
>
|
||||
Typo
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="absolute flex items-center h-5">
|
||||
<input
|
||||
id="issue_broken-link"
|
||||
type="radio"
|
||||
name="type"
|
||||
className="form-radio h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-7 text-sm leading-5">
|
||||
<label
|
||||
htmlFor="issue_broken-link"
|
||||
className="font-medium text-gray-900"
|
||||
>
|
||||
Broken Link
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="absolute flex items-center h-5">
|
||||
<input
|
||||
id="issue_unclear-explanation"
|
||||
type="radio"
|
||||
name="type"
|
||||
className="form-radio h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-7 text-sm leading-5">
|
||||
<label
|
||||
htmlFor="issue_unclear-explanation"
|
||||
className="font-medium text-gray-900"
|
||||
>
|
||||
Unclear Explanation
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="absolute flex items-center h-5">
|
||||
<input
|
||||
id="issue_suggestion"
|
||||
type="radio"
|
||||
name="type"
|
||||
className="form-radio h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-7 text-sm leading-5">
|
||||
<label
|
||||
htmlFor="issue_suggestion"
|
||||
className="font-medium text-gray-900"
|
||||
>
|
||||
Suggestion
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="absolute flex items-center h-5">
|
||||
<input
|
||||
id="issue_website"
|
||||
type="radio"
|
||||
name="type"
|
||||
className="form-radio h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-7 text-sm leading-5">
|
||||
<label
|
||||
htmlFor="issue_website"
|
||||
className="font-medium text-gray-900"
|
||||
>
|
||||
Website Bug
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex items-start">
|
||||
<div className="absolute flex items-center h-5">
|
||||
<input
|
||||
id="issue_other"
|
||||
type="radio"
|
||||
name="type"
|
||||
className="form-radio h-4 w-4 text-blue-600 transition duration-150 ease-in-out"
|
||||
/>
|
||||
</div>
|
||||
<div className="pl-7 text-sm leading-5">
|
||||
<label
|
||||
htmlFor="issue_other"
|
||||
className="font-medium text-gray-900"
|
||||
>
|
||||
Other
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="block text-sm font-medium leading-5 text-gray-900"
|
||||
>
|
||||
Description
|
||||
</label>
|
||||
<div className="relative rounded-md shadow-sm">
|
||||
<textarea
|
||||
id="description"
|
||||
rows={4}
|
||||
className="form-input block w-full sm:text-sm sm:leading-5 transition ease-in-out duration-150"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Slideover>
|
||||
);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import Transition from '../Transition';
|
||||
import { FormEvent } from 'react';
|
||||
|
||||
type SlideoverProps = {
|
||||
isOpen: boolean;
|
||||
|
@ -8,9 +9,10 @@ type SlideoverProps = {
|
|||
subtitle: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
footerButtons: React.ReactNode;
|
||||
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
||||
};
|
||||
|
||||
export default function Slideover(props: SlideoverProps) {
|
||||
export default function SlideoverForm(props: SlideoverProps) {
|
||||
return (
|
||||
<Transition show={props.isOpen} timeout={700}>
|
||||
<div className="fixed inset-0 overflow-hidden">
|
||||
|
@ -39,7 +41,10 @@ export default function Slideover(props: SlideoverProps) {
|
|||
leaveTo="translate-x-full"
|
||||
>
|
||||
<div className="w-screen max-w-md">
|
||||
<div className="h-full divide-y divide-gray-200 flex flex-col bg-white shadow-xl">
|
||||
<form
|
||||
className="h-full divide-y divide-gray-200 flex flex-col bg-white shadow-xl"
|
||||
onSubmit={props.onSubmit}
|
||||
>
|
||||
<div className="flex-1 h-0 overflow-y-auto">
|
||||
<header className="space-y-1 py-6 px-4 bg-blue-700 sm:px-6">
|
||||
<div className="flex items-center justify-between space-x-3">
|
||||
|
@ -81,7 +86,7 @@ export default function Slideover(props: SlideoverProps) {
|
|||
<div className="flex-shrink-0 px-4 py-4 space-x-4 flex justify-end">
|
||||
{props.footerButtons}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Transition>
|
||||
</section>
|
14
src/hooks/useStickyState.tsx
Normal file
14
src/hooks/useStickyState.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import * as React from 'react';
|
||||
|
||||
// source: https://joshwcomeau.com/react/persisting-react-state-in-localstorage/
|
||||
|
||||
export default function useStickyState(defaultValue, key) {
|
||||
const [value, setValue] = React.useState(() => {
|
||||
const stickyValue = window.localStorage.getItem(key);
|
||||
return stickyValue !== null ? JSON.parse(stickyValue) : defaultValue;
|
||||
});
|
||||
React.useEffect(() => {
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
}, [key, value]);
|
||||
return [value, setValue];
|
||||
}
|
Reference in a new issue