diff --git a/package.json b/package.json index 84aeb3d..d274db0 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@hookform/resolvers": "^3.3.1", "@mui/icons-material": "^5.14.9", "@mui/material": "^5.14.10", "dayjs": "^1.11.10", diff --git a/src/app/api/hello/route.ts b/src/app/api/hello/route.ts deleted file mode 100644 index 7ac9b84..0000000 --- a/src/app/api/hello/route.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { NextResponse } from 'next/server'; - -export async function GET() { - return NextResponse.json({ message: 'Test getApiResponse success!' }); -} diff --git a/src/app/api/test/route.ts b/src/app/api/test/route.ts new file mode 100644 index 0000000..53ab123 --- /dev/null +++ b/src/app/api/test/route.ts @@ -0,0 +1,20 @@ +import { NextResponse } from 'next/server'; + +export const GET = async (req: Request) => { + const { searchParams } = new URL(req.url); + const reqData = Object.fromEntries(searchParams); + return NextResponse.json({ + message: 'Test getApiResponse GET success!', + method: 'GET', + reqData, + }); +}; + +export const POST = async (req: Request) => { + const reqData = await req.json(); + return NextResponse.json({ + message: 'Test postApiResponse POST success!', + method: 'POST', + reqData, + }); +}; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1b07f90..f0157b6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,9 @@ +import { Container } from '@mui/material'; +import GlobalStyles from '@mui/material/GlobalStyles'; import { Metadata } from 'next'; import * as React from 'react'; -import { SITE_CONFIG } from '@/constant/config'; +import { GLOBAL_STYLES, SITE_CONFIG } from '@/constant'; // !STARTERCONF Change these default meta // !STARTERCONF Look at @/constant/config to change them @@ -50,8 +52,11 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - {children} + + + + {children} + ); } diff --git a/src/component/Homepage.tsx b/src/component/Homepage.tsx index 812e3d6..772fe20 100644 --- a/src/component/Homepage.tsx +++ b/src/component/Homepage.tsx @@ -3,6 +3,7 @@ import { Box, Typography } from '@mui/material'; import Link from 'next/link'; import PageFooter from '@/component/shared/PageFooter'; +import ReactHookForm from '@/component/shared/ReactHookForm'; import { SITE_CONFIG } from '@/constant'; export default function Homepage({ reactVersion = 'unknown' }) { @@ -11,10 +12,19 @@ export default function Homepage({ reactVersion = 'unknown' }) {
- + {SITE_CONFIG.title} - + {SITE_CONFIG.description} @@ -42,6 +52,20 @@ export default function Homepage({ reactVersion = 'unknown' }) { Click here to deploy a demo site to your Vercel in 1 minute + + + Test NextJs API method GET with parameters + + + + +

Test NextJs API method POST with parameters

+ +
+ Test 404 page not found diff --git a/src/component/shared/PageFooter.tsx b/src/component/shared/PageFooter.tsx index ec2560d..ab5c9a0 100644 --- a/src/component/shared/PageFooter.tsx +++ b/src/component/shared/PageFooter.tsx @@ -1,15 +1,20 @@ import { Box } from '@mui/material'; import * as React from 'react'; +import ServerDateTime from '@/component/shared/ServerDateTime'; + const PageFooter = () => { return (
- PageFooter.tsx © {new Date().getFullYear()} Boilerplate live example: + PageFooter.tsx © Boilerplate live example: HiHB + + +
); }; diff --git a/src/component/shared/ReactHookForm.tsx b/src/component/shared/ReactHookForm.tsx new file mode 100644 index 0000000..8eaa2ad --- /dev/null +++ b/src/component/shared/ReactHookForm.tsx @@ -0,0 +1,90 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { Box, Button, FormHelperText } from '@mui/material'; +import React from 'react'; +import { Controller, SubmitHandler, useForm } from 'react-hook-form'; +import { z } from 'zod'; + +import { consoleLog } from '@/util/shared/console-log'; +import { getApiResponse } from '@/util/shared/get-api-response'; + +const zodSchema = z.object({ + name: z.string().min(5).nonempty({ message: 'Name is required' }), + email: z.string().min(10).email({ message: 'Invalid email address' }), +}); + +type FormValues = z.infer; + +const ReactHookForm: React.FC = () => { + const apiEndpoint = '/api/test'; + const [apiResult, setApiResult] = React.useState(); + + const { + handleSubmit, + control, + formState: { errors }, + } = useForm({ + resolver: zodResolver(zodSchema), + }); + + const onSubmit: SubmitHandler = async (data) => { + try { + const result = await getApiResponse<{ + reqData: FormValues; + }>({ + apiEndpoint, + method: 'POST', + requestData: JSON.stringify(data), + }); + setApiResult(result?.reqData); + consoleLog('getApiResponse result', result, errors); + } catch (error) { + consoleLog('handleSubmit ERROR', error); + } + }; + + return ( +
+ + + } + /> + {errors.name && ( + + {errors.name.message} + + )} + + + + + } + /> + {errors.email && ( + + {errors.email.message} + + )} + + {apiResult && ( + + API result from {apiEndpoint}: {apiResult.name} & {apiResult.email} + + )} + +
+ ); +}; + +export default ReactHookForm; diff --git a/src/component/shared/ServerDateTime.tsx b/src/component/shared/ServerDateTime.tsx new file mode 100644 index 0000000..12a5ec6 --- /dev/null +++ b/src/component/shared/ServerDateTime.tsx @@ -0,0 +1,26 @@ +import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +const ServerDateTime = ({ + cityTimezone, + timeFormat = 'dddd, MMMM D, YYYY h:mm:ss A', + color, + date, +}: { + cityTimezone: string; + timeFormat?: string; + color?: string; + date?: string; +}) => { + return ( + + {dayjs(date).tz(cityTimezone).format(timeFormat)} + + ); +}; + +export default ServerDateTime; diff --git a/src/constant/config.ts b/src/constant/config.ts index 415887d..3efbf66 100644 --- a/src/constant/config.ts +++ b/src/constant/config.ts @@ -1,3 +1,5 @@ +import { blue, grey } from '@mui/material/colors'; + export const SITE_CONFIG = { title: 'NextJs 13.x + MUI 5.x + TypeScript Starter', description: @@ -10,3 +12,18 @@ export const HIDE_DEBUG_ARY = [ // 'getApiResponse', 'getMongoDbApiData', ]; + +export const GLOBAL_STYLES = { + body: { margin: 4 }, + '.page-title': { color: 'darkblue' }, + '.page-subtitle': { color: grey[600] }, + a: { + textDecoration: 'underline', + textDecorationColor: blue[800], + color: blue['700'], + fontSize: '1rem', + fontWeight: 400, + lineHeight: '1.8', + letterSpacing: '0.00938em', + }, +}; diff --git a/yarn.lock b/yarn.lock index a4eec41..7eadaef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1373,6 +1373,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.4.tgz#19654d1026cc410975d46445180e70a5089b3e7d" integrity sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA== +"@hookform/resolvers@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.3.1.tgz#b7cbfe767434f52cba6b99b0a9a0b73eb8895188" + integrity sha512-K7KCKRKjymxIB90nHDQ7b9nli474ru99ZbqxiqDAWYsYhOsU3/4qLxW91y+1n04ic13ajjZ66L3aXbNef8PELQ== + "@humanwhocodes/config-array@^0.11.11": version "0.11.11" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844"