Skip to content

Commit

Permalink
clients/orders: dedicated order page
Browse files Browse the repository at this point in the history
  • Loading branch information
emilwidlund committed Dec 16, 2024
1 parent 1914d12 commit 9b07290
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 142 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { CustomerModal } from '@/components/Customer/CustomerModal'
import { DashboardBody } from '@/components/Layout/DashboardLayout'
import { InlineModal } from '@/components/Modal/InlineModal'
import { useModal } from '@/components/Modal/useModal'
Expand All @@ -25,7 +26,6 @@ import {
} from 'polarkit/components/ui/atoms/datatable'
import Input from 'polarkit/components/ui/atoms/input'
import React, { useCallback, useEffect, useState } from 'react'
import { CustomerModal } from './CustomerModal'

interface ClientPageProps {
organization: Organization
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { InlineModal } from '@/components/Modal/InlineModal'
import { useModal } from '@/components/Modal/useModal'
import Pagination from '@/components/Pagination/Pagination'
import { ProductCheckoutModal } from '@/components/Products/ProductCheckoutModal'
import { ProductThumbnail } from '@/components/Products/ProductThumbnail'
import ProductPriceLabel from '@/components/Products/ProductPriceLabel'
import ProductPrices from '@/components/Products/ProductPrices'
import { useProducts } from '@/hooks/queries/products'
Expand All @@ -22,7 +23,6 @@ import {
HiveOutlined,
MoreVertOutlined,
Search,
TextureOutlined,
} from '@mui/icons-material'
import {
Organization,
Expand Down Expand Up @@ -167,31 +167,6 @@ export default function ClientPage({
)
}

const ProductListCoverImage = ({ product }: { product: Product }) => {
let coverUrl = null
if (product.medias.length > 0) {
coverUrl = product.medias[0].public_url
}

return (
<div className="dark:bg-polar-800 dark:border-polar-700 flex aspect-square h-10 flex-col items-center justify-center rounded-md border border-transparent bg-gray-100 text-center">
{coverUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={coverUrl}
alt={product.name}
className="aspect-square h-10 rounded-md object-cover"
/>
) : (
<TextureOutlined
fontSize="medium"
className="dark:text-polar-600 text-gray-300"
/>
)}
</div>
)
}

interface ProductListItemProps {
product: Product
organization: Organization
Expand Down Expand Up @@ -252,7 +227,7 @@ const ProductListItem = ({ product, organization }: ProductListItemProps) => {
)}
>
<div className="flex flex-grow flex-row items-center gap-x-4 text-sm">
<ProductListCoverImage product={product} />
<ProductThumbnail product={product} />
<div className="flex flex-col">
<span className="truncate">{product.name}</span>
{product.description && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
'use client'

import CustomFieldValue from '@/components/CustomFields/CustomFieldValue'
import { DashboardBody } from '@/components/Layout/DashboardLayout'
import { InlineModal } from '@/components/Modal/InlineModal'
import { useModal } from '@/components/Modal/useModal'
import ProductSelect from '@/components/Products/ProductSelect'
import { useCustomFields } from '@/hooks/queries'
import { useOrders } from '@/hooks/queries/orders'
import { MaintainerOrganizationContext } from '@/providers/maintainerOrganization'
import {
DataTablePaginationState,
DataTableSortingState,
Expand All @@ -25,7 +20,7 @@ import {
DataTableColumnHeader,
} from 'polarkit/components/ui/atoms/datatable'
import { formatCurrencyAndAmount } from 'polarkit/lib/money'
import React, { useContext, useEffect, useState } from 'react'
import React, { useEffect, useState } from 'react'

interface ClientPageProps {
organization: Organization
Expand All @@ -42,7 +37,6 @@ const ClientPage: React.FC<ClientPageProps> = ({
}) => {
const [selectedOrderState, setSelectedOrderState] =
useState<RowSelectionState>({})
const { hide: hideModal, show: showModal, isShown: isModalShown } = useModal()

const getSearchParams = (
pagination: DataTablePaginationState,
Expand Down Expand Up @@ -183,11 +177,9 @@ const ClientPage: React.FC<ClientPageProps> = ({

useEffect(() => {
if (selectedOrder) {
showModal()
} else {
hideModal()
router.push(`/dashboard/${organization.slug}/sales/${selectedOrder.id}`)
}
}, [selectedOrder, showModal, hideModal])
}, [selectedOrder, router])

return (
<DashboardBody>
Expand Down Expand Up @@ -221,93 +213,8 @@ const ClientPage: React.FC<ClientPageProps> = ({
/>
)}
</div>
<InlineModal
modalContent={<OrderModal order={selectedOrder} />}
isShown={isModalShown}
hide={() => {
setSelectedOrderState({})
hideModal()
}}
/>
</DashboardBody>
)
}

interface OrderModalProps {
order?: Order
}

const OrderModal = ({ order }: OrderModalProps) => {
const { organization } = useContext(MaintainerOrganizationContext)
const { data: customFields } = useCustomFields(organization.id)

if (!order) return null

return (
<div className="flex flex-col gap-8 overflow-y-auto px-8 py-12">
<h2 className="mb-4 text-2xl">Order Details</h2>
<div className="flex flex-row items-center gap-4">
<Avatar
avatar_url={order.customer.avatar_url}
name={order.customer.name || order.customer.email}
className="h-16 w-16"
/>
<div className="flex flex-col gap-1">
<p className="text-xl">{order.customer.email}</p>
<p className="dark:text-polar-500 text-gray-500">
{order.user.email}
</p>
</div>
</div>
<h2 className="text-2xl">{order.product.name}</h2>
<div className="flex flex-col gap-2">
<div className="flex justify-between">
<span className="dark:text-polar-500 text-gray-500">Order Date</span>
<span>
<FormattedDateTime datetime={order.created_at} />
</span>
</div>
<div className="flex justify-between">
<span className="dark:text-polar-500 text-gray-500">Tax</span>
<span>
{formatCurrencyAndAmount(order.tax_amount, order.currency)}
</span>
</div>
<div className="flex justify-between">
<span className="dark:text-polar-500 text-gray-500">Discount</span>
<span>{order.discount ? order.discount.code : '—'}</span>
</div>
<div className="flex justify-between">
<span className="dark:text-polar-500 text-gray-500">Amount</span>
<span>{formatCurrencyAndAmount(order.amount, order.currency)}</span>
</div>
</div>
{(customFields?.items?.length ?? 0) > 0 && (
<div className="flex flex-col gap-4">
<h3 className="text-lg">Custom Fields</h3>
<div className="flex flex-col gap-2">
{customFields?.items?.map((field) => (
<div key={field.slug} className="flex flex-col gap-y-2">
<span>{field.name}</span>
<div className="font-mono text-sm">
<CustomFieldValue
field={field}
value={
order.custom_field_data
? order.custom_field_data[
field.slug as keyof typeof order.custom_field_data
]
: undefined
}
/>
</div>
</div>
))}
</div>
</div>
)}
</div>
)
}

export default ClientPage
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
'use client'

import CustomFieldValue from '@/components/CustomFields/CustomFieldValue'
import { CustomerModal } from '@/components/Customer/CustomerModal'
import { DashboardBody } from '@/components/Layout/DashboardLayout'
import { BenefitList } from '@/components/Products/BenefitList'
import { ProductThumbnail } from '@/components/Products/ProductThumbnail'
import { useCustomFields, useProduct } from '@/hooks/queries'
import { useOrder, useOrders } from '@/hooks/queries/orders'
import { markdownOptionsJustText } from '@/utils/markdown'
import { Organization, OrderProduct, Order, Product} from '@polar-sh/sdk'
import { formatCurrencyAndAmount } from '@polarkit/lib/money'
import { Separator } from '@radix-ui/react-dropdown-menu'
import Markdown from 'markdown-to-jsx'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { FormattedDateTime } from 'polarkit/components/ui/atoms'
import Button from 'polarkit/components/ui/atoms/button'
import ShadowBox from 'polarkit/components/ui/atoms/shadowbox'
import React, { PropsWithChildren, useEffect, useState } from 'react'
import { twMerge } from 'tailwind-merge'

interface OrderProductItemProps {
product: Product
}

const OrderProductItem = ({ product }: OrderProductItemProps) => {


return (
<div className="flex bg-gray-100 dark:bg-polar-800 flex-row items-center gap-6 border border-gray-200 dark:border-polar-700 rounded-3xl p-4">
<ProductThumbnail product={product} size="medium" />
<div className="flex flex-col gap-2">
<h3 className="text-xl">{product.name}</h3>
{product.description && (
<div
className={twMerge(
'prose dark:prose-invert dark:text-polar-500 flex-shrink leading-normal text-gray-500',
'max-w-96 truncate',
)}
>
<Markdown options={markdownOptionsJustText}>
{product.description}
</Markdown>
</div>
)}
</div>
</div>
)
}

interface ClientPageProps {
organization: Organization
orderId: string
}

const ClientPage: React.FC<ClientPageProps> = ({
organization,
orderId
}) => {
const { data: order } = useOrder(orderId)
const { data: product } = useProduct(order?.product.id)
const { data: customFields } = useCustomFields(organization.id)

if (!order || !product) {
return null
}


return (
<DashboardBody
title={
<div className='flex flex-row items-baseline gap-8'>
<h2 className="text-xl font-normal">Order</h2>
<span className='text-gray-500 font-mono text-sm dark:text-polar-500'>{order.id}</span>
</div>
}
className='gap-y-8'
contextView={<CustomerModal customer={order.customer} />}
>
<ShadowBox className="flex flex-col gap-8">
<OrderProductItem product={product} />
<div className="flex flex-row gap-4">
<Link href={`/dashboard/${organization.slug}/products/${product.id}`}>
<Button>View Product</Button>
</Link>
<Link href={`/dashboard/${organization.slug}/sales?product_id=${product.id}`}>
<Button variant="secondary">All Product Orders</Button>
</Link>
</div>
</ShadowBox>
<ShadowBox className="flex flex-col gap-8">
<h2 className="text-xl">Order Details</h2>
<div className="flex flex-col gap-1">
<DetailRow title="Order ID">
<span className='font-mono text-sm'>{order.id}</span>
</DetailRow>
<DetailRow title="Order Date">
<span><FormattedDateTime dateStyle='long' datetime={order.created_at} /></span>
</DetailRow>
<DetailRow title="Billing Reason">
<span className='capitalize'>{order.billing_reason.split('_').join(' ')}</span>
</DetailRow>

<Separator className='h-[1px] my-4 dark:bg-polar-700 bg-gray-200' />

<DetailRow title="Tax">
<span>{formatCurrencyAndAmount(order.tax_amount)}</span>
</DetailRow>
<DetailRow title="Discount">
<span>{order.discount ? order.discount.code : '—'}</span>
</DetailRow>
<DetailRow title="Amount">
<span>{formatCurrencyAndAmount(order.amount)}</span>
</DetailRow>
{order.billing_address ? (
<>
<Separator className='h-[1px] my-4 dark:bg-polar-700 bg-gray-200' />
<DetailRow title="Country">
<span>{order.billing_address?.country}</span>
</DetailRow>
<DetailRow title="Address">
<span>{order.billing_address?.line1 ?? '—'}</span>
</DetailRow>
<DetailRow title="Address 2">
<span>{order.billing_address?.line2 ?? '—'}</span>
</DetailRow>
<DetailRow title="Postal Code">
<span>{order.billing_address?.postal_code ?? '—'}</span>
</DetailRow>
<DetailRow title="City">
<span>{order.billing_address?.city ?? '—'}</span>
</DetailRow>
<DetailRow title="State">
<span>{order.billing_address?.state ?? '—'}</span>
</DetailRow>
</>) : <></>}
</div>
</ShadowBox>

{(customFields?.items?.length ?? 0) > 0 && (
<ShadowBox className="flex flex-col gap-8">
<h3 className="text-lg">Custom Fields</h3>
<div className="flex flex-col gap-2">
{customFields?.items?.map((field) => (
<div key={field.slug} className="flex flex-col gap-y-2">
<span>{field.name}</span>
<div className="font-mono text-sm">
<CustomFieldValue
field={field}
value={
order.custom_field_data
? order.custom_field_data[
field.slug as keyof typeof order.custom_field_data
]
: undefined
}
/>
</div>
</div>
))}
</div>
</ShadowBox>
)}
</DashboardBody>
)
}

const DetailRow = ({ title, children }: PropsWithChildren<{ title: string;}>) => {
return (
<div className="flex flex-row justify-between gap-8">
<span className="dark:text-polar-500 text-gray-500">{title}</span>
{children}
</div>
)
}

export default ClientPage
Loading

0 comments on commit 9b07290

Please sign in to comment.