├── public ├── favicon.ico ├── vercel.svg └── next.svg ├── postcss.config.js ├── styles └── globals.css ├── app ├── components │ ├── navbar │ │ ├── Logo.tsx │ │ ├── UserMenuItem.tsx │ │ ├── Navbar.tsx │ │ ├── CategoriesItem.tsx │ │ ├── Categories.tsx │ │ └── UserMenu.tsx │ ├── MountedClient.tsx │ ├── buttons │ │ └── Button.tsx │ ├── listings │ │ ├── CategorySelect.tsx │ │ ├── CountrySelect.tsx │ │ └── CounterSelect.tsx │ ├── inputs │ │ └── Input.tsx │ └── modals │ │ ├── Modal.tsx │ │ ├── LoginModal.tsx │ │ ├── RegisterModal.tsx │ │ └── ElementModal.tsx ├── libs │ └── prismadb.ts ├── providers │ ├── ToastProvider.tsx │ └── ReduxProvider.tsx ├── redux │ ├── hooks.ts │ ├── store.ts │ └── modalSlice.ts ├── api │ ├── register │ │ └── route.ts │ └── listings │ │ └── route.ts ├── actions │ └── getCurrentUser.ts ├── page.tsx └── layout.tsx ├── next.config.js ├── .gitignore ├── tailwind.config.js ├── tsconfig.json ├── package.json ├── README.md ├── pages └── api │ └── auth │ └── [...nextauth].ts └── prisma └── schema.prisma /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berkantkaya/youtube-next13-prisma/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, body, :root{ 6 | height: 100%; 7 | } -------------------------------------------------------------------------------- /app/components/navbar/Logo.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | const Logo = () => { 4 | return ( 5 |
thebest-Next13
6 | ) 7 | } 8 | 9 | export default Logo -------------------------------------------------------------------------------- /app/libs/prismadb.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global{ 4 | var prisma : PrismaClient | undefined 5 | } 6 | 7 | const client = globalThis.prisma || new PrismaClient(); 8 | if(process.env.NODE_ENV != "production") globalThis.prisma = client; 9 | export default client -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | experimental: { 5 | appDir: true 6 | }, 7 | images:{ 8 | domains: ['upload.wikimedia.org', 'lh3.googleusercontent.com'] 9 | } 10 | } 11 | 12 | module.exports = nextConfig 13 | -------------------------------------------------------------------------------- /app/providers/ToastProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { ToastContainer } from 'react-toastify'; 3 | import 'react-toastify/dist/ReactToastify.css'; 4 | 5 | const ToastProvider = () => { 6 | return ( 7 | <> 8 | 9 | 10 | ) 11 | } 12 | 13 | export default ToastProvider -------------------------------------------------------------------------------- /app/providers/ReduxProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Provider } from "react-redux" 4 | import { store } from "../redux/store" 5 | 6 | const ReduxProvider = ({children}: {children: React.ReactNode}) => { 7 | return ( 8 | {children} 9 | ) 10 | } 11 | 12 | export default ReduxProvider -------------------------------------------------------------------------------- /app/redux/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from 'react-redux' 2 | import type { TypedUseSelectorHook } from 'react-redux' 3 | import type { RootState, AppDispatch } from './store' 4 | 5 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 6 | export const useAppDispatch: () => AppDispatch = useDispatch 7 | export const useAppSelector: TypedUseSelectorHook = useSelector -------------------------------------------------------------------------------- /app/components/navbar/UserMenuItem.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | interface UserMenuItemProps { 4 | name: string; 5 | onClick:() => void; 6 | } 7 | const UserMenuItem:React.FC = ({ 8 | name, 9 | onClick 10 | }) => { 11 | return ( 12 |
13 | {name} 14 |
15 | ) 16 | } 17 | 18 | export default UserMenuItem -------------------------------------------------------------------------------- /app/components/MountedClient.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import {useState, useEffect} from 'react' 3 | 4 | const MountedClient = ({children}: {children: React.ReactNode}) => { 5 | const [mounted, setMounted] = useState(false) 6 | 7 | useEffect(() => { 8 | setMounted(true) 9 | }, []) 10 | 11 | if(!mounted){ 12 | return null 13 | } 14 | 15 | return ( 16 | <> 17 | {children} 18 | 19 | ) 20 | } 21 | 22 | export default MountedClient -------------------------------------------------------------------------------- /app/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit' 2 | import modalSlice from './modalSlice' 3 | // ... 4 | 5 | export const store = configureStore({ 6 | reducer: { 7 | modal: modalSlice 8 | }, 9 | }) 10 | 11 | // Infer the `RootState` and `AppDispatch` types from the store itself 12 | export type RootState = ReturnType 13 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 14 | export type AppDispatch = typeof store.dispatch -------------------------------------------------------------------------------- /app/components/navbar/Navbar.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { User } from "@prisma/client" 4 | import Categories from "./Categories" 5 | import Logo from "./Logo" 6 | import UserMenu from "./UserMenu" 7 | 8 | const Navbar = ({user} : {user: User | any | undefined}) => { 9 | return ( 10 |
11 | 12 | 13 | 14 |
15 | ) 16 | } 17 | 18 | export default Navbar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './app/**/*.{js,ts,jsx,tsx,mdx}', 7 | ], 8 | theme: { 9 | extend: { 10 | backgroundImage: { 11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 12 | 'gradient-conic': 13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } 19 | -------------------------------------------------------------------------------- /app/api/register/route.ts: -------------------------------------------------------------------------------- 1 | 2 | import bcrypt from 'bcrypt'; 3 | import prisma from '@/app/libs/prismadb'; 4 | import { NextResponse } from 'next/server'; 5 | 6 | export async function POST(request: Request){ 7 | const {name, email, password} = await request.json(); 8 | 9 | const hashedPassword = await bcrypt.hash(password, 12); 10 | 11 | const user = await prisma.user.create({ 12 | data: { 13 | name, 14 | email, 15 | hashedPassword 16 | } 17 | }) 18 | 19 | return NextResponse.json(user); 20 | } -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/actions/getCurrentUser.ts: -------------------------------------------------------------------------------- 1 | import prisma from '@/app/libs/prismadb'; 2 | import { authOptions } from '@/pages/api/auth/[...nextauth]'; 3 | import { getServerSession } from 'next-auth'; 4 | 5 | 6 | export async function getSession(){ 7 | return await getServerSession(authOptions) 8 | } 9 | 10 | export default async function getCurrentUser(){ 11 | const session = await getSession(); 12 | if(!session?.user?.email){ 13 | return null; 14 | } 15 | const user = await prisma.user.findUnique({ 16 | where: { 17 | email : session.user.email 18 | } 19 | }) 20 | 21 | if(!user){ 22 | return null; 23 | } 24 | return user 25 | } 26 | -------------------------------------------------------------------------------- /app/components/buttons/Button.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { IconType } from "react-icons"; 4 | 5 | type ButtonProps = { 6 | onSubmit: (e:React.MouseEvent) => void; 7 | btnLabel: string; 8 | outline?: boolean; 9 | icon?: IconType 10 | } 11 | const Button: React.FC = ({ 12 | onSubmit, 13 | btnLabel, 14 | outline, 15 | icon: Icon 16 | }) => { 17 | return ( 18 | 22 | ) 23 | } 24 | 25 | export default Button -------------------------------------------------------------------------------- /app/components/listings/CategorySelect.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { IconType } from "react-icons"; 4 | 5 | 6 | type CategorySelectProps = { 7 | name: string; 8 | icon: IconType; 9 | selected: boolean; 10 | onClick: ( value : string) => void; 11 | } 12 | const CategorySelect:React.FC = ({ 13 | name, 14 | icon: Icon, 15 | selected, 16 | onClick 17 | }) => { 18 | return ( 19 |
 onClick(name)}> 20 | 21 |
{name}
22 |
23 | ) 24 | } 25 | 26 | export default CategorySelect -------------------------------------------------------------------------------- /app/components/navbar/CategoriesItem.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import {IconType} from 'react-icons' 3 | import {useRouter} from 'next/navigation'; 4 | 5 | 6 | type CategoriesItemProps = { 7 | name: string; 8 | icon: IconType; 9 | selected: boolean 10 | } 11 | const CategoriesItem:React.FC = ({ 12 | name, 13 | icon: Icon, 14 | selected 15 | }) => { 16 | const router = useRouter() 17 | return ( 18 |
router.push(`?category=${name}`)} className={`${selected ? "border-b-2 border-black" : ""} pb-2 flex items-center gap-2 cursor-pointer`}> 19 | 20 |
{name}
21 |
22 | ) 23 | } 24 | 25 | export default CategoriesItem -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import prisma from '@/app/libs/prismadb'; 2 | import Image from 'next/image'; 3 | 4 | const Page = async() => { 5 | const listings = await prisma.listing.findMany({ 6 | orderBy : { 7 | createdAt: "desc" 8 | } 9 | }) 10 | 11 | return ( 12 |
13 | { 14 | listings?.map((list,i) => ( 15 |
16 | 22 |
23 | {list.category} - {list.locationValue} 24 |
25 |
26 | )) 27 | } 28 |
29 | ) 30 | } 31 | 32 | export default Page -------------------------------------------------------------------------------- /app/components/inputs/Input.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { FieldErrors, FieldValues, UseFormRegister } from "react-hook-form"; 4 | 5 | type InputProps = { 6 | id: string; 7 | type: string; 8 | placeholder: string; 9 | required: boolean; 10 | register:UseFormRegister; 11 | errors: FieldErrors; 12 | } 13 | const Input:React.FC = ({ 14 | id, 15 | type, 16 | placeholder, 17 | required, 18 | register, 19 | errors 20 | }) => { 21 | return ( 22 |
23 | 29 |
30 | ) 31 | } 32 | 33 | export default Input -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "incremental": true, 21 | "paths": { 22 | "@/*": [ 23 | "./*" 24 | ] 25 | }, 26 | "plugins": [ 27 | { 28 | "name": "next" 29 | } 30 | ] 31 | }, 32 | "include": [ 33 | "next-env.d.ts", 34 | "**/*.ts", 35 | "**/*.tsx", 36 | ".next/types/**/*.ts" 37 | ], 38 | "exclude": [ 39 | "node_modules" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /app/api/listings/route.ts: -------------------------------------------------------------------------------- 1 | import getCurrentUser from "@/app/actions/getCurrentUser"; 2 | import { NextResponse } from "next/server"; 3 | import prisma from '@/app/libs/prismadb'; 4 | 5 | 6 | 7 | export async function POST(request: Request){ 8 | const user = await getCurrentUser(); 9 | 10 | if(!user){ 11 | return NextResponse.error(); 12 | } 13 | 14 | const body = await request.json(); 15 | 16 | const {imageSrc, category, roomCount, location} = body; 17 | 18 | Object.keys(body).forEach((value :any) => { 19 | if(!body[value]){ 20 | NextResponse.error() 21 | } 22 | }) 23 | const listing = await prisma.listing.create({ 24 | data:{ 25 | imageSrc, 26 | category, 27 | roomCount, 28 | locationValue: location.value, 29 | userId: user.id 30 | } 31 | }) 32 | if(!listing){ 33 | NextResponse.error(); 34 | } 35 | return NextResponse.json(listing); 36 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ry", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@auth/prisma-adapter": "^0.0.0-manual.70f2982a", 13 | "@prisma/client": "^4.15.0", 14 | "@reduxjs/toolkit": "^1.9.5", 15 | "@types/node": "20.2.5", 16 | "@types/react": "18.2.7", 17 | "@types/react-dom": "18.2.4", 18 | "autoprefixer": "10.4.14", 19 | "axios": "^1.4.0", 20 | "bcrypt": "^5.1.0", 21 | "next": "13.4.4", 22 | "next-auth": "^4.22.1", 23 | "postcss": "8.4.24", 24 | "react": "18.2.0", 25 | "react-dom": "18.2.0", 26 | "react-hook-form": "^7.44.2", 27 | "react-redux": "^8.0.7", 28 | "react-select": "^5.7.3", 29 | "react-toastify": "^9.1.3", 30 | "tailwindcss": "3.3.2", 31 | "typescript": "5.0.4", 32 | "world-countries": "^4.0.0" 33 | }, 34 | "devDependencies": { 35 | "@types/bcrypt": "^5.0.0", 36 | "prisma": "^4.15.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/components/listings/CountrySelect.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import Select from 'react-select' 3 | import Countries from 'world-countries'; 4 | 5 | export const getCountries = Countries?.map(countries => { 6 | return { 7 | name : countries.name.common, 8 | flag: countries.flag, 9 | lating: countries.latlng 10 | } 11 | }) 12 | 13 | type CountrySelectProps = { 14 | value?: string | any; 15 | onChange: (value : any) => void 16 | } 17 | 18 | const options : any = getCountries.map(country => ({value: country.name, label: country.name, flag: country.flag})) 19 | const CountrySelect:React.FC = ({value, onChange}) => { 20 | return ( 21 |
22 | 52 | 60 |
61 | ) 62 | const footerElement = ( 63 |
64 |
71 | ) 72 | return ( 73 |
74 | {dispatch(loginModalFunc())}} btnLabel='Login' title='Login'/> 75 |
76 | ) 77 | } 78 | 79 | export default LoginModal -------------------------------------------------------------------------------- /app/components/modals/RegisterModal.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { FieldValues, SubmitHandler, useForm } from "react-hook-form"; 4 | import Modal from "./Modal" 5 | import Input from "../inputs/Input"; 6 | import Button from "../buttons/Button"; 7 | import {FcGoogle} from 'react-icons/fc' 8 | import { useAppDispatch, useAppSelector } from "@/app/redux/hooks"; 9 | import { registerModalFunc } from "@/app/redux/modalSlice"; 10 | import axios from "axios"; 11 | import { toast } from "react-toastify"; 12 | import { signIn } from "next-auth/react"; 13 | 14 | const RegisterModal = () => { 15 | const { register, handleSubmit, watch, formState: { errors } } = useForm({ 16 | defaultValues:{ 17 | name: "", 18 | email:"", 19 | password:"" 20 | } 21 | }); 22 | const {registerModal} = useAppSelector(state => state.modal); 23 | const dispatch = useAppDispatch(); 24 | const onSubmit: SubmitHandler = (data) => { 25 | console.log(data); 26 | axios.post('/api/register', data) 27 | .then(() => { 28 | dispatch(registerModalFunc()) 29 | toast.success('Register işlemi basarılı!!!') 30 | }) 31 | .catch((err: any) => { 32 | toast.error('Register işlemi hatalı!!!') 33 | }) 34 | } 35 | 36 | const bodyElement = ( 37 |
38 | 46 | 54 | 62 |
63 | ) 64 | const footerElement = ( 65 |
66 |
73 | ) 74 | return ( 75 |
76 | {dispatch(registerModalFunc())}} btnLabel='Register' title='Register'/> 77 |
78 | ) 79 | } 80 | 81 | export default RegisterModal -------------------------------------------------------------------------------- /app/components/navbar/UserMenu.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import {useState} from 'react' 3 | import {GiHamburgerMenu} from 'react-icons/gi'; 4 | import Image from 'next/image'; 5 | import UserMenuItem from './UserMenuItem'; 6 | import { useAppDispatch } from '@/app/redux/hooks'; 7 | import { elementModalFunc, loginModalFunc, registerModalFunc } from '@/app/redux/modalSlice'; 8 | import { User } from '@prisma/client'; 9 | import { signOut } from "next-auth/react" 10 | 11 | const UserMenu = ({user} : {user: User | any | undefined}) => { 12 | const [openMenu, setOpenMenu] = useState(false) 13 | const dispatch = useAppDispatch(); 14 | 15 | console.log(user, "user") 16 | return ( 17 |
setOpenMenu(!openMenu)} className='relative flex items-center gap-2 cursor-pointer'> 18 |
{user?.name}
19 | 20 | 27 | 28 | { 29 | openMenu && ( 30 |
31 | { 32 | user ? ( 33 | <> 34 | {dispatch(elementModalFunc())}} 37 | /> 38 | {}} 41 | /> 42 | {signOut()}} 45 | /> 46 | 47 | ) : 48 | ( 49 | <> 50 | {dispatch(loginModalFunc())}} 53 | /> 54 | {dispatch(registerModalFunc())}} 57 | /> 58 | 59 | ) 60 | } 61 | 62 |
63 | ) 64 | } 65 |
66 | ) 67 | } 68 | 69 | export default UserMenu -------------------------------------------------------------------------------- /app/components/modals/ElementModal.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { FieldValues, SubmitHandler, useForm } from "react-hook-form"; 4 | import Modal from "./Modal" 5 | import Input from "../inputs/Input"; 6 | import Button from "../buttons/Button"; 7 | import {FcGoogle} from 'react-icons/fc' 8 | import { useAppDispatch, useAppSelector } from "@/app/redux/hooks"; 9 | import { elementModalFunc, loginModalFunc, registerModalFunc } from "@/app/redux/modalSlice"; 10 | import { signIn } from "next-auth/react"; 11 | import { useRouter } from "next/navigation"; 12 | import { toast } from "react-toastify"; 13 | import { categories } from "../navbar/Categories"; 14 | import CategorySelect from "../listings/CategorySelect"; 15 | import CountrySelect from "../listings/CountrySelect"; 16 | import CounterSelect from "../listings/CounterSelect"; 17 | import { useState } from "react"; 18 | import Image from "next/image"; 19 | import axios from "axios"; 20 | 21 | const ElementModal = () => { 22 | 23 | const [imgsSrc, setImgsSrc] = useState([]) 24 | const { register, handleSubmit, watch,setValue,reset, formState: { errors } } = useForm({ 25 | defaultValues:{ 26 | imageSrc:'', 27 | category:'', 28 | roomCount:1, 29 | location: null 30 | } 31 | }); 32 | const router = useRouter() 33 | const {elementModal} = useAppSelector(state => state.modal); 34 | const dispatch = useAppDispatch(); 35 | const onSubmit: SubmitHandler = (data) => { 36 | axios.post('/api/listings', data) 37 | .then(() => { 38 | toast.success('ekleme işlemi basarılı') 39 | router.refresh(); 40 | reset() 41 | dispatch(elementModalFunc()) 42 | }) 43 | .catch((err) => { 44 | toast.error('ekleme işlemi basarısız!!') 45 | console.log(err, "err") 46 | }) 47 | } 48 | 49 | const category = watch('category'); 50 | const roomCount = watch('roomCount'); 51 | const imageSrc = watch('imageSrc'); 52 | const location = watch('location'); 53 | 54 | const customSetValue = (id: string, value: any) => { 55 | setValue(id, value, { 56 | shouldValidate: true, 57 | shouldDirty: true, 58 | shouldTouch: true 59 | }) 60 | } 61 | 62 | const imageSelectFunc = (e: any) => { 63 | for (const file of e.target.files) { 64 | const reader = new FileReader(); 65 | reader.readAsDataURL(file); 66 | reader.onload = () => { 67 | setImgsSrc((imgs):any => [...imgs, reader.result] ) 68 | return customSetValue('imageSrc',reader.result) 69 | } 70 | reader.onerror = () => { 71 | console.log(reader.error) 72 | } 73 | } 74 | } 75 | 76 | const bodyElement = ( 77 | <> 78 |
79 | { 80 | categories.map((cat,i) => ( 81 | {customSetValue('category', category)}} 86 | selected = {category == cat.name} 87 | /> 88 | )) 89 | } 90 |
91 |
92 | {customSetValue('location', value)}} 95 | /> 96 |
97 |
98 | {customSetValue('roomCount', value)} } 103 | /> 104 |
105 | imageSelectFunc(val)} /> 106 |
107 | 113 |
114 | 115 | ) 116 | 117 | return ( 118 |
119 | {dispatch(elementModalFunc())}} btnLabel='Create' title='Create Listing'/> 120 |
121 | ) 122 | } 123 | 124 | export default ElementModal --------------------------------------------------------------------------------