Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
101 changes: 76 additions & 25 deletions src/app/api/checkout/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NextResponse } from "next/server";
import type { ApiTime } from "@/types";

export interface payloadProps {
experienceId: string;
Expand All @@ -16,27 +17,76 @@ export interface payloadProps {
giftMessage?: string;
}

interface CheckoutCustomer {
firstName: string;
lastName: string;
email: string;
phone: string;
specialRequests?: string;
}

interface CheckoutGiftDetails {
itemId: string;
recipientEmail: string;
message?: string;
}

interface CheckoutItem {
id: string;
experienceId: string;
date: string;
time?: string | ApiTime;
duration: string | number;
quantity: number;
isGift?: boolean;
redeemedBookingId?: string;
}

interface CheckoutRequestBody {
items: CheckoutItem[];
customer: CheckoutCustomer;
gifts?: CheckoutGiftDetails[];
}

const isApiErrorResponse = (value: unknown): value is { message?: string } =>
typeof value === "object" && value !== null && "message" in value;

const normaliseTimeValue = (time?: string | ApiTime): string => {
if (!time) return "";
if (typeof time === "string") {
return time;
}
const { hour, minute, period } = time;
return `${hour}:${minute} ${period}`;
};

export async function POST(req: Request) {
try {
const body = await req.json();
const body = (await req.json()) as CheckoutRequestBody;
const { items, customer, gifts } = body;

const bookingRequests = items.map(async (item: any) => {
const bookingRequests = items.map(async (item) => {
const durationValue =
typeof item.duration === "number"
? item.duration
: parseInt(item.duration, 10);

const payload: payloadProps = {
experienceId: item.experienceId,
selectedDate: item.date,
selectedTime: item.time,
duration: parseInt(item.duration, 10),
selectedTime: normaliseTimeValue(item.time),
duration: Number.isFinite(durationValue) ? durationValue : 0,
quantity: item.quantity,
name: `${customer.firstName} ${customer.lastName}`,
name: `${customer.firstName} ${customer.lastName}`.trim(),
email: customer.email,
phoneNumber: customer.phone,
specialRequests: customer.specialRequests,
specialRequests: customer.specialRequests ?? "",
};
if (item.isGift === undefined || item.isGift === false) {

if (!item.isGift) {
payload.status = "CONFIRMED";
} else {
const gift = gifts?.find((gift: any) => gift.itemId === item.id);
const gift = gifts?.find((giftItem) => giftItem.itemId === item.id);
if (!gift) {
throw new Error(`Gift details missing for cart item ${item.id}`);
}
Expand All @@ -58,9 +108,11 @@ export async function POST(req: Request) {
);

if (!response.ok) {
throw new Error(
`Failed to create booking: ${response.status} - ${response.body}`
);
const errorData = await response.json().catch(() => null);
const message = isApiErrorResponse(errorData)
? errorData.message
: response.statusText;
throw new Error(`Failed to create booking: ${message}`);
}
} else {
const response = await fetch(
Expand All @@ -78,12 +130,11 @@ export async function POST(req: Request) {
);

if (!response.ok) {
const errorData = await response.json();
throw new Error(
`Failed to redeem booking: ${
errorData.message || response.statusText
}`
);
const errorData = await response.json().catch(() => null);
const message = isApiErrorResponse(errorData)
? errorData.message
: response.statusText;
throw new Error(`Failed to redeem booking: ${message}`);
}
}
});
Expand All @@ -94,14 +145,14 @@ export async function POST(req: Request) {
{ message: "All bookings created successfully" },
{ status: 201 }
);
} catch (err: any) {
console.error("API booking error:", err);
} catch (error: unknown) {
console.error("API booking error:", error);

return NextResponse.json(
{
message: err?.message || "An error occurred while creating bookings",
},
{ status: 500 }
);
const message =
error instanceof Error
? error.message
: "An error occurred while creating bookings";

return NextResponse.json({ message }, { status: 500 });
}
}
34 changes: 27 additions & 7 deletions src/app/api/reviews/[id]/helpful/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import { config } from "@/lib/config";
import { NextResponse } from "next/server";

const parseJsonSafely = async (
response: Response
): Promise<Record<string, unknown> | null> => {
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.includes("application/json")) {
return null;
}

const data = await response.json().catch(() => null);
if (data && typeof data === "object") {
return data as Record<string, unknown>;
}
return null;
};

// PATCH /api/reviews/[id]/helpful - Mark a review as helpful
export async function PATCH(
request: Request,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
try {
const { id } = await context.params;

const response = await fetch(
`${config.backendApiUrl}/reviews/${params.id}/helpful`,
`${config.backendApiUrl}/reviews/${id}/helpful`,
{
method: "PATCH",
headers: {
Expand All @@ -24,18 +41,21 @@ export async function PATCH(
}
);

const data = await response.json();
const data = await parseJsonSafely(response);

if (!response.ok) {
const errorMessage =
(data?.message as string | undefined) ||
"Failed to mark review as helpful";
return NextResponse.json(
{ error: data.message || "Failed to mark review as helpful" },
{ error: errorMessage },
{ status: response.status }
);
}

return NextResponse.json(data, { status: 200 });
} catch (error) {
console.error(`Error marking review ${params.id} as helpful:`, error);
return NextResponse.json(data ?? {}, { status: 200 });
} catch (error: unknown) {
console.error("Error marking review as helpful:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
Expand Down
86 changes: 41 additions & 45 deletions src/app/api/reviews/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import { NextResponse } from "next/server";
// GET /api/reviews/[id] - Get a specific review
export async function GET(
request: Request,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
try {
const response = await fetch(
`${config.backendApiUrl}/reviews/${params.id}`,
{
method: "GET",
cache: "no-store",
}
);
const { id } = await context.params;

const response = await fetch(`${config.backendApiUrl}/reviews/${id}`, {
method: "GET",
cache: "no-store",
});

const data = await response.json();

Expand All @@ -26,7 +25,7 @@ export async function GET(

return NextResponse.json(data, { status: 200 });
} catch (error) {
console.error(`Error fetching review ${params.id}:`, error);
console.error("Error fetching review:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
Expand All @@ -37,27 +36,25 @@ export async function GET(
// PUT /api/reviews/[id] - Update a specific review
export async function PUT(
request: Request,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
try {
const { id } = await context.params;
const body = await request.json();
const response = await fetch(
`${config.backendApiUrl}/reviews/${params.id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
...(request.headers.get("cookie")
? { cookie: request.headers.get("cookie") as string }
: {}),
...(request.headers.get("authorization")
? { authorization: request.headers.get("authorization") as string }
: {}),
},
body: JSON.stringify(body),
cache: "no-store",
}
);
const response = await fetch(`${config.backendApiUrl}/reviews/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
...(request.headers.get("cookie")
? { cookie: request.headers.get("cookie") as string }
: {}),
...(request.headers.get("authorization")
? { authorization: request.headers.get("authorization") as string }
: {}),
},
body: JSON.stringify(body),
cache: "no-store",
});

const data = await response.json();

Expand All @@ -70,7 +67,7 @@ export async function PUT(

return NextResponse.json(data, { status: 200 });
} catch (error) {
console.error(`Error updating review ${params.id}:`, error);
console.error("Error updating review:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
Expand All @@ -81,24 +78,23 @@ export async function PUT(
// DELETE /api/reviews/[id] - Delete a specific review
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
try {
const response = await fetch(
`${config.backendApiUrl}/reviews/${params.id}`,
{
method: "DELETE",
headers: {
...(request.headers.get("cookie")
? { cookie: request.headers.get("cookie") as string }
: {}),
...(request.headers.get("authorization")
? { authorization: request.headers.get("authorization") as string }
: {}),
},
cache: "no-store",
}
);
const { id } = await context.params;

const response = await fetch(`${config.backendApiUrl}/reviews/${id}`, {
method: "DELETE",
headers: {
...(request.headers.get("cookie")
? { cookie: request.headers.get("cookie") as string }
: {}),
...(request.headers.get("authorization")
? { authorization: request.headers.get("authorization") as string }
: {}),
},
cache: "no-store",
});

if (response.status === 204 || response.ok) {
return new NextResponse(null, { status: 204 });
Expand All @@ -110,7 +106,7 @@ export async function DELETE(
{ status: response.status }
);
} catch (error) {
console.error(`Error deleting review ${params.id}:`, error);
console.error("Error deleting review:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
Expand Down
Loading