-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add checkout and confirmation pages, implement order service, and upd…
…ate routing
- Loading branch information
Showing
11 changed files
with
388 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
"use client"; | ||
|
||
import React, { useState } from 'react'; | ||
import { useRouter } from 'next/navigation'; | ||
import { useCart } from '@/context/CartContext'; | ||
import { useAuth } from '@/context/AuthContext'; | ||
import { orderService } from '@/services/orderService'; | ||
import { Button } from "@/components/ui/button"; | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardFooter, | ||
CardHeader, | ||
CardTitle, | ||
} from "@/components/ui/card"; | ||
import { useToast } from '@/hooks/use-toast'; | ||
import { Separator } from '@/components/ui/separator'; | ||
|
||
export function CheckoutPage() { | ||
const { cart, totalPrice, clearCart } = useCart(); | ||
const { user } = useAuth(); | ||
const { toast } = useToast(); | ||
const router = useRouter(); | ||
const [isProcessing, setIsProcessing] = useState(false); | ||
|
||
const handleCheckout = async () => { | ||
if (!user) { | ||
toast({ | ||
title: "Please log in", | ||
description: "You need to be logged in to checkout", | ||
variant: "destructive", | ||
}); | ||
router.push('/auth/login'); | ||
return; | ||
} | ||
|
||
try { | ||
setIsProcessing(true); | ||
|
||
// Create order lines from cart | ||
const orderLines = cart.map(item => ({ | ||
productId: item.id, | ||
quantity: item.quantity, | ||
...(item.selectedVariant && { variantId: item.selectedVariant.id }), | ||
})); | ||
|
||
// Create order | ||
const orderResponse = await orderService.createOrder(user.id, { | ||
currency: "DKK", | ||
orderLines, | ||
}); | ||
|
||
// Create MobilePay payment | ||
const paymentResponse = await orderService.createMobilePayPayment({ | ||
orderId: orderResponse.order.id.toString(), | ||
currency: "DKK", | ||
paymentMethod: "WALLET", | ||
returnUrl: `${window.location.origin}/checkout/confirmation`, | ||
}); | ||
|
||
// Clear cart and redirect to MobilePay | ||
clearCart(); | ||
window.location.href = paymentResponse.redirectUrl; | ||
|
||
} catch (error) { | ||
toast({ | ||
title: "Checkout failed", | ||
description: error instanceof Error ? error.message : "Please try again", | ||
variant: "destructive", | ||
}); | ||
} finally { | ||
setIsProcessing(false); | ||
} | ||
}; | ||
|
||
const formatPrice = (amount: number) => { | ||
return new Intl.NumberFormat('da-DK', { | ||
style: 'currency', | ||
currency: 'DKK', | ||
}).format(amount); | ||
}; | ||
|
||
if (cart.length === 0) { | ||
return ( | ||
<div className="container mx-auto py-8 text-center"> | ||
<h1 className="text-2xl font-bold mb-4">Your cart is empty</h1> | ||
<Button onClick={() => router.push('/')}> | ||
Continue Shopping | ||
</Button> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<div className="container mx-auto py-8"> | ||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8"> | ||
{/* Order Summary */} | ||
<div> | ||
<Card> | ||
<CardHeader> | ||
<CardTitle>Order Summary</CardTitle> | ||
<CardDescription>Review your items</CardDescription> | ||
</CardHeader> | ||
<CardContent className="space-y-4"> | ||
{cart.map((item) => ( | ||
<div key={item.cartItemId} className="flex justify-between"> | ||
<div> | ||
<p className="font-medium">{item.name}</p> | ||
{item.selectedVariant && ( | ||
<p className="text-sm text-muted-foreground"> | ||
Size: {item.selectedVariant.options[0].value} | ||
</p> | ||
)} | ||
<p className="text-sm text-muted-foreground"> | ||
Quantity: {item.quantity} | ||
</p> | ||
</div> | ||
<p className="font-medium"> | ||
{formatPrice(item.price.amount * item.quantity)} | ||
</p> | ||
</div> | ||
))} | ||
</CardContent> | ||
<CardFooter className="flex flex-col"> | ||
<Separator className="my-4" /> | ||
<div className="flex justify-between w-full"> | ||
<p className="font-bold">Total</p> | ||
<p className="font-bold">{formatPrice(totalPrice)}</p> | ||
</div> | ||
</CardFooter> | ||
</Card> | ||
</div> | ||
|
||
{/* Payment */} | ||
<div> | ||
<Card> | ||
<CardHeader> | ||
<CardTitle>Payment</CardTitle> | ||
<CardDescription>Choose your payment method</CardDescription> | ||
</CardHeader> | ||
<CardContent> | ||
<p className="text-sm text-muted-foreground mb-4"> | ||
You will be redirected to MobilePay to complete your payment | ||
</p> | ||
</CardContent> | ||
<CardFooter> | ||
<Button | ||
className="w-full" | ||
onClick={handleCheckout} | ||
disabled={isProcessing} | ||
> | ||
{isProcessing ? "Processing..." : "Pay with MobilePay"} | ||
</Button> | ||
</CardFooter> | ||
</Card> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
61 changes: 61 additions & 0 deletions
61
src/app/(client)/checkout/_components/ConfirmationPage.tsx
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,61 @@ | ||
"use client"; | ||
|
||
import React from 'react'; | ||
import { useRouter, useSearchParams } from 'next/navigation'; | ||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; | ||
import { Button } from "@/components/ui/button"; | ||
import { CheckCircle, XCircle } from "lucide-react"; | ||
|
||
export function ConfirmationPage() { | ||
const router = useRouter(); | ||
const searchParams = useSearchParams(); | ||
const success = searchParams.get('success') === 'true'; | ||
const orderId = searchParams.get('orderId'); | ||
|
||
return ( | ||
<div className="container mx-auto py-16"> | ||
<Card className="max-w-md mx-auto text-center"> | ||
<CardHeader> | ||
<div className="flex justify-center mb-4"> | ||
{success ? ( | ||
<CheckCircle className="h-12 w-12 text-green-500" /> | ||
) : ( | ||
<XCircle className="h-12 w-12 text-red-500" /> | ||
)} | ||
</div> | ||
<CardTitle className="text-2xl"> | ||
{success ? 'Order Confirmed' : 'Payment Failed'} | ||
</CardTitle> | ||
{orderId && success && ( | ||
<p className="text-sm text-muted-foreground mt-2"> | ||
Order #{orderId} | ||
</p> | ||
)} | ||
</CardHeader> | ||
<CardContent className="space-y-4"> | ||
<p className="text-muted-foreground"> | ||
{success | ||
? 'Thank you for your order. You will receive a confirmation email shortly.' | ||
: 'Sorry, your payment was not successful. Please try again.'} | ||
</p> | ||
<div className="flex flex-col gap-2"> | ||
{!success && ( | ||
<Button | ||
variant="default" | ||
onClick={() => router.push('/checkout')} | ||
> | ||
Try Again | ||
</Button> | ||
)} | ||
<Button | ||
variant={success ? "default" : "secondary"} | ||
onClick={() => router.push('/')} | ||
> | ||
Continue Shopping | ||
</Button> | ||
</div> | ||
</CardContent> | ||
</Card> | ||
</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,5 @@ | ||
import { ConfirmationPage } from "../_components/ConfirmationPage"; | ||
|
||
export default function Page() { | ||
return <ConfirmationPage />; | ||
} |
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,5 @@ | ||
import { CheckoutPage } from "./_components/CheckoutPage"; | ||
|
||
export default function Page() { | ||
return <CheckoutPage />; | ||
} |
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,31 @@ | ||
"use client" | ||
|
||
import * as React from "react" | ||
import * as SeparatorPrimitive from "@radix-ui/react-separator" | ||
|
||
import { cn } from "@/lib/utils" | ||
|
||
const Separator = React.forwardRef< | ||
React.ElementRef<typeof SeparatorPrimitive.Root>, | ||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root> | ||
>( | ||
( | ||
{ className, orientation = "horizontal", decorative = true, ...props }, | ||
ref | ||
) => ( | ||
<SeparatorPrimitive.Root | ||
ref={ref} | ||
decorative={decorative} | ||
orientation={orientation} | ||
className={cn( | ||
"shrink-0 bg-border", | ||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", | ||
className | ||
)} | ||
{...props} | ||
/> | ||
) | ||
) | ||
Separator.displayName = SeparatorPrimitive.Root.displayName | ||
|
||
export { Separator } |
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
Oops, something went wrong.