Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to NextJs App Router #26

Open
wants to merge 79 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
4da5793
fixes #14 and #15
timfee May 31, 2023
5e387f0
fix font rendering
timfee May 31, 2023
a6bed36
feat: Create LEAD_TIME variable with default 0
trillium May 27, 2023
8a69fc9
feat: Update getAvailability to use leadTime
trillium May 27, 2023
f0f1a00
fix: Solve timezones for app slots bug back-end
trillium Jun 13, 2023
f7d7cd8
fix: fix useEffect bug for first avail suggestion
trillium Jun 13, 2023
8ae4900
fix: DayButton isDisabled boolean correction
trillium Jun 15, 2023
256734f
feat: localeDayString uses date-fns-tz.format
trillium Jun 16, 2023
99fe68c
fix: Force getDateRangeInterval to use "Etc/GMT"
trillium Jun 18, 2023
332bc93
wip: Rename pages dir to pages_old
trillium Jul 10, 2024
219bfb5
wip: Move booked.tsx to app router app/booked
trillium Jul 5, 2024
e3fc4f8
wip: Rename app/booked/booked to app/booked/ClientPage.tsx
trillium Jul 5, 2024
9fc88de
feat: Update app/booked to use "use client" and searchParams
trillium Jul 5, 2024
edd9744
feat: Create server page component for /booked
trillium Jul 5, 2024
d4876fa
wip: Move confirmation.tsx from /pages to /app router
trillium Jul 5, 2024
0c24a10
wip: Rename confirmation.tsx ClientPage.tsx in app router
trillium Jul 5, 2024
a28e6aa
feat: Update app/confirmation to use app router language
trillium Jul 5, 2024
3c3907a
feat: Create server page component for /confirmation
trillium Jul 5, 2024
1c639f0
chore: Move pages_old/api/request to app/api/request/request
trillium Jul 5, 2024
0d3ad25
chore: Rename request.ts to route.ts
trillium Jul 5, 2024
e79f870
wip: Rename handler to POST in appprequest/route
trillium Jul 5, 2024
0815e6f
wip: Update types and function signature for POST
trillium Jul 5, 2024
d63f600
wip: update request/route to use next/headers
trillium Jul 5, 2024
66e0553
wip: Build jsonData from req.json instead of res.body
trillium Jul 5, 2024
75b8c87
wip: Remove NextResponse
trillium Jul 5, 2024
f45d33e
wip: Add NextResponse to imorts in request/route.ts
trillium Jul 5, 2024
9126012
wip: use NextResponse.json instead of res.status
trillium Jul 5, 2024
6dfad3f
style: Remove blank line
trillium Jul 5, 2024
55c31a8
wip: Move confirm.ts to app/api/confirm
trillium Jul 5, 2024
ed08fc0
wip: Rename confirm.ts to route.ts in app/api/confirm
trillium Jul 5, 2024
d866240
wip: Rename handler to GET, change to named export
trillium Jul 5, 2024
e79d3a4
wip: Change GET import to NextRequest type
trillium Jul 5, 2024
67e2174
wip: Switch to using searchParams for data, key
trillium Jul 5, 2024
fca6abc
wip: Sitch to using redirect for next/navigation
trillium Jul 5, 2024
4817a78
style: searchParams ' --> "
trillium Jul 5, 2024
7acdc8f
wip: Use NextResponse for returning status codes
trillium Jul 5, 2024
d6ab1be
style: NextRepsone quotes fix
trillium Jul 5, 2024
3f15324
wip: Remove unused import types
trillium Jul 5, 2024
74a21eb
wip: NextResponse remove double return
trillium Jul 5, 2024
c695c63
wip: Remove console.log statements and old debug comments
trillium Jul 5, 2024
b9adc40
wip: Move _app.tsx to /app dir
trillium Jul 5, 2024
2f71d76
wip: Rename _app to layout in /app dir
trillium Jul 5, 2024
50ad14a
wip: Rename export App to RootLayout, edit function signature to retu…
trillium Jul 5, 2024
ed80b3d
wip: Change Component render to children render
trillium Jul 5, 2024
3743852
wip: Swich Head to MetaData, create obj {}
trillium Jul 5, 2024
a19c282
wip: Move title to metadata object
trillium Jul 5, 2024
cc0df5c
wip: Switch <Head> to html for layout
trillium Jul 5, 2024
385f17f
wip: Move html to outermost jsx element
trillium Jul 5, 2024
11d538f
wip: Move <link> elements directly below <html>
trillium Jul 5, 2024
c5a24ba
wip: Move React.Fragment down to not encompass link elements
trillium Jul 5, 2024
74e1a0e
wip: Drop unnecessary React.fragment
trillium Jul 5, 2024
cf8dc6a
wip: Move public_sans.className to <html>
trillium Jul 5, 2024
6c40f39
wip: Add in <body> element to wrap app
trillium Jul 5, 2024
380d554
wip: Move pages_old/index to app dir
trillium Jul 5, 2024
8c19d2f
wip: Duplicate index.tsx for adapting to ClientPage
trillium Jul 5, 2024
863f1fe
wip: Remove separate downstream objects/props
trillium Jul 10, 2024
9aa0b98
wip: Rename index_client to ClientPage
trillium Jul 5, 2024
1732c53
wip: Add "use client" to ClientPage.tsx
trillium Jul 5, 2024
88ae3e1
wip: Drop getServerSideProps from ClientPage
trillium Jul 5, 2024
6c72ce2
wip: Drop unused imports from ClientPage
trillium Jul 5, 2024
e8786c3
wip: Switch to PageProps import from app/page.tsx
trillium Jul 5, 2024
180f1de
wip: Rename index.tsx to page.tsx
trillium Jul 5, 2024
ba5a69e
wip: Drop function Page export, useEffect import from app/page
trillium Jul 5, 2024
d091ba1
wip: Rename getServerSideProps to fetchData in app/page
trillium Jul 5, 2024
c43be76
wip: Extra query params with searchParams from in page.tsx
trillium Jul 5, 2024
c89b9b0
feat: Pass data to ClientPage in default Page export
trillium Jul 5, 2024
f3739da
feat: Force app/page to always fetch new data on load
trillium Jul 5, 2024
7f86349
wip: Drop unusde imports in app/page.tsx
trillium Jul 5, 2024
6a58206
wip: Use @ import for ClientPage component
trillium Jul 5, 2024
2ff8759
fix: Update PageProps import location
trillium Jul 5, 2024
77e802d
feat: Add react path to tsconfig
trillium Jul 8, 2024
99d5073
feat: Add app directory in tailwind confing content
trillium Jul 10, 2024
0ca8bb0
style: Remove react.fragment
trillium Jul 10, 2024
82e1da8
feat: Regenerate yarn.lock
trillium Jul 10, 2024
13638e7
Merge branch 'main' into migrate.appRouter
trillium Jul 10, 2024
a117c76
fix: Remove export from fetchData call
trillium Jul 5, 2024
b624839
fix: Fix blank render for booked/ClientPage
trillium Jul 7, 2024
bd6fb6c
fix: Update router type
trillium May 10, 2024
a391f07
feat: Remove pnpm.lock
trillium Jul 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 8 additions & 59 deletions pages/index.tsx → app/ClientPage.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,25 @@
import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next"
"use client"

import type { InferGetServerSidePropsType } from "next"
import { useEffect } from "react"
import { z } from "zod"

import Template from "@/components/Template"
import AvailabilityPicker from "@/components/availability/AvailabilityPicker"
import {
ALLOWED_DURATIONS,
DEFAULT_DURATION,
OWNER_AVAILABILITY,
} from "@/config"
import { OWNER_AVAILABILITY } from "@/config"
import { useProvider, withProvider } from "@/context/AvailabilityContext"
import getAvailability from "@/lib/availability/getAvailability"
import getBusyTimes from "@/lib/availability/getBusyTimes"
import getPotentialTimes from "@/lib/availability/getPotentialTimes"
import {
getDateRangeInterval,
mapDatesToStrings,
mapStringsToDates,
} from "@/lib/availability/helpers"
import { mapStringsToDates } from "@/lib/availability/helpers"
import Day from "@/lib/day"
import localeDayString from "@/lib/locale"

export type PageProps = InferGetServerSidePropsType<typeof getServerSideProps>
import PageProps from "./page"

function Page({
start,
end,
busy,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
}: InferGetServerSidePropsType<typeof PageProps>) {
const {
state: { duration, selectedDate },
dispatch,
Expand Down Expand Up @@ -62,7 +51,7 @@ function Page({
// with some availability.
useEffect(() => {
if (!selectedDate && slots.length > 0) {
const date: Date = slots[0].start;
const date: Date = slots[0].start
const dateString: string = localeDayString(date)

dispatch({
Expand All @@ -82,44 +71,4 @@ function Page({
)
}

export async function getServerSideProps({ query }: GetServerSidePropsContext) {
const schema = z.object({
duration: z
.enum([...(ALLOWED_DURATIONS.map(String) as [string, ...string[]])])
.optional()
.default(String(DEFAULT_DURATION))
.transform(Number),
timeZone: z.string().optional(),
selectedDate: z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/u)
.optional(),
})

const { duration, timeZone, selectedDate } = schema.parse(query)

// Offer two weeks of availability.
const start = Day.todayWithOffset(0)
const end = Day.todayWithOffset(14)

const busy = await getBusyTimes(
getDateRangeInterval({
start,
end,
timeZone,
})
)

return {
props: {
start: start.toString(),
end: end.toString(),
busy: mapDatesToStrings(busy),
duration,
...(timeZone && { timeZone }),
...(selectedDate && { selectedDate }),
},
}
}

export default withProvider(Page)
43 changes: 24 additions & 19 deletions pages/api/confirm.ts → app/api/confirm/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { NextApiRequest, NextApiResponse } from "next"
import { NextResponse } from "next/server"
import { type NextRequest } from "next/server"
import { redirect } from "next/navigation"
import { z } from "zod"

import createCalendarAppointment from "@/lib/availability/createAppointment"
Expand All @@ -18,27 +20,24 @@ const AppointmentPropsSchema = z.object({
}),
})

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
export async function GET(req: NextRequest) {
if (req.method !== "GET") {
res.status(405).json({ error: "Method not allowed" })
return
return NextResponse.json({ error: "Method not allowed" }, { status: 405 })
}

const { data, key } = req.query
const searchParams = req.nextUrl.searchParams

const data = searchParams.get("data")
const key = searchParams.get("key")

if (!data) {
res.status(400).json({ error: "Data is missing" })
return
return NextResponse.json({ error: "Data is missing" }, { status: 400 })
}
// Make sure the hash matches before doing anything
const hash = getHash(decodeURIComponent(data as string))

if (hash !== key) {
res.status(403).json({ error: "Invalid key" })
return
return NextResponse.json({ error: "Invalid key" }, { status: 403 })
}

const object = JSON.parse(decodeURIComponent(data as string))
Expand All @@ -47,8 +46,10 @@ export default async function handler(
const validationResult = AppointmentPropsSchema.safeParse(object)

if (!validationResult.success) {
res.status(400).json({ error: "Malformed request" })
return
return NextResponse.json(
{ error: "Malformed request in data validation" },
{ status: 400 }
)
}

const validObject = validationResult.data
Expand All @@ -58,8 +59,10 @@ export default async function handler(
Number.isNaN(Date.parse(validObject.start)) ||
Number.isNaN(Date.parse(validObject.end))
) {
res.status(400).json({ error: "Malformed request" })
return
return NextResponse.json(
{ error: "Malformed request in date parsing" },
{ status: 400 }
)
}

// Create the confirmed appointment
Expand All @@ -77,11 +80,13 @@ export default async function handler(

// If we have a link to the event, take us there.
if (match && match[1]) {
res.redirect(`/booked?url=${encodeURIComponent(match[1])}`)

redirect(`/booked?url=${encodeURIComponent(match[1])}`)
return
}

// Otherwise, something's wrong.
res.status(500).json({ error: "Error trying to create an appointment" })
return NextResponse.json(
{ error: "Error trying to create an appointment" },
{ status: 500 }
)
}
32 changes: 16 additions & 16 deletions pages/api/request.ts → app/api/request/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { NextRequest, NextResponse } from "next/server"
import { headers as nextHeaders } from "next/headers"
import { IncomingMessage } from "http"

import LRUCache from "lru-cache"
import type { NextApiRequest, NextApiResponse } from "next"
import { z } from "zod"

import { OWNER_TIMEZONE } from "@/config"
Expand Down Expand Up @@ -36,38 +39,35 @@ const AppointmentRequestSchema = z.object({
}),
})

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
): Promise<void> {
export async function POST(
req: NextRequest & IncomingMessage
): Promise<NextResponse> {
const headers = nextHeaders()
const jsonData = await req.json()
if (req.method !== "POST") {
res.status(405).json({ error: "Method not allowed" })
return
return NextResponse.json({ error: "Method not allowed" }, { status: 405 })
}

// Apply rate limiting using the client's IP address

const limitReached = checkRateLimit()

if (limitReached) {
res.status(429).json({ error: "Rate limit exceeded" })
return
return NextResponse.json({ error: "Rate limit exceeded" }, { status: 429 })
}

// Validate and parse the request body using Zod
const validationResult = AppointmentRequestSchema.safeParse(req.body)
const validationResult = AppointmentRequestSchema.safeParse(jsonData)

if (!validationResult.success) {
res.status(400).json({ error: validationResult.error.message })
return
return NextResponse.json(validationResult.error.message, { status: 400 })
}
const { data } = validationResult

const start = new Date(data.start)
const end = new Date(data.end)

const approveUrl = `${
req.headers.origin ?? "?"
headers.get("origin") ?? "?"
}/api/confirm/?data=${encodeURIComponent(JSON.stringify(data))}&key=${getHash(
JSON.stringify(data)
)}`
Expand Down Expand Up @@ -102,15 +102,15 @@ export default async function handler(
body: confirmationEmail.body,
})

res.status(200).json({ success: true })
return NextResponse.json({ success: true }, { status: 200 })

/**
* Checks the rate limit for the current IP address.
*
* @return {boolean} Whether the rate limit has been reached.
*/
function checkRateLimit(): boolean {
const forwarded = req.headers["x-forwarded-for"]
const forwarded = headers.get("x-forwarded-for")
const ip =
(Array.isArray(forwarded) ? forwarded[0] : forwarded) ??
req.socket.remoteAddress ??
Expand Down
20 changes: 15 additions & 5 deletions pages/booked.tsx → app/booked/ClientPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { useRouter } from "next/router"
"use client"

import { useSearchParams } from "next/navigation"

export default function Booked() {
const { query } = useRouter()
const searchParams = useSearchParams()

const url = searchParams.get("url")

if (!query || typeof query.url !== "string") {
return
if (!url || typeof url !== "string") {
return (
<div className="py-8 sm:py-16 mx-auto max-w-xl">
<h1 className="text-3xl font-bold tracking-tight text-secondary-700">
There was an error with the url parameter.
</h1>
</div>
)
}
return (
<div className="py-8 sm:py-16 mx-auto max-w-xl">
Expand All @@ -14,7 +24,7 @@ export default function Booked() {
<p className="mt-6 text-xl text-gray-800 font-medium">
It’s now on your calendar and an invite has been sent to them.{" "}
<a
href={"https://www.google.com/calendar/event?eid=" + query.url}
href={"https://www.google.com/calendar/event?eid=" + url}
target="_blank"
rel="noreferrer"
className="text-blue-700 underline">
Expand Down
7 changes: 7 additions & 0 deletions app/booked/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react"

import ClientPage from "./ClientPage"

export default async function Page() {
return <ClientPage />
}
2 changes: 2 additions & 0 deletions pages/confirmation.tsx → app/confirmation/ClientPage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

export default function Confirmation() {
return (
<div className="py-8 sm:py-16 mx-auto max-w-2xl">
Expand Down
7 changes: 7 additions & 0 deletions app/confirmation/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react"

import ClientPage from "./ClientPage"

export default async function Page() {
return <ClientPage />
}
39 changes: 39 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react"
import { Public_Sans } from "next/font/google"
import { Metadata } from "next"

import "../styles/global.css"

const public_sans = Public_Sans({
subsets: ["latin"],
weight: "variable",
display: "swap",
})

export const metadata: Metadata = {
title: `Meet with ${process.env.NEXT_PUBLIC_OWNER_NAME ?? "me"}`,
}

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={public_sans.className} suppressHydrationWarning>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<body className="h-full">{children}</body>
</html>
)
}
Loading