Merge branch 'main' into MED-63

This commit is contained in:
Danel Kungla
2025-07-08 16:06:27 +03:00
237 changed files with 40809 additions and 2141 deletions

View File

@@ -0,0 +1,30 @@
import { retrieveCart } from "@lib/data/cart"
import { retrieveCustomer } from "@lib/data/customer"
import PaymentWrapper from "@modules/checkout/components/payment-wrapper"
import CheckoutForm from "@modules/checkout/templates/checkout-form"
import CheckoutSummary from "@modules/checkout/templates/checkout-summary"
import { Metadata } from "next"
import { notFound } from "next/navigation"
export const metadata: Metadata = {
title: "Checkout",
}
export default async function Checkout() {
const cart = await retrieveCart()
if (!cart) {
return notFound()
}
const customer = await retrieveCustomer()
return (
<div className="grid grid-cols-1 small:grid-cols-[1fr_416px] content-container gap-x-40 py-12">
<PaymentWrapper cart={cart}>
<CheckoutForm cart={cart} customer={customer} />
</PaymentWrapper>
<CheckoutSummary cart={cart} />
</div>
)
}

View File

@@ -0,0 +1,45 @@
import { LocalizedClientLink } from '~/medusa/modules/common/components';
import { ChevronDownIcon } from '~/medusa/modules/common/icons';
import { MedusaCTA } from '~/medusa/modules/layout/components';
export default function CheckoutLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="small:min-h-screen relative w-full bg-white">
<div className="h-16 border-b bg-white">
<nav className="content-container flex h-full items-center justify-between">
<LocalizedClientLink
href="/cart"
className="text-small-semi text-ui-fg-base flex flex-1 basis-0 items-center gap-x-2 uppercase"
data-testid="back-to-cart-link"
>
<ChevronDownIcon className="rotate-90" size={16} />
<span className="small:block txt-compact-plus text-ui-fg-subtle hover:text-ui-fg-base mt-px hidden">
Back to shopping cart
</span>
<span className="small:hidden txt-compact-plus text-ui-fg-subtle hover:text-ui-fg-base mt-px block">
Back
</span>
</LocalizedClientLink>
<LocalizedClientLink
href="/"
className="txt-compact-xlarge-plus text-ui-fg-subtle hover:text-ui-fg-base uppercase"
data-testid="store-link"
>
Medusa Store
</LocalizedClientLink>
<div className="flex-1 basis-0" />
</nav>
</div>
<div className="relative" data-testid="checkout-container">
{children}
</div>
<div className="flex w-full items-center justify-center py-4">
<MedusaCTA />
</div>
</div>
);
}

View File

@@ -0,0 +1,20 @@
import { Metadata } from 'next';
import InteractiveLink from '~/medusa/modules/common/components/interactive-link';
export const metadata: Metadata = {
title: '404',
description: 'Something went wrong',
};
export default async function NotFound() {
return (
<div className="flex min-h-[calc(100vh-64px)] flex-col items-center justify-center gap-4">
<h1 className="text-2xl-semi text-ui-fg-base">Page not found</h1>
<p className="text-small-regular text-ui-fg-base">
The page you tried to access does not exist.
</p>
<InteractiveLink href="/">Go to frontpage</InteractiveLink>
</div>
);
}

View File

@@ -0,0 +1,38 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { retrieveCustomer } from '~/medusa/lib/data/customer';
import { getRegion } from '~/medusa/lib/data/regions';
import AddressBook from '~/medusa/modules/account/components/address-book';
export const metadata: Metadata = {
title: 'Addresses',
description: 'View your addresses',
};
export default async function Addresses(props: {
params: Promise<{ countryCode: string }>;
}) {
const params = await props.params;
const { countryCode } = params;
const customer = await retrieveCustomer();
const region = await getRegion(countryCode);
if (!customer || !region) {
notFound();
}
return (
<div className="w-full" data-testid="addresses-page-wrapper">
<div className="mb-8 flex flex-col gap-y-4">
<h1 className="text-2xl-semi">Shipping Addresses</h1>
<p className="text-base-regular">
View and update your shipping addresses, you can add as many as you
like. Saving your addresses will make them available during checkout.
</p>
</div>
<AddressBook customer={customer} region={region} />
</div>
);
}

View File

@@ -0,0 +1,9 @@
import Spinner from '~/medusa/modules/common/icons/spinner';
export default function Loading() {
return (
<div className="text-ui-fg-base flex h-full w-full items-center justify-center">
<Spinner size={36} />
</div>
);
}

View File

@@ -0,0 +1,35 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { retrieveOrder } from '~/medusa/lib/data/orders';
import OrderDetailsTemplate from '~/medusa/modules/order/templates/order-details-template';
type Props = {
params: Promise<{ id: string }>;
};
export async function generateMetadata(props: Props): Promise<Metadata> {
const params = await props.params;
const order = await retrieveOrder(params.id).catch(() => null);
if (!order) {
notFound();
}
return {
title: `Order #${order.display_id}`,
description: `View your order`,
};
}
export default async function OrderDetailPage(props: Props) {
const params = await props.params;
const order = await retrieveOrder(params.id).catch(() => null);
if (!order) {
notFound();
}
return <OrderDetailsTemplate order={order} />;
}

View File

@@ -0,0 +1,38 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { listOrders } from '~/medusa/lib/data/orders';
import OrderOverview from '~/medusa/modules/account/components/order-overview';
import TransferRequestForm from '~/medusa/modules/account/components/transfer-request-form';
import Divider from '~/medusa/modules/common/components/divider';
export const metadata: Metadata = {
title: 'Orders',
description: 'Overview of your previous orders.',
};
export default async function Orders() {
const orders = await listOrders();
if (!orders) {
notFound();
}
return (
<div className="w-full" data-testid="orders-page-wrapper">
<div className="mb-8 flex flex-col gap-y-4">
<h1 className="text-2xl-semi">Orders</h1>
<p className="text-base-regular">
View your previous orders and their status. You can also create
returns or exchanges for your orders if needed.
</p>
</div>
<div>
<OrderOverview orders={orders} />
<Divider className="my-16" />
<TransferRequestForm />
</div>
</div>
);
}

View File

@@ -0,0 +1,23 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { retrieveCustomer } from '~/medusa/lib/data/customer';
import { listOrders } from '~/medusa/lib/data/orders';
import Overview from '~/medusa/modules/account/components/overview';
export const metadata: Metadata = {
title: 'Account',
description: 'Overview of your account activity.',
};
export default async function OverviewTemplate() {
const customer = await retrieveCustomer().catch(() => null);
const orders = (await listOrders().catch(() => null)) || null;
if (!customer) {
notFound();
}
return <Overview customer={customer} orders={orders} />;
}

View File

@@ -0,0 +1,54 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { retrieveCustomer } from '~/medusa/lib/data/customer';
import { listRegions } from '~/medusa/lib/data/regions';
import ProfilePhone from '~/medusa/modules/account//components/profile-phone';
import ProfileBillingAddress from '~/medusa/modules/account/components/profile-billing-address';
import ProfileEmail from '~/medusa/modules/account/components/profile-email';
import ProfileName from '~/medusa/modules/account/components/profile-name';
import ProfilePassword from '~/medusa/modules/account/components/profile-password';
export const metadata: Metadata = {
title: 'Profile',
description: 'View and edit your Medusa Store profile.',
};
export default async function Profile() {
const customer = await retrieveCustomer();
const regions = await listRegions();
if (!customer || !regions) {
notFound();
}
return (
<div className="w-full" data-testid="profile-page-wrapper">
<div className="mb-8 flex flex-col gap-y-4">
<h1 className="text-2xl-semi">Profile</h1>
<p className="text-base-regular">
View and update your profile information, including your name, email,
and phone number. You can also update your billing address, or change
your password.
</p>
</div>
<div className="flex w-full flex-col gap-y-8">
<ProfileName customer={customer} />
<Divider />
<ProfileEmail customer={customer} />
<Divider />
<ProfilePhone customer={customer} />
<Divider />
{/* <ProfilePassword customer={customer} />
<Divider /> */}
<ProfileBillingAddress customer={customer} regions={regions} />
</div>
</div>
);
}
const Divider = () => {
return <div className="h-px w-full bg-gray-200" />;
};
``;

View File

@@ -0,0 +1,12 @@
import { Metadata } from 'next';
import LoginTemplate from '~/medusa/modules/account/templates/login-template';
export const metadata: Metadata = {
title: 'Sign in',
description: 'Sign in to your Medusa Store account.',
};
export default function Login() {
return <LoginTemplate />;
}

View File

@@ -0,0 +1,21 @@
import { Toaster } from '@medusajs/ui';
import { retrieveCustomer } from '~/medusa/lib/data';
import { AccountLayout } from '~/medusa/modules/account/templates';
export default async function AccountPageLayout({
dashboard,
login,
}: {
dashboard?: React.ReactNode;
login?: React.ReactNode;
}) {
const customer = await retrieveCustomer().catch(() => null);
return (
<AccountLayout customer={customer}>
{customer ? dashboard : login}
<Toaster />
</AccountLayout>
);
}

View File

@@ -0,0 +1,9 @@
import Spinner from '~/medusa/modules/common/icons/spinner';
export default function Loading() {
return (
<div className="text-ui-fg-base flex h-full w-full items-center justify-center">
<Spinner size={36} />
</div>
);
}

View File

@@ -0,0 +1,5 @@
import SkeletonCartPage from '~/medusa/modules/skeletons/templates/skeleton-cart-page';
export default function Loading() {
return <SkeletonCartPage />;
}

View File

@@ -0,0 +1,21 @@
import { Metadata } from 'next';
import InteractiveLink from '~/medusa/modules/common/components/interactive-link';
export const metadata: Metadata = {
title: '404',
description: 'Something went wrong',
};
export default function NotFound() {
return (
<div className="flex min-h-[calc(100vh-64px)] flex-col items-center justify-center">
<h1 className="text-2xl-semi text-ui-fg-base">Page not found</h1>
<p className="text-small-regular text-ui-fg-base">
The cart you tried to access does not exist. Clear your cookies and try
again.
</p>
<InteractiveLink href="/">Go to frontpage</InteractiveLink>
</div>
);
}

View File

@@ -0,0 +1,23 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { retrieveCart } from '~/medusa/lib/data/cart';
import { retrieveCustomer } from '~/medusa/lib/data/customer';
import CartTemplate from '~/medusa/modules/cart/templates';
export const metadata: Metadata = {
title: 'Cart',
description: 'View your cart',
};
export default async function Cart() {
const cart = await retrieveCart().catch((error) => {
console.error(error);
return notFound();
});
const customer = await retrieveCustomer();
return <CartTemplate cart={cart} customer={customer} />;
}

View File

@@ -0,0 +1,90 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { StoreRegion } from '@medusajs/types';
import {
getCategoryByHandle,
listCategories,
} from '~/medusa/lib/data/categories';
import { listRegions } from '~/medusa/lib/data/regions';
import CategoryTemplate from '~/medusa/modules/categories/templates';
import { SortOptions } from '~/medusa/modules/store/components/refinement-list/sort-products';
type Props = {
params: Promise<{ category: string[]; countryCode: string }>;
searchParams: Promise<{
sortBy?: SortOptions;
page?: string;
}>;
};
export async function generateStaticParams() {
const product_categories = await listCategories();
if (!product_categories) {
return [];
}
const countryCodes = await listRegions().then((regions: StoreRegion[]) =>
regions?.map((r) => r.countries?.map((c) => c.iso_2)).flat(),
);
const categoryHandles = product_categories.map(
(category: any) => category.handle,
);
const staticParams = countryCodes
?.map((countryCode: string | undefined) =>
categoryHandles.map((handle: any) => ({
countryCode,
category: [handle],
})),
)
.flat();
return staticParams;
}
export async function generateMetadata(props: Props): Promise<Metadata> {
const params = await props.params;
try {
const productCategory = await getCategoryByHandle(params.category);
const title = productCategory.name + ' | Medusa Store';
const description = productCategory.description ?? `${title} category.`;
return {
title: `${title} | Medusa Store`,
description,
alternates: {
canonical: `${params.category.join('/')}`,
},
};
} catch (error) {
notFound();
}
}
export default async function CategoryPage(props: Props) {
const searchParams = await props.searchParams;
const params = await props.params;
const { sortBy, page } = searchParams;
const productCategory = await getCategoryByHandle(params.category);
if (!productCategory) {
notFound();
}
return (
<CategoryTemplate
category={productCategory}
sortBy={sortBy}
page={page}
countryCode={params.countryCode}
/>
);
}

View File

@@ -0,0 +1,95 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { StoreCollection, StoreRegion } from '@medusajs/types';
import {
getCollectionByHandle,
listCollections,
} from '~/medusa/lib/data/collections';
import { listRegions } from '~/medusa/lib/data/regions';
import CollectionTemplate from '~/medusa/modules/collections/templates';
import { SortOptions } from '~/medusa/modules/store/components/refinement-list/sort-products';
type Props = {
params: Promise<{ handle: string; countryCode: string }>;
searchParams: Promise<{
page?: string;
sortBy?: SortOptions;
}>;
};
export const PRODUCT_LIMIT = 12;
export async function generateStaticParams() {
const { collections } = await listCollections({
fields: '*products',
});
if (!collections) {
return [];
}
const countryCodes = await listRegions().then(
(regions: StoreRegion[]) =>
regions
?.map((r) => r.countries?.map((c) => c.iso_2))
.flat()
.filter(Boolean) as string[],
);
const collectionHandles = collections.map(
(collection: StoreCollection) => collection.handle,
);
const staticParams = countryCodes
?.map((countryCode: string) =>
collectionHandles.map((handle: string | undefined) => ({
countryCode,
handle,
})),
)
.flat();
return staticParams;
}
export async function generateMetadata(props: Props): Promise<Metadata> {
const params = await props.params;
const collection = await getCollectionByHandle(params.handle);
if (!collection) {
notFound();
}
const metadata = {
title: `${collection.title} | Medusa Store`,
description: `${collection.title} collection`,
} as Metadata;
return metadata;
}
export default async function CollectionPage(props: Props) {
const searchParams = await props.searchParams;
const params = await props.params;
const { sortBy, page } = searchParams;
const collection = await getCollectionByHandle(params.handle).then(
(collection: StoreCollection) => collection,
);
if (!collection) {
notFound();
}
return (
<CollectionTemplate
collection={collection}
page={page}
sortBy={sortBy}
countryCode={params.countryCode}
/>
);
}

View File

@@ -0,0 +1,46 @@
import { Metadata } from 'next';
import { StoreCartShippingOption } from '@medusajs/types';
import { listCartOptions, retrieveCart } from '~/medusa/lib/data/cart';
import { retrieveCustomer } from '~/medusa/lib/data/customer';
import { getBaseURL } from '~/medusa/lib/util/env';
import CartMismatchBanner from '~/medusa/modules/layout/components/cart-mismatch-banner';
import Footer from '~/medusa/modules/layout/templates/footer';
import Nav from '~/medusa/modules/layout/templates/nav';
import FreeShippingPriceNudge from '~/medusa/modules/shipping/components/free-shipping-price-nudge';
export const metadata: Metadata = {
metadataBase: new URL(getBaseURL()),
};
export default async function PageLayout(props: { children: React.ReactNode }) {
const customer = await retrieveCustomer();
const cart = await retrieveCart();
let shippingOptions: StoreCartShippingOption[] = [];
if (cart) {
const { shipping_options } = await listCartOptions();
shippingOptions = shipping_options;
}
return (
<>
<Nav />
{customer && cart && (
<CartMismatchBanner customer={customer} cart={cart} />
)}
{cart && (
<FreeShippingPriceNudge
variant="popup"
cart={cart}
shippingOptions={shippingOptions}
/>
)}
{props.children}
<Footer />
</>
);
}

View File

@@ -0,0 +1,20 @@
import { Metadata } from 'next';
import InteractiveLink from '~/medusa/modules/common/components/interactive-link';
export const metadata: Metadata = {
title: '404',
description: 'Something went wrong',
};
export default function NotFound() {
return (
<div className="flex min-h-[calc(100vh-64px)] flex-col items-center justify-center gap-4">
<h1 className="text-2xl-semi text-ui-fg-base">Page not found</h1>
<p className="text-small-regular text-ui-fg-base">
The page you tried to access does not exist.
</p>
<InteractiveLink href="/">Go to frontpage</InteractiveLink>
</div>
);
}

View File

@@ -0,0 +1,5 @@
import SkeletonOrderConfirmed from '~/medusa/modules/skeletons/templates/skeleton-order-confirmed';
export default function Loading() {
return <SkeletonOrderConfirmed />;
}

View File

@@ -0,0 +1,25 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { retrieveOrder } from '~/medusa/lib/data/orders';
import OrderCompletedTemplate from '~/medusa/modules/order/templates/order-completed-template';
type Props = {
params: Promise<{ id: string }>;
};
export const metadata: Metadata = {
title: 'Order Confirmed',
description: 'You purchase was successful',
};
export default async function OrderConfirmedPage(props: Props) {
const params = await props.params;
const order = await retrieveOrder(params.id).catch(() => null);
if (!order) {
return notFound();
}
return <OrderCompletedTemplate order={order} />;
}

View File

@@ -0,0 +1,42 @@
import { Heading, Text } from '@medusajs/ui';
import { acceptTransferRequest } from '~/medusa/lib/data/orders';
import TransferImage from '~/medusa/modules/order/components/transfer-image';
export default async function TransferPage({
params,
}: {
params: { id: string; token: string };
}) {
const { id, token } = params;
const { success, error } = await acceptTransferRequest(id, token);
return (
<div className="mx-auto mt-10 mb-20 flex w-2/5 flex-col items-start gap-y-4">
<TransferImage />
<div className="flex flex-col gap-y-6">
{success && (
<>
<Heading level="h1" className="text-xl text-zinc-900">
Order transfered!
</Heading>
<Text className="text-zinc-600">
Order {id} has been successfully transfered to the new owner.
</Text>
</>
)}
{!success && (
<>
<Text className="text-zinc-600">
There was an error accepting the transfer. Please try again.
</Text>
{error && (
<Text className="text-red-500">Error message: {error}</Text>
)}
</>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,42 @@
import { Heading, Text } from '@medusajs/ui';
import { declineTransferRequest } from '~/medusa/lib/data/orders';
import TransferImage from '~/medusa/modules/order/components/transfer-image';
export default async function TransferPage({
params,
}: {
params: { id: string; token: string };
}) {
const { id, token } = params;
const { success, error } = await declineTransferRequest(id, token);
return (
<div className="mx-auto mt-10 mb-20 flex w-2/5 flex-col items-start gap-y-4">
<TransferImage />
<div className="flex flex-col gap-y-6">
{success && (
<>
<Heading level="h1" className="text-xl text-zinc-900">
Order transfer declined!
</Heading>
<Text className="text-zinc-600">
Transfer of order {id} has been successfully declined.
</Text>
</>
)}
{!success && (
<>
<Text className="text-zinc-600">
There was an error declining the transfer. Please try again.
</Text>
{error && (
<Text className="text-red-500">Error message: {error}</Text>
)}
</>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,39 @@
import { Heading, Text } from '@medusajs/ui';
import TransferActions from '~/medusa/modules/order/components/transfer-actions';
import TransferImage from '~/medusa/modules/order/components/transfer-image';
export default async function TransferPage({
params,
}: {
params: { id: string; token: string };
}) {
const { id, token } = params;
return (
<div className="mx-auto mt-10 mb-20 flex w-2/5 flex-col items-start gap-y-4">
<TransferImage />
<div className="flex flex-col gap-y-6">
<Heading level="h1" className="text-xl text-zinc-900">
Transfer request for order {id}
</Heading>
<Text className="text-zinc-600">
You&#39;ve received a request to transfer ownership of your order (
{id}). If you agree to this request, you can approve the transfer by
clicking the button below.
</Text>
<div className="h-px w-full bg-zinc-200" />
<Text className="text-zinc-600">
If you accept, the new owner will take over all responsibilities and
permissions associated with this order.
</Text>
<Text className="text-zinc-600">
If you do not recognize this request or wish to retain ownership, no
further action is required.
</Text>
<div className="h-px w-full bg-zinc-200" />
<TransferActions id={id} token={token} />
</div>
</div>
);
}

View File

@@ -0,0 +1,40 @@
import { Metadata } from 'next';
import { getRegion, listCollections } from '~/medusa/lib/data';
import FeaturedProducts from '~/medusa/modules/home/components/featured-products';
import Hero from '~/medusa/modules/home/components/hero';
export const metadata: Metadata = {
title: 'Medusa Next.js Starter Template',
description:
'A performant frontend ecommerce starter template with Next.js 15 and Medusa.',
};
export default async function Home(props: {
params: Promise<{ countryCode: string }>;
}) {
const params = await props.params;
const { countryCode } = params;
const region = await getRegion(countryCode);
const { collections } = await listCollections({
fields: 'id, handle, title',
});
if (!collections || !region) {
return null;
}
return (
<>
<Hero />
<div className="py-12">
<ul className="flex flex-col gap-x-6">
<FeaturedProducts collections={collections} region={region} />
</ul>
</div>
</>
);
}

View File

@@ -0,0 +1,108 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { listProducts } from '~/medusa/lib/data/products';
import { getRegion, listRegions } from '~/medusa/lib/data/regions';
import ProductTemplate from '~/medusa/modules/products/templates';
type Props = {
params: Promise<{ countryCode: string; handle: string }>;
};
export async function generateStaticParams() {
try {
const countryCodes = await listRegions().then((regions) =>
regions?.map((r) => r.countries?.map((c) => c.iso_2)).flat(),
);
if (!countryCodes) {
return [];
}
const promises = countryCodes.map(async (country) => {
const { response } = await listProducts({
countryCode: country,
queryParams: { limit: 100, fields: 'handle' },
});
return {
country,
products: response.products,
};
});
const countryProducts = await Promise.all(promises);
return countryProducts
.flatMap((countryData) =>
countryData.products.map((product) => ({
countryCode: countryData.country,
handle: product.handle,
})),
)
.filter((param) => param.handle);
} catch (error) {
console.error(
`Failed to generate static paths for product pages: ${
error instanceof Error ? error.message : 'Unknown error'
}.`,
);
return [];
}
}
export async function generateMetadata(props: Props): Promise<Metadata> {
const params = await props.params;
const { handle } = params;
const region = await getRegion(params.countryCode);
if (!region) {
notFound();
}
const product = await listProducts({
countryCode: params.countryCode,
queryParams: { handle },
}).then(({ response }) => response.products[0]);
if (!product) {
notFound();
}
return {
title: `${product.title} | Medusa Store`,
description: `${product.title}`,
openGraph: {
title: `${product.title} | Medusa Store`,
description: `${product.title}`,
images: product.thumbnail ? [product.thumbnail] : [],
},
};
}
export default async function ProductPage(props: Props) {
const params = await props.params;
const region = await getRegion(params.countryCode);
if (!region) {
notFound();
}
const pricedProduct = await listProducts({
countryCode: params.countryCode,
queryParams: { handle: params.handle },
}).then(({ response }) => response.products[0]);
if (!pricedProduct) {
notFound();
}
return (
<ProductTemplate
product={pricedProduct}
region={region}
countryCode={params.countryCode}
/>
);
}

View File

@@ -0,0 +1,33 @@
import { Metadata } from 'next';
import { SortOptions } from '~/medusa/modules/store/components/refinement-list/sort-products';
import StoreTemplate from '~/medusa/modules/store/templates';
export const metadata: Metadata = {
title: 'Store',
description: 'Explore all of our products.',
};
type Params = {
searchParams: Promise<{
sortBy?: SortOptions;
page?: string;
}>;
params: Promise<{
countryCode: string;
}>;
};
export default async function StorePage(props: Params) {
const params = await props.params;
const searchParams = await props.searchParams;
const { sortBy, page } = searchParams;
return (
<StoreTemplate
sortBy={sortBy}
page={page}
countryCode={params.countryCode}
/>
);
}

29
app/store/not-found.tsx Normal file
View File

@@ -0,0 +1,29 @@
import { Metadata } from 'next';
import Link from 'next/link';
import { ArrowUpRightMini } from '@medusajs/icons';
import { Text } from '@medusajs/ui';
export const metadata: Metadata = {
title: '404',
description: 'Something went wrong',
};
export default function NotFound() {
return (
<div className="flex min-h-[calc(100vh-64px)] flex-col items-center justify-center gap-4">
<h1 className="text-2xl-semi text-ui-fg-base">Page not found</h1>
<p className="text-small-regular text-ui-fg-base">
The page you tried to access does not exist.
</p>
<Link className="group flex items-center gap-x-1" href="/">
<Text className="text-ui-fg-interactive">Go to frontpage</Text>
<ArrowUpRightMini
className="duration-150 ease-in-out group-hover:rotate-45"
color="var(--fg-interactive)"
/>
</Link>
</div>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

BIN
app/store/twitter-image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB