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
5 changes: 5 additions & 0 deletions .changeset/legal-beers-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@godaddy/react": patch
---

Add ui extension support for targets
3 changes: 2 additions & 1 deletion packages/react/biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"includes": [
"**/*",
"!!**/src/globals.css",
"!!**/src/globals-tailwind.css"
"!!**/src/globals-tailwind.css",
"!!**/src/lib/godaddy/*-env.ts"
]
},
"linter": {
Expand Down
8 changes: 8 additions & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
"./package.json": "./package.json",
"./styles.css": "./dist/index.css",
"./styles-tailwind.css": "./dist/index-tailwind.css",
"./ui-extensions": {
"types": "./dist/ui-extensions/index.d.ts",
"import": "./dist/ui-extensions/index.js",
"default": "./dist/ui-extensions/index.js"
},
"./server": {
"types": "./dist/server.d.ts",
"import": "./dist/server.js",
Expand All @@ -28,6 +33,9 @@
"server": [
"./dist/server.d.ts"
],
"ui-extensions": [
"./dist/ui-extensions/index.d.ts"
],
"*": [
"./dist/index.d.ts"
]
Expand Down
7 changes: 4 additions & 3 deletions packages/react/src/components/checkout/checkout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { TrackingProperties } from '@/tracking/event-properties';
import { TrackingProvider } from '@/tracking/tracking-provider';
import { type CheckoutSession, PaymentMethodType } from '@/types';
import { CheckoutFormContainer } from './form/checkout-form-container';
import type { Target } from './target/target';
import type { Target } from './target/types';

// Utility function for redirecting to success URL after checkout
export function redirectToSuccessUrl(successUrl?: string): void {
Expand Down Expand Up @@ -48,7 +48,8 @@ export type LayoutSection =
| 'payment'
| 'pickup'
| 'tips'
| 'delivery';
| 'delivery'
| 'notes';

export const LayoutSections = {
EXPRESS_CHECKOUT: 'express-checkout',
Expand All @@ -58,6 +59,7 @@ export const LayoutSections = {
PICKUP: 'pickup',
DELIVERY: 'delivery',
TIPS: 'tips',
NOTES: 'notes',
} as const;

export type StripeConfig = {
Expand Down Expand Up @@ -253,7 +255,6 @@ export function Checkout(props: CheckoutProps) {
const { t } = useGoDaddyContext();

const { session, jwt, isLoading: isLoadingJWT } = useCheckoutSession(props);

useTheme(session?.appearance?.theme);
useVariables(session?.appearance?.variables || props?.appearance?.variables);

Expand Down
66 changes: 54 additions & 12 deletions packages/react/src/components/checkout/form/checkout-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ export function CheckoutForm({

const isFree = orderTotal <= 0;
const showExpressButtons = subtotal > 0;
const enableDelivery = Boolean(
session?.enableShipping || session?.enableLocalPickup
);
const enableStandaloneNotes = Boolean(
session?.enableNotesCollection && !enableDelivery
);

// Show shipping/taxes/fees lines if collection is enabled OR if there's
// a preset amount on the order. This way merchants who disable collection
Expand Down Expand Up @@ -171,8 +177,8 @@ export function CheckoutForm({
const totalSavings = Math.abs(orderDiscount + lineItemDiscounts);

const [gridTemplateAreas, sectionLength] = React.useMemo(() => {
const { enableTips, paymentMethods, enableShipping, enableLocalPickup } =
session || {};
const { enableTips, paymentMethods } = session || {};

if (!props?.layout) {
const enableExpressCheckout = Object.values(paymentMethods ?? {}).some(
method =>
Expand All @@ -181,18 +187,31 @@ export function CheckoutForm({
method.checkoutTypes.includes(CheckoutType.EXPRESS)
);

const enableDelivery = enableShipping || enableLocalPickup;
const defaultTemplate = ` ${enableExpressCheckout ? "'express-checkout'" : ''} 'contact' ${enableDelivery ? "'delivery'" : ''} '${deliveryMethodToGridArea[deliveryMethod]}' ${enableTips ? "'tips'" : ''} 'payment'`;
// Return consistent tuple type: [string, number]
let totalSections = 2;
enableTips && totalSections++;
enableDelivery && totalSections++;
enableExpressCheckout && totalSections++;
return [defaultTemplate, totalSections];
const deliveryArea = enableDelivery
? deliveryMethodToGridArea[deliveryMethod]
: undefined;
const defaultAreas = [
enableExpressCheckout ? 'express-checkout' : undefined,
'contact',
enableDelivery ? 'delivery' : undefined,
deliveryArea,
enableTips ? 'tips' : undefined,
enableStandaloneNotes ? 'notes' : undefined,
'payment',
].filter(Boolean);
const defaultTemplate = defaultAreas
.map(section => `'${section}'`)
.join(' ');

return [defaultTemplate, defaultAreas.length];
}

// Filter out sections that shouldn't be shown based on delivery method
const filteredLayout = props.layout.filter(section => {
if (section === 'notes') {
return enableStandaloneNotes;
}

if (section !== 'shipping' && section !== 'pickup') {
return true;
}
Expand Down Expand Up @@ -220,14 +239,23 @@ export function CheckoutForm({
if (!enableTips && section === 'tips') {
return false;
}
if (!enableStandaloneNotes && section === 'notes') {
return false;
}
return !filteredLayout.includes(section);
});

// Add missing sections to the end of the layout
const completeLayout = [...filteredLayout, ...missingLayoutSections];

return [`'${completeLayout.join("' '")}'`, completeLayout.length];
}, [props?.layout, deliveryMethod, session]);
}, [
props?.layout,
deliveryMethod,
session,
enableDelivery,
enableStandaloneNotes,
]);

React.useEffect(() => {
if (deliveryMethod) {
Expand Down Expand Up @@ -379,11 +407,25 @@ export function CheckoutForm({
</p>
</div>
)}
{session?.enableNotesCollection ? <NotesForm /> : null}
{session?.enableNotesCollection ? (
<>
<Target id='checkout.form.notes.before' />
<NotesForm />
<Target id='checkout.form.notes.after' />
</>
) : null}
</div>
<Target id='checkout.form.shipping.after' />
</CheckoutSection>
) : null}
{enableStandaloneNotes ? (
<CheckoutSection style={{ gridArea: 'notes' }}>
<Target id='checkout.form.notes.before' />
<CheckoutSectionHeader title={t.general.notes} />
<NotesForm />
<Target id='checkout.form.notes.after' />
</CheckoutSection>
) : null}
<CheckoutSection style={{ gridArea: 'payment' }}>
<Target id='checkout.form.payment.before' />
<CheckoutSectionHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useFormContext } from 'react-hook-form';
import { useCheckoutContext } from '@/components/checkout/checkout';
import { useApplyFulfillmentLocation } from '@/components/checkout/delivery/utils/use-apply-fulfillment-location';
import { NotesForm } from '@/components/checkout/notes/notes-form';
import { Target } from '@/components/checkout/target/target';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import {
Expand Down Expand Up @@ -610,7 +611,13 @@ export function LocalPickupForm({
</div>
)}

{session?.enableNotesCollection ? <NotesForm /> : null}
{session?.enableNotesCollection ? (
<>
<Target id='checkout.form.notes.before' />
<NotesForm />
<Target id='checkout.form.notes.after' />
</>
) : null}

{selectedLocationId && displayHours && showStoreHours && (
<Collapsible
Expand Down
73 changes: 33 additions & 40 deletions packages/react/src/components/checkout/target/target.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,41 @@
'use client';

import { type ReactNode, useMemo } from 'react';
import { useCheckoutContext } from '@/components/checkout/checkout';
import type { Target as CheckoutTarget } from '@/components/checkout/target/types';
import { useGoDaddyContext } from '@/godaddy-provider';
import { cn } from '@/lib/utils';
import type { CheckoutSession } from '@/types';
import { Target as UiExtensionTarget } from '@/ui-extensions/target';
import type { EnabledUiExtensionApp } from '@/ui-extensions/types';
import { groupAppsByUiExtensionTarget } from '@/ui-extensions/utils';

export type Target =
| 'checkout.before'
| 'checkout.after'
| 'checkout.form.before'
| 'checkout.form.after'
| 'checkout.form.contact.before'
| 'checkout.form.contact.after'
| 'checkout.form.express-checkout.before'
| 'checkout.form.express-checkout.after'
| 'checkout.form.pickup.before'
| 'checkout.form.pickup.after'
| 'checkout.form.pickup.form.before'
| 'checkout.form.delivery.before'
| 'checkout.form.delivery.after'
| 'checkout.form.tips.before'
| 'checkout.form.tips.after'
| 'checkout.form.shipping.before'
| 'checkout.form.shipping.after'
| 'checkout.form.payment.before'
| 'checkout.form.payment.after'
| 'checkout.form.submit.before'
| 'checkout.form.submit.after'
| 'checkout.summary.before'
| 'checkout.summary.line-items.before'
| 'checkout.summary.line-items.after'
| 'checkout.summary.totals.subtotal.before'
| 'checkout.summary.totals.discount.before'
| 'checkout.summary.totals.shipping.before'
| 'checkout.summary.totals.tip.before'
| 'checkout.summary.totals.taxes.before'
| 'checkout.summary.totals.fees.before'
| 'checkout.summary.totals.total-due.before'
| 'checkout.summary.totals.total-due.after'
| 'checkout.summary.totals.after'
| 'checkout.summary.after';

export function Target({ id }: { id: Target }) {
export function Target({ id }: { id: CheckoutTarget }) {
const { debug } = useGoDaddyContext();
const { targets, session } = useCheckoutContext();

const target = targets?.[id];
const enabledStoreApplications = (
session as
| (CheckoutSession & {
enabledStoreApplications?: EnabledUiExtensionApp[] | null;
})
| null
| undefined
)?.enabledStoreApplications;
const uiExtensionApps = useMemo(
() => groupAppsByUiExtensionTarget(enabledStoreApplications)[id],
[enabledStoreApplications, id]
);

if (!target && !debug) {
if (!target && !uiExtensionApps?.length && !debug) {
return null;
}

let content: React.ReactNode = null;
let content: ReactNode = null;
if (target) {
content = target(session);
} else if (debug) {
} else if (debug && !uiExtensionApps?.length) {
content = <span className='text-xs text-blue-500'>{id}</span>;
}

Expand All @@ -65,7 +47,18 @@ export function Target({ id }: { id: Target }) {
'm-0'
)}
>
{debug && target ? (
<span className='text-xs text-blue-500'>{id}</span>
) : null}
{content}
{uiExtensionApps?.length ? (
<UiExtensionTarget
apps={uiExtensionApps}
id={id}
orderId={session?.draftOrder?.id ?? undefined}
storeId={session?.storeId}
/>
) : null}
</div>
);
}
40 changes: 40 additions & 0 deletions packages/react/src/components/checkout/target/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export const checkoutTargetIds = [
'checkout.before',
'checkout.after',
'checkout.form.before',
'checkout.form.after',
'checkout.form.contact.before',
'checkout.form.contact.after',
'checkout.form.express-checkout.before',
'checkout.form.express-checkout.after',
'checkout.form.pickup.before',
'checkout.form.pickup.after',
'checkout.form.pickup.form.before',
'checkout.form.delivery.before',
'checkout.form.delivery.after',
'checkout.form.tips.before',
'checkout.form.tips.after',
'checkout.form.notes.before',
'checkout.form.notes.after',
'checkout.form.shipping.before',
'checkout.form.shipping.after',
'checkout.form.payment.before',
'checkout.form.payment.after',
'checkout.form.submit.before',
'checkout.form.submit.after',
'checkout.summary.before',
'checkout.summary.line-items.before',
'checkout.summary.line-items.after',
'checkout.summary.totals.subtotal.before',
'checkout.summary.totals.discount.before',
'checkout.summary.totals.shipping.before',
'checkout.summary.totals.tip.before',
'checkout.summary.totals.taxes.before',
'checkout.summary.totals.fees.before',
'checkout.summary.totals.total-due.before',
'checkout.summary.totals.total-due.after',
'checkout.summary.totals.after',
'checkout.summary.after',
] as const;

export type Target = (typeof checkoutTargetIds)[number];
7 changes: 0 additions & 7 deletions packages/react/src/godaddy-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,6 @@ export interface GoDaddyProviderProps {
localization?: typeof enUs;
appearance?: GoDaddyAppearance;
debug?: boolean;
/**
* API host for checkout GraphQL requests.
* Defaults to production (https://checkout.commerce.api.godaddy.com).
*
* Internal devs can set to:
* - "http://localhost:3000" for local development
*/
apiHost?: string;
clientId?: string;
storeId?: string;
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export {
useFormatCurrency,
} from './lib/format-currency';
export * from './types';
export * from './ui-extensions';
Loading