Skip to content

feat: Multi step checkout #1

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
13 changes: 13 additions & 0 deletions app/(checkout)/checkout/[step]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { checkoutSteps } from '@/lib/vendure/checkout';
import {redirect} from "next/navigation";

export default async function CheckoutStep(props: { params: { step: string } }) {
const params = await props.params
const step = checkoutSteps.find((step) => step.identifier == params.step);

if (!step){
return redirect('/')
}

return step?.component;
}
14 changes: 14 additions & 0 deletions app/(checkout)/checkout/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { PropsWithChildren } from 'react';
import { getActiveOrder } from '@/lib/vendure';
import { CheckoutSteps } from '@/components/checkout/step';

export default async function CheckoutLayout({ children }: PropsWithChildren) {
const activeOrder = await getActiveOrder();

return (
<div className="mx-auto max-w-screen-lg">
<CheckoutSteps />
<div className="rounded-md border border-neutral-200 bg-white px-6 py-4">{children}</div>
</div>
);
}
31 changes: 31 additions & 0 deletions app/(checkout)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { PropsWithChildren } from 'react';
import Link from 'next/link';
import LogoSquare from '@/components/logo-square';

const { SITE_NAME } = process.env;

export default async function CheckoutLayout({ children }: PropsWithChildren) {
return (
<div>
<header className="relative flex items-center justify-between p-4 lg:px-6">
<Link
href="/"
prefetch={true}
className="mr-2 flex w-full items-center justify-center md:w-auto lg:mr-6"
>
<LogoSquare />
<div className="ml-2 flex-none text-sm font-medium uppercase md:hidden lg:block">
{SITE_NAME}
</div>
</Link>
<div>
Support Actions
</div>
</header>
<div className="mt-20 grid grid-cols-[1fr_400px]">
<div>{children}</div>
<div>cart</div>
</div>
</div>
);
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 12 additions & 0 deletions app/(default)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Navbar } from 'components/layout/navbar';
import { ReactNode } from 'react';
import './globals.css';

export default async function RootLayout({ children }: { children: ReactNode }) {
return (
<>
<Navbar />
{children}
</>
);
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 0 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { CartProvider } from 'components/cart/cart-context';
import { Navbar } from 'components/layout/navbar';
import { WelcomeToast } from 'components/welcome-toast';
import { GeistSans } from 'geist/font/sans';
import { getActiveChannel, getActiveOrder } from 'lib/vendure';
Expand Down Expand Up @@ -50,7 +49,6 @@ export default async function RootLayout({ children }: { children: ReactNode })
<body className="bg-neutral-50 text-black selection:bg-teal-300 dark:bg-neutral-900 dark:text-white dark:selection:bg-pink-500 dark:selection:text-white">
<ChannelProvider channelPromise={activeChannel}>
<CartProvider activeOrderPromise={activeOrder}>
<Navbar />
<main>
{children}
<Toaster />
Expand Down
3 changes: 3 additions & 0 deletions components/checkout/addresses.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Addresses() {
return <h1>addresses</h1>;
}
39 changes: 39 additions & 0 deletions components/checkout/step.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client';

import { getCheckoutSteps } from '@/lib/vendure/checkout';
import Link from 'next/link';
import { cn } from '@/ui-components/lib/utils';
import { usePathname } from 'next/navigation';
import { FaRegCircle, FaRegCircleCheck, FaRegCircleDot } from 'react-icons/fa6';

export function CheckoutSteps() {
const pathname = usePathname();
const checkoutSteps = getCheckoutSteps(pathname.split('/').pop());

return (
<div className="relative">
<div className="absolute left-0 top-1/2 h-1 w-full -translate-y-1/2 bg-blue-300"></div>
<div className="flex items-center justify-between">
{checkoutSteps.map((step) => {
const stepPathname = `/checkout/${step.identifier}`;
return (
<Link
key={`step-${step.identifier}`}
href={stepPathname}
className={cn({
'z-10 flex items-center bg-neutral-50 p-4 first:ps-0 last:pe-0': true,
'text-neutral-500': !step.active,
'text-blue-600': step.done
})}
>
{step.done && <FaRegCircleCheck />}
{step.active && <FaRegCircleDot />}
{!step.active && !step.done && <FaRegCircle />}
<span className="ml-1">{step.title}</span>
</Link>
);
})}
</div>
</div>
);
}
4 changes: 4 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import {Active_OrderFragment} from "@/lib/vendure/types";

export const AUTH_COOKIE_KEY = 'vendure-auth-token'



export type SortFilterItem = {
name: string;
slug: string | null;
Expand Down
60 changes: 60 additions & 0 deletions lib/vendure/checkout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Active_OrderFragment, Order } from '@/lib/vendure/types';
import React from 'react';
import { Addresses } from '@/components/checkout/addresses';

export type CheckoutStep = {
title?: string;
identifier: string;
validate: (order: Order) => boolean;
commit?: any;
component?: React.ReactNode;
};

export const checkoutSteps: Array<CheckoutStep> = [
{
title: 'Addresses',
identifier: 'addresses',
validate: (order) => {
return !!order.billingAddress && !!order.shippingAddress;
},
component: <Addresses />
},
{
title: 'Shipping',
identifier: 'shipping',
validate: (order) => {
// TODO: where to store shipping method?
return false;
}
},
{
title: 'Payment',
identifier: 'payment',
validate: (order) => {
// TODO: where to store payment method?
return false;
}
},
{
title: 'Summary',
identifier: 'summary',
validate: (order) => {
// TODO: check everything again
return false;
}
}
];

export function getCheckoutSteps(
currentStep: string | undefined
): Array<CheckoutStep & { active: boolean; done: boolean }> {
return checkoutSteps.map((step) => {
return {
...step,
active: step.identifier === currentStep,
done:
checkoutSteps.findIndex((s) => s.identifier === currentStep) >
checkoutSteps.findIndex((s) => s.identifier === step.identifier)
};
});
}
5 changes: 3 additions & 2 deletions ui-components/multi-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ export const MultiSelect = React.forwardRef<
{...props}
onClick={handleTogglePopover}
className={cn(
"flex w-full p-1 rounded-md border min-h-10 h-auto items-center justify-between bg-inherit hover:bg-inherit [&_svg]:pointer-events-auto",
"flex w-full p-1 rounded-md border min-h-10 h-auto items-center font-normal justify-between bg-inherit hover:bg-inherit [&_svg]:pointer-events-auto",
className
)}
>
Expand All @@ -217,7 +217,8 @@ export const MultiSelect = React.forwardRef<
key={value}
className={cn(
isAnimating ? "animate-bounce" : "",
multiSelectVariants({ variant })
multiSelectVariants({ variant }),
'shadow-none'
)}
style={{ animationDuration: `${animation}s` }}
>
Expand Down
2 changes: 1 addition & 1 deletion ui-components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const buttonVariants = cva(
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
"bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
Expand Down