@@ -1,18 +1,26 @@
11import debounce from 'lodash.debounce';
22import React, { useCallback, useEffect, useRef, useState } from 'react';
3−import { useForm } from 'react-hook-form';
3+import { FormProvider, useForm } from 'react-hook-form';
44import { Link, useNavigate } from 'react-router-dom';
55import { MultiValue } from 'react-select';
66import api from '../api';
7+import { ErrorMessage } from '../components/ErrorMessage';
78import { Filter } from '../components/Filter';
89import { Option, TagField } from '../components/TagField';
910import { Declare, Post as PostForm } from '../types/sphinx';
1011
12+function errorMessages(length: number) {
13+ return {
14+ required: 'This field is required.',
15+ maxLength: `The maximum length is ${length}.`
16+ }
17+}
18+
1119export const Post = () => {
1220 const navigate = useNavigate();
1321 const [tags, setTags] = useState<MultiValue<Option>>([]);
1422 const [image, setImage] = useState<string>('');
15− const { register, watch, reset, setValue, handleSubmit } = useForm<PostForm>({
23+ const form = useForm<PostForm>({
1624 defaultValues: {
1725 title: '',
1826 type: 'other',
@@ -22,6 +30,8 @@ export const Post = () => {
2230 }
2331 });
2432
33+ const { register, watch, reset, setValue, handleSubmit, formState } = form;
34+
2535 const updateImg = useRef(debounce(setImage));
2636 const img = watch('image');
2737 const type = watch('type');
@@ -57,50 +67,59 @@ export const Post = () => {
5767 <header>
5868 <h1 className='text-2xl font-semibold'>Add a Listing</h1>
5969 </header>
60− <form onSubmit={handleSubmit(onSubmit)}>
61− <div className='flex w-full space-x-6'>
62− <div className='flex-1 space-y-3'>
63− <div>
64− <label htmlFor='title' className='text-sm font-semibold'>Title</label>
65− <div className='flex items-center space-x-2'>
66− <input {...register('title', { required: true, maxLength: 80 })} className='flex-1 w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='sphinx'/>
67− <Filter selected={type} onSelect={(value) => setValue('type', value)} />
68− </div>
69− </div>
70− <div className='flex items-center space-x-6'>
71− <div className='flex-1 space-y-3'>
72− <div>
73− <label htmlFor='image' className='text-sm font-semibold'>Image</label>
74− <input type="url" {...register('image')} className='flex-1 w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='https://nyc3.digitaloceanspaces.com/...' />
70+ <FormProvider {...form}>
71+ <form onSubmit={handleSubmit(onSubmit)}>
72+ <div className='flex w-full space-x-6'>
73+ <div className='flex-1 space-y-3'>
74+ <div>
75+ <label htmlFor='title' className='text-sm font-semibold'>Title</label>
76+ <div className='flex items-center space-x-2'>
77+ <input {...register('title', { required: true, maxLength: 77 })} className='flex-1 w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='sphinx'/>
78+ <Filter selected={type} onSelect={(value) => setValue('type', value)} />
7579 </div>
76− <div>
77− <label htmlFor='link' className='text-sm font-semibold'>Link</label>
78− <input type="url" {...register('link', { required: true })} className='flex-1 w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='web+urbitgraph://~nocsyx-lassul/sphinx' />
80+ <ErrorMessage className='mt-1' field="title" messages={errorMessages(77)}/>
81+ </div>
82+ <div className='flex items-center space-x-6'>
83+ <div className='flex-1 space-y-3'>
84+ <div>
85+ <label htmlFor='image' className='text-sm font-semibold'>Image</label>
86+ <input type="url" {...register('image', {
87+ maxLength: 1024
88+ })} className='flex-1 w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='https://nyc3.digitaloceanspaces.com/...' />
89+ <ErrorMessage className='mt-1' field="image" messages={errorMessages(1024)}/>
90+ </div>
91+ <div>
92+ <label htmlFor='link' className='text-sm font-semibold'>Link</label>
93+ <input type="url" {...register('link', { required: true, maxLength: 1024 })} className='flex-1 w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='web+urbitgraph://~nocsyx-lassul/sphinx' />
94+ <ErrorMessage className='mt-1' field="link" messages={errorMessages(1024)} />
95+ </div>
7996 </div>
97+ <img className='flex-none object-cover w-28 h-28 mt-4 border-2 border-dashed border-mauve/60 rounded-lg' src={image || undefined} />
8098 </div>
81− <img className='flex-none object-cover w-28 h-28 mt-4 border-2 border-dashed border-mauve/60 rounded-lg' src={image || undefined} />
82− </div>
83− <div>
84− <label htmlFor='description' className='text-sm font-semibold'>Description</label>
85− <textarea {...register('description', { required: true, maxLength: 256 })} rows={2} className='align-middle w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='An app for answering your riddles' />
86− </div>
87− <div>
88− <label className='text-sm font-semibold'>Tags</label>
89− <TagField tags={tags} onTags={setTags} />
90− </div>
91− <div className='pt-3'>
92− <div className='flex justify-between border-t border-zinc-300 py-3'>
93− <button type="button" className='flex items-center rounded-lg text-base font-semibold text-rosy bg-rosy/30 border-2 border-transparent hover:border-rosy leading-none py-2 px-3 transition-colors' onClick={() => navigate(-1)}>
94− Back to Search
95− </button>
96− <button type="submit" className='flex items-center rounded-lg text-base font-semibold text-linen bg-rosy border-2 border-transparent hover:border-linen/60 leading-none py-2 px-3 transition-colors'>
97− Publish
98− </button>
99+ <div>
100+ <label htmlFor='description' className='text-sm font-semibold'>Description</label>
101+ <textarea {...register('description', { required: true, maxLength: 256 })} rows={2} className='align-middle w-full py-1 px-2 bg-fawn/30 focus:outline-none focus:ring-2 ring-lavender rounded-lg border border-fawn/30' placeholder='An app for answering your riddles' />
102+ <ErrorMessage className='mt-1' field="description" messages={errorMessages(256)} />
103+ </div>
104+ <div>
105+ <label className='text-sm font-semibold'>Tags</label>
106+ <TagField tags={tags} onTags={setTags} />
107+ {tags.length === 8 && <div className='text-mauve/50 text-xs mt-1'>8 tags maximum</div>}
108+ </div>
109+ <div className='pt-3'>
110+ <div className='flex justify-between border-t border-zinc-300 py-3'>
111+ <button type="button" className='flex items-center rounded-lg text-base font-semibold text-rosy bg-rosy/30 border-2 border-transparent hover:border-rosy leading-none py-2 px-3 transition-colors' onClick={() => navigate(-1)}>
112+ Back to Search
113+ </button>
114+ <button type="submit" className='flex items-center rounded-lg text-base font-semibold text-linen bg-rosy border-2 border-transparent hover:border-linen/60 leading-none py-2 px-3 transition-colors'>
115+ Publish
116+ </button>
117+ </div>
99118 </div>
100119 </div>
101120 </div>
102− </div>
103− </form>
121+ </form>
122+ </FormProvider>
104123 </>
105124 )
106125}