-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
37 changed files
with
732 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { cls } from '@/utils/classes'; | ||
|
||
type CircleBadgeProps = { | ||
value: string; | ||
className?: string; | ||
}; | ||
|
||
export const CircleBadge = ({ value, className }: CircleBadgeProps) => { | ||
return ( | ||
<div | ||
className={cls( | ||
'relative w-3 h-3 bg-black rounded-full flex justify-center items-center text-center p-4 text-white', | ||
className | ||
)} | ||
> | ||
<span className="absolute left-0 top-0"></span> | ||
{value} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { CircleBadge } from './CircleBadge'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { cls } from '@/utils/classes'; | ||
import { Menu, Transition } from '@headlessui/react'; | ||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/20/solid'; | ||
import { Fragment } from 'react'; | ||
|
||
type DropdownProps = { | ||
options: string[]; | ||
value: string; | ||
onChange: (value: string) => void; | ||
}; | ||
|
||
export const Dropdown = ({ value: selected, options, onChange }: DropdownProps) => { | ||
return ( | ||
<Menu as="div" className="relative block text-left w-full"> | ||
<div> | ||
<Menu.Button className="inline-flex w-full justify-between items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-black"> | ||
{selected} | ||
<ChevronUpIcon | ||
className="-mr-1 ml-2 h-5 w-5 ui-open:hidden" | ||
aria-hidden="true" | ||
/> | ||
<ChevronDownIcon | ||
className="-mr-1 ml-2 h-5 w-5 hidden ui-open:block" | ||
aria-hidden="true" | ||
/> | ||
</Menu.Button> | ||
</div> | ||
|
||
<Transition | ||
as={Fragment} | ||
enter="transition ease-out duration-100" | ||
enterFrom="transform opacity-0 scale-95" | ||
enterTo="transform opacity-100 scale-100" | ||
leave="transition ease-in duration-75" | ||
leaveFrom="transform opacity-100 scale-100" | ||
leaveTo="transform opacity-0 scale-95" | ||
> | ||
<Menu.Items | ||
className="absolute left-0 z-10 mt-2 w-full origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" | ||
key={selected} | ||
> | ||
<div className=""> | ||
{options.map((item) => ( | ||
<Menu.Item key={item}> | ||
{({ active }) => ( | ||
<button | ||
onClick={() => onChange(item)} | ||
className={cls( | ||
active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', | ||
selected === item ? 'bg-gray-200' : '', | ||
'px-4 py-2 text-sm w-full text-left flex items-center space-x-2 justify-between' | ||
)} | ||
> | ||
<span>{item}</span> | ||
{selected === item ? ( | ||
<CheckIcon className="w-4 h-4 text-bold" /> | ||
) : null} | ||
</button> | ||
)} | ||
</Menu.Item> | ||
))} | ||
</div> | ||
</Menu.Items> | ||
</Transition> | ||
</Menu> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { Dropdown } from './Dropdown'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { Icon } from '../Svgs'; | ||
|
||
export const Footer = () => { | ||
return ( | ||
<footer className="w-full border-t border-gray-200 h-18 sm:h-12"> | ||
<div className="flex sm:flex-row flex-col justify-between mx-auto max-w-7xl h-18 sm:h-12 items-center sm:py-0 py-2 sm:px-2"> | ||
<div className="text-sm text-center"> | ||
Powered by{' '} | ||
<a | ||
href="https://openai.com/" | ||
target="_blank" | ||
rel="noreferrer" | ||
className="font-bold hover:underline transition underline-offset-2" | ||
> | ||
OpenAI{' '} | ||
</a> | ||
and{' '} | ||
<a | ||
href="https://vercel.com/" | ||
target="_blank" | ||
rel="noreferrer" | ||
className="font-bold hover:underline transition underline-offset-2" | ||
> | ||
Vercel Edge Functions. | ||
</a> | ||
</div> | ||
{/**<p className="text-sm font-medium">Made with ❤️</p>*/} | ||
<div className="flex space-x-2"> | ||
<a | ||
href="https://github.com/alexmarqs/real-estate-ai-app" | ||
target="_blank" | ||
rel="noreferrer" | ||
> | ||
<Icon name="github" className="h-6 w-6 fill-slate-500 hover:fill-slate-700" /> | ||
</a> | ||
</div> | ||
</div> | ||
</footer> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { Footer } from './Footer'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import { useGenerationForm } from '@/hooks/useGenerationForm'; | ||
import { cls } from '@/utils/classes'; | ||
import { AUDIENCES, MOODS, PROPERTY_TYPES } from '@/utils/options'; | ||
import { useEffect } from 'react'; | ||
import { Controller, useForm } from 'react-hook-form'; | ||
import { CircleBadge } from '../CircleBadge'; | ||
import { Dropdown } from '../Dropdown'; | ||
import { Icon } from '../Svgs'; | ||
|
||
type GenerationFormProps = { | ||
className?: string; | ||
}; | ||
|
||
type FormData = { | ||
propertyType: string; | ||
description: string; | ||
audience: string; | ||
mood: string; | ||
}; | ||
|
||
const INITIAL_FORM_DATA: FormData = { | ||
propertyType: PROPERTY_TYPES[0], | ||
description: '', | ||
audience: AUDIENCES[0], | ||
mood: MOODS[0], | ||
}; | ||
|
||
export const GenerationForm = ({ className }: GenerationFormProps) => { | ||
const { | ||
//reset, | ||
register, | ||
handleSubmit, | ||
formState: { errors }, | ||
control, | ||
} = useForm<FormData>({ | ||
defaultValues: INITIAL_FORM_DATA, | ||
}); | ||
|
||
const { description, isLoading, generate } = useGenerationForm(); | ||
|
||
const onSubmit = (data: FormData) => { | ||
generate({ | ||
propertyType: data.propertyType as any, | ||
description: data.description, | ||
targetAudience: data.audience as any, | ||
mood: data.mood as any, | ||
}); | ||
}; | ||
|
||
useEffect(() => { | ||
if (!description) { | ||
return; | ||
} | ||
// scroll to the bottom of the page | ||
window.scrollTo(0, document.body.scrollHeight); | ||
}, [description]); | ||
|
||
return ( | ||
<div className={cls('max-w-2xl w-full', className)}> | ||
<form className="flex flex-col space-y-8" onSubmit={handleSubmit(onSubmit)}> | ||
<div className="flex flex-col space-y-4"> | ||
<div className="flex items-center space-x-2"> | ||
<CircleBadge value="1" /> | ||
<label className="text-left font-medium" htmlFor="propertyType"> | ||
Property Type | ||
</label> | ||
</div> | ||
<Controller | ||
name="propertyType" | ||
control={control} | ||
render={({ field }) => ( | ||
<Dropdown | ||
options={Array.from(PROPERTY_TYPES)} | ||
value={field.value} | ||
onChange={(item: string) => field.onChange(item)} | ||
/> | ||
)} | ||
/> | ||
</div> | ||
<div className="flex flex-col space-y-4"> | ||
<div className="flex items-center space-x-2"> | ||
<CircleBadge value="2" /> | ||
<label className="text-left font-medium" htmlFor="description"> | ||
Description | ||
</label> | ||
</div> | ||
<div> | ||
<textarea | ||
{...register('description', { required: true })} | ||
id="description" | ||
name="description" | ||
rows={4} | ||
className={cls( | ||
'w-full rounded-md border-gray-300 shadow-sm focus:border-black focus:ring-black', | ||
errors.description?.type === 'required' | ||
? ' focus:border-red-500 focus:ring-red-500 border-red-300' | ||
: undefined | ||
)} | ||
placeholder={ | ||
'e.g. An apartment with beautiful views of the city, modern interior, and a spacious living room.' | ||
} | ||
/> | ||
{errors.description?.type === 'required' && ( | ||
<p className="text-red-500 text-sm text-left">This field is required</p> | ||
)} | ||
</div> | ||
</div> | ||
<div className="flex flex-col space-y-4"> | ||
<div className="flex items-center space-x-2"> | ||
<CircleBadge value="3" /> | ||
<label className="text-left font-medium" htmlFor="audience"> | ||
Target Audience | ||
</label> | ||
</div> | ||
<Controller | ||
name="audience" | ||
control={control} | ||
render={({ field }) => ( | ||
<Dropdown | ||
options={Array.from(AUDIENCES)} | ||
value={field.value} | ||
onChange={(item: string) => field.onChange(item)} | ||
/> | ||
)} | ||
/> | ||
</div> | ||
<div className="flex flex-col space-y-4"> | ||
<div className="flex items-center space-x-2"> | ||
<CircleBadge value="4" /> | ||
<label className="text-left font-medium" htmlFor="mood"> | ||
Mood | ||
</label> | ||
</div> | ||
<Controller | ||
name="mood" | ||
control={control} | ||
render={({ field }) => ( | ||
<Dropdown | ||
options={Array.from(MOODS)} | ||
value={field.value} | ||
onChange={(item: string) => field.onChange(item)} | ||
/> | ||
)} | ||
/> | ||
</div> | ||
<button | ||
type="submit" | ||
disabled={isLoading} | ||
className={cls( | ||
'bg-black text-white font-medium py-2 px-4 rounded-lg hover:bg-gray-800', | ||
isLoading ? 'cursor-not-allowed bg-gray-500 hover:bg-gray-500' : undefined | ||
)} | ||
> | ||
{isLoading && <Icon name="spinner" />} | ||
Generate now ⚡︎ | ||
</button> | ||
</form> | ||
{!isLoading && description && ( | ||
<div className="mt-8"> | ||
<h2 className="text-3xl font-bold">Generated Description 🎉</h2> | ||
<p className="mt-4 text-left">{description}</p> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { GenerationForm } from './GenerationForm'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import Image from 'next/image'; | ||
import vercelLogo from '../../../public/vercelLogo.png'; | ||
|
||
export const Header = () => { | ||
return ( | ||
<header className="w-full border-b border-gray-200 h-14"> | ||
<div className="flex justify-between mx-auto max-w-7xl h-14 items-center px-4 md:px-2"> | ||
<h1 className=" text-xl sm:text-2xl font-bold tracking-tight text-center"> | ||
Real Estate AI Generator | ||
</h1> | ||
|
||
<a | ||
href={'https://vercel.com/templates/next.js/twitter-bio'} | ||
target="_blank" | ||
rel="noreferrer" | ||
> | ||
<Image src={vercelLogo} alt="Logo" className="w-8 h-[28px]" /> | ||
</a> | ||
</div> | ||
</header> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { Header } from './Header'; |
Oops, something went wrong.