HEX
Server: LiteSpeed
System: Linux eko108.isimtescil.net 4.18.0-477.21.1.lve.1.el8.x86_64 #1 SMP Tue Sep 5 23:08:35 UTC 2023 x86_64
User: uyarreklamcomtr (11202)
PHP: 7.4.33
Disabled: opcache_get_status
Upload Files
File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/extensions.tar
google-analytics/constants.ts000064400000000171151551514440012375 0ustar00export const namespace = 'woocommerce-google-analytics';
export const actionPrefix = 'experimental__woocommerce_blocks';
google-analytics/index.ts000064400000015627151551514440011504 0ustar00/**
 * External dependencies
 */
import { __ } from '@wordpress/i18n';
import { addAction } from '@wordpress/hooks';
import type {
	ProductResponseItem,
	CartResponseItem,
	StoreCart,
} from '@woocommerce/types';

/**
 * Internal dependencies
 */
import { namespace, actionPrefix } from './constants';
import {
	getProductFieldObject,
	getProductImpressionObject,
	trackEvent,
	trackCheckoutStep,
	trackCheckoutOption,
} from './utils';

/**
 * Track customer progress through steps of the checkout. Triggers the event when the step changes:
 * 	1 - Contact information
 * 	2 - Shipping address
 * 	3 - Billing address
 * 	4 - Shipping options
 * 	5 - Payment options
 *
 * @summary Track checkout progress with begin_checkout and checkout_progress
 * @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#1_measure_checkout_steps
 */
addAction(
	`${ actionPrefix }-checkout-render-checkout-form`,
	namespace,
	trackCheckoutStep( 0 )
);
addAction(
	`${ actionPrefix }-checkout-set-email-address`,
	namespace,
	trackCheckoutStep( 1 )
);
addAction(
	`${ actionPrefix }-checkout-set-shipping-address`,
	namespace,
	trackCheckoutStep( 2 )
);
addAction(
	`${ actionPrefix }-checkout-set-billing-address`,
	namespace,
	trackCheckoutStep( 3 )
);
addAction(
	`${ actionPrefix }-checkout-set-phone-number`,
	namespace,
	( { step, ...rest }: { step: string; storeCart: StoreCart } ): void => {
		trackCheckoutStep( step === 'shipping' ? 2 : 3 )( rest );
	}
);

/**
 * Choose a shipping rate
 *
 * @summary Track the shipping rate being set using set_checkout_option
 * @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#2_measure_checkout_options
 */
addAction(
	`${ actionPrefix }-checkout-set-selected-shipping-rate`,
	namespace,
	( { shippingRateId }: { shippingRateId: string } ): void => {
		trackCheckoutOption( {
			step: 4,
			option: __( 'Shipping Method', 'woo-gutenberg-products-block' ),
			value: shippingRateId,
		} )();
	}
);

/**
 * Choose a payment method
 *
 * @summary Track the payment method being set using set_checkout_option
 * @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#2_measure_checkout_options
 */
addAction(
	`${ actionPrefix }-checkout-set-active-payment-method`,
	namespace,
	( { paymentMethodSlug }: { paymentMethodSlug: string } ): void => {
		trackCheckoutOption( {
			step: 5,
			option: __( 'Payment Method', 'woo-gutenberg-products-block' ),
			value: paymentMethodSlug,
		} )();
	}
);

/**
 * Add Payment Information
 *
 * This event signifies a user has submitted their payment information. Note, this is used to indicate checkout
 * submission, not `purchase` which is triggered on the thanks page.
 *
 * @summary Track the add_payment_info event
 * @see https://developers.google.com/gtagjs/reference/ga4-events#add_payment_info
 */
addAction( `${ actionPrefix }-checkout-submit`, namespace, (): void => {
	trackEvent( 'add_payment_info' );
} );

/**
 * Add to cart.
 *
 * This event signifies that an item was added to a cart for purchase.
 *
 * @summary Track the add_to_cart event
 * @see https://developers.google.com/gtagjs/reference/ga4-events#add_to_cart
 */
addAction(
	`${ actionPrefix }-cart-add-item`,
	namespace,
	( {
		product,
		quantity = 1,
	}: {
		product: ProductResponseItem;
		quantity: number;
	} ): void => {
		trackEvent( 'add_to_cart', {
			event_category: 'ecommerce',
			event_label: __( 'Add to Cart', 'woo-gutenberg-products-block' ),
			items: [ getProductFieldObject( product, quantity ) ],
		} );
	}
);

/**
 * Remove item from the cart
 *
 * @summary Track the remove_from_cart event
 * @see https://developers.google.com/gtagjs/reference/ga4-events#remove_from_cart
 */
addAction(
	`${ actionPrefix }-cart-remove-item`,
	namespace,
	( {
		product,
		quantity = 1,
	}: {
		product: CartResponseItem;
		quantity: number;
	} ): void => {
		trackEvent( 'remove_from_cart', {
			event_category: 'ecommerce',
			event_label: __(
				'Remove Cart Item',
				'woo-gutenberg-products-block'
			),
			items: [ getProductFieldObject( product, quantity ) ],
		} );
	}
);

/**
 * Change cart item quantities
 *
 * @summary Custom change_cart_quantity event.
 */
addAction(
	`${ actionPrefix }-cart-set-item-quantity`,
	namespace,
	( {
		product,
		quantity = 1,
	}: {
		product: CartResponseItem;
		quantity: number;
	} ): void => {
		trackEvent( 'change_cart_quantity', {
			event_category: 'ecommerce',
			event_label: __(
				'Change Cart Item Quantity',
				'woo-gutenberg-products-block'
			),
			items: [ getProductFieldObject( product, quantity ) ],
		} );
	}
);

/**
 * Product List View
 *
 * @summary Track the view_item_list event
 * @see https://developers.google.com/gtagjs/reference/ga4-events#view_item_list
 */
addAction(
	`${ actionPrefix }-product-list-render`,
	namespace,
	( {
		products,
		listName = __( 'Product List', 'woo-gutenberg-products-block' ),
	}: {
		products: Array< ProductResponseItem >;
		listName: string;
	} ): void => {
		if ( products.length === 0 ) {
			return;
		}
		trackEvent( 'view_item_list', {
			event_category: 'engagement',
			event_label: __(
				'Viewing products',
				'woo-gutenberg-products-block'
			),
			items: products.map( ( product, index ) => ( {
				...getProductImpressionObject( product, listName ),
				list_position: index + 1,
			} ) ),
		} );
	}
);

/**
 * Product View Link Clicked
 *
 * @summary Track the select_content event
 * @see https://developers.google.com/gtagjs/reference/ga4-events#select_content
 */
addAction(
	`${ actionPrefix }-product-view-link`,
	namespace,
	( {
		product,
		listName,
	}: {
		product: ProductResponseItem;
		listName: string;
	} ): void => {
		trackEvent( 'select_content', {
			content_type: 'product',
			items: [ getProductImpressionObject( product, listName ) ],
		} );
	}
);

/**
 * Product Search
 *
 * @summary Track the search event
 * @see https://developers.google.com/gtagjs/reference/ga4-events#search
 */
addAction(
	`${ actionPrefix }-product-search`,
	namespace,
	( { searchTerm }: { searchTerm: string } ): void => {
		trackEvent( 'search', {
			search_term: searchTerm,
		} );
	}
);

/**
 * Single Product View
 *
 * @summary Track the view_item event
 * @see https://developers.google.com/gtagjs/reference/ga4-events#view_item
 */
addAction(
	`${ actionPrefix }-product-render`,
	namespace,
	( {
		product,
		listName,
	}: {
		product: ProductResponseItem;
		listName: string;
	} ): void => {
		if ( product ) {
			trackEvent( 'view_item', {
				items: [ getProductImpressionObject( product, listName ) ],
			} );
		}
	}
);

/**
 * Track notices as Exception events.
 *
 * @summary Track the exception event
 * @see https://developers.google.com/analytics/devguides/collection/gtagjs/exceptions
 */
addAction(
	`${ actionPrefix }-store-notice-create`,
	namespace,
	( { status, content }: { status: string; content: string } ): void => {
		if ( status === 'error' ) {
			trackEvent( 'exception', {
				description: content,
				fatal: false,
			} );
		}
	}
);
google-analytics/utils.ts000064400000005572151551514440011533 0ustar00/**
 * External dependencies
 */
import type {
	ProductResponseItem,
	CartResponseItem,
	StoreCart,
} from '@woocommerce/types';

interface ImpressionItem extends Gtag.Item {
	list_name?: string;
}

/**
 * Formats data into the productFieldObject shape.
 *
 * @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#product-data
 */
export const getProductFieldObject = (
	product: ProductResponseItem | CartResponseItem,
	quantity: number | undefined
): Gtag.Item => {
	const productIdentifier = product.sku ? product.sku : '#' + product.id;
	const productCategory =
		'categories' in product && product.categories.length
			? product.categories[ 0 ].name
			: '';
	return {
		id: productIdentifier,
		name: product.name,
		quantity,
		category: productCategory,
		price: (
			parseInt( product.prices.price, 10 ) /
			10 ** product.prices.currency_minor_unit
		).toString(),
	};
};

/**
 * Formats data into the impressionFieldObject shape.
 *
 * @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-ecommerce#impression-data
 */
export const getProductImpressionObject = (
	product: ProductResponseItem,
	listName: string
): ImpressionItem => {
	const productIdentifier = product.sku ? product.sku : '#' + product.id;
	const productCategory = product.categories.length
		? product.categories[ 0 ].name
		: '';
	return {
		id: productIdentifier,
		name: product.name,
		list_name: listName,
		category: productCategory,
		price: (
			parseInt( product.prices.price, 10 ) /
			10 ** product.prices.currency_minor_unit
		).toString(),
	};
};

/**
 * Track an event using the global gtag function.
 */
export const trackEvent = (
	eventName: Gtag.EventNames | string,
	eventParams?: Gtag.ControlParams | Gtag.EventParams | Gtag.CustomParams
): void => {
	if ( typeof gtag !== 'function' ) {
		throw new Error( 'Function gtag not implemented.' );
	}
	// eslint-disable-next-line no-console
	console.log( `Tracking event ${ eventName }` );
	window.gtag( 'event', eventName, eventParams );
};

let currentStep = -1;

export const trackCheckoutStep =
	( step: number ) =>
	( { storeCart }: { storeCart: StoreCart } ): void => {
		if ( currentStep === step ) {
			return;
		}
		trackEvent( step === 0 ? 'begin_checkout' : 'checkout_progress', {
			items: storeCart.cartItems.map( getProductFieldObject ),
			coupon: storeCart.cartCoupons[ 0 ]?.code || '',
			currency: storeCart.cartTotals.currency_code,
			value: (
				parseInt( storeCart.cartTotals.total_price, 10 ) /
				10 ** storeCart.cartTotals.currency_minor_unit
			).toString(),
			checkout_step: step,
		} );
		currentStep = step;
	};

export const trackCheckoutOption =
	( {
		step,
		option,
		value,
	}: {
		step: number;
		option: string;
		value: string;
	} ) =>
	(): void => {
		trackEvent( 'set_checkout_option', {
			checkout_step: step,
			checkout_option: option,
			value,
		} );
		currentStep = step;
	};
payment-methods/bacs/constants.js000064400000000053151551514440013147 0ustar00export const PAYMENT_METHOD_NAME = 'bacs';
payment-methods/bacs/index.js000064400000002263151551514440012247 0ustar00/**
 * External dependencies
 */
import { registerPaymentMethod } from '@woocommerce/blocks-registry';
import { __ } from '@wordpress/i18n';
import { getPaymentMethodData } from '@woocommerce/settings';
import { decodeEntities } from '@wordpress/html-entities';

/**
 * Internal dependencies
 */
import { PAYMENT_METHOD_NAME } from './constants';

const settings = getPaymentMethodData( 'bacs', {} );
const defaultLabel = __(
	'Direct bank transfer',
	'woocommerce'
);
const label = decodeEntities( settings?.title || '' ) || defaultLabel;

/**
 * Content component
 */
const Content = () => {
	return decodeEntities( settings.description || '' );
};

/**
 * Label component
 *
 * @param {*} props Props from payment API.
 */
const Label = ( props ) => {
	const { PaymentMethodLabel } = props.components;
	return <PaymentMethodLabel text={ label } />;
};

/**
 * Bank transfer (BACS) payment method config object.
 */
const bankTransferPaymentMethod = {
	name: PAYMENT_METHOD_NAME,
	label: <Label />,
	content: <Content />,
	edit: <Content />,
	canMakePayment: () => true,
	ariaLabel: label,
	supports: {
		features: settings?.supports ?? [],
	},
};

registerPaymentMethod( bankTransferPaymentMethod );
payment-methods/cheque/constants.js000064400000000055151551514440013513 0ustar00export const PAYMENT_METHOD_NAME = 'cheque';
payment-methods/cheque/index.js000064400000002240151551514440012604 0ustar00/**
 * External dependencies
 */
import { registerPaymentMethod } from '@woocommerce/blocks-registry';
import { __ } from '@wordpress/i18n';
import { getPaymentMethodData } from '@woocommerce/settings';
import { decodeEntities } from '@wordpress/html-entities';

/**
 * Internal dependencies
 */
import { PAYMENT_METHOD_NAME } from './constants';

const settings = getPaymentMethodData( 'cheque', {} );
const defaultLabel = __( 'Check payment', 'woocommerce' );
const label = decodeEntities( settings?.title || '' ) || defaultLabel;

/**
 * Content component
 */
const Content = () => {
	return decodeEntities( settings.description || '' );
};

/**
 * Label component
 *
 * @param {*} props Props from payment API.
 */
const Label = ( props ) => {
	const { PaymentMethodLabel } = props.components;
	return <PaymentMethodLabel text={ label } />;
};

/**
 * Cheque payment method config object.
 */
const offlineChequePaymentMethod = {
	name: PAYMENT_METHOD_NAME,
	label: <Label />,
	content: <Content />,
	edit: <Content />,
	canMakePayment: () => true,
	ariaLabel: label,
	supports: {
		features: settings?.supports ?? [],
	},
};

registerPaymentMethod( offlineChequePaymentMethod );
payment-methods/cod/constants.js000064400000000052151551514440013003 0ustar00export const PAYMENT_METHOD_NAME = 'cod';
payment-methods/cod/index.js000064400000004770151551514440012111 0ustar00/**
 * External dependencies
 */
import { registerPaymentMethod } from '@woocommerce/blocks-registry';
import { __ } from '@wordpress/i18n';
import { getPaymentMethodData } from '@woocommerce/settings';
import { decodeEntities } from '@wordpress/html-entities';

/**
 * Internal dependencies
 */
import { PAYMENT_METHOD_NAME } from './constants';

const settings = getPaymentMethodData( 'cod', {} );
const defaultLabel = __( 'Cash on delivery', 'woocommerce' );
const label = decodeEntities( settings?.title || '' ) || defaultLabel;

/**
 * Content component
 */
const Content = () => {
	return decodeEntities( settings.description || '' );
};

/**
 * Label component
 *
 * @param {*} props Props from payment API.
 */
const Label = ( props ) => {
	const { PaymentMethodLabel } = props.components;
	return <PaymentMethodLabel text={ label } />;
};

/**
 * Determine whether COD is available for this cart/order.
 *
 * @param {Object}  props                         Incoming props for the component.
 * @param {boolean} props.cartNeedsShipping       True if the cart contains any physical/shippable products.
 * @param {boolean} props.selectedShippingMethods
 *
 * @return {boolean}  True if COD payment method should be displayed as a payment option.
 */
const canMakePayment = ( { cartNeedsShipping, selectedShippingMethods } ) => {
	if ( ! settings.enableForVirtual && ! cartNeedsShipping ) {
		// Store doesn't allow COD for virtual orders AND
		// order doesn't contain any shippable products.
		return false;
	}

	if ( ! settings.enableForShippingMethods.length ) {
		// Store does not limit COD to specific shipping methods.
		return true;
	}

	// Look for a supported shipping method in the user's selected
	// shipping methods. If one is found, then COD is allowed.
	const selectedMethods = Object.values( selectedShippingMethods );
	// supported shipping methods might be global (eg. "Any flat rate"), hence
	// this is doing a `String.prototype.includes` match vs a `Array.prototype.includes` match.
	return settings.enableForShippingMethods.some( ( shippingMethodId ) => {
		return selectedMethods.some( ( selectedMethod ) => {
			return selectedMethod.includes( shippingMethodId );
		} );
	} );
};

/**
 * Cash on Delivery (COD) payment method config object.
 */
const cashOnDeliveryPaymentMethod = {
	name: PAYMENT_METHOD_NAME,
	label: <Label />,
	content: <Content />,
	edit: <Content />,
	canMakePayment,
	ariaLabel: label,
	supports: {
		features: settings?.supports ?? [],
	},
};

registerPaymentMethod( cashOnDeliveryPaymentMethod );
payment-methods/paypal/constants.js000064400000000055151551514440013527 0ustar00export const PAYMENT_METHOD_NAME = 'paypal';
payment-methods/paypal/index.js000064400000002103151551514440012616 0ustar00/**
 * External dependencies
 */
import { registerPaymentMethod } from '@woocommerce/blocks-registry';
import { __ } from '@wordpress/i18n';
import { getPaymentMethodData, WC_ASSET_URL } from '@woocommerce/settings';
import { decodeEntities } from '@wordpress/html-entities';

/**
 * Internal dependencies
 */
import { PAYMENT_METHOD_NAME } from './constants';

const settings = getPaymentMethodData( 'paypal', {} );

/**
 * Content component
 */
const Content = () => {
	return decodeEntities( settings.description || '' );
};

const paypalPaymentMethod = {
	name: PAYMENT_METHOD_NAME,
	label: (
		<img
			src={ `${ WC_ASSET_URL }/images/paypal.png` }
			alt={ decodeEntities(
				settings.title || __( 'PayPal', 'woocommerce' )
			) }
		/>
	),
	placeOrderButtonLabel: __(
		'Proceed to PayPal',
		'woocommerce'
	),
	content: <Content />,
	edit: <Content />,
	canMakePayment: () => true,
	ariaLabel: decodeEntities(
		settings?.title ||
			__( 'Payment via PayPal', 'woocommerce' )
	),
	supports: {
		features: settings.supports ?? [],
	},
};

registerPaymentMethod( paypalPaymentMethod );
shipping-methods/pickup-location/edit-location/form.tsx000064400000011116151551514440017372 0ustar00/**
 * External dependencies
 */
import { __ } from '@wordpress/i18n';
import { SelectControl, TextControl } from '@wordpress/components';

/**
 * Internal dependencies
 */
import type { PickupLocation } from '../types';
import { countryStateOptions, states } from '../utils';

const Form = ( {
	formRef,
	values,
	setValues,
}: {
	formRef: React.RefObject< HTMLFormElement >;
	values: PickupLocation;
	setValues: React.Dispatch< React.SetStateAction< PickupLocation > >;
} ) => {
	const { country: selectedCountry, state: selectedState } = values.address;
	const setLocationField =
		( field: keyof PickupLocation ) => ( newValue: string | boolean ) => {
			setValues( ( prevValue: PickupLocation ) => ( {
				...prevValue,
				[ field ]: newValue,
			} ) );
		};

	const setLocationAddressField =
		( field: keyof PickupLocation[ 'address' ] ) =>
		( newValue: string | boolean ) => {
			setValues( ( prevValue ) => ( {
				...prevValue,
				address: {
					...prevValue.address,
					[ field ]: newValue,
				},
			} ) );
		};

	const countryHasStates =
		states[ selectedCountry ] &&
		Object.keys( states[ selectedCountry ] ).length > 0;

	return (
		<form ref={ formRef }>
			<TextControl
				label={ __( 'Location name', 'woo-gutenberg-products-block' ) }
				name={ 'location_name' }
				value={ values.name }
				onChange={ setLocationField( 'name' ) }
				autoComplete="off"
				required={ true }
				onInvalid={ (
					event: React.InvalidEvent< HTMLInputElement >
				) => {
					event.target.setCustomValidity(
						__(
							'A Location title is required',
							'woo-gutenberg-products-block'
						)
					);
				} }
				onInput={ ( event: React.ChangeEvent< HTMLInputElement > ) => {
					event.target.setCustomValidity( '' );
				} }
			/>
			<TextControl
				label={ __( 'Address', 'woo-gutenberg-products-block' ) }
				name={ 'location_address' }
				placeholder={ __( 'Address', 'woo-gutenberg-products-block' ) }
				value={ values.address.address_1 }
				onChange={ setLocationAddressField( 'address_1' ) }
				autoComplete="off"
			/>
			<TextControl
				label={ __( 'City', 'woo-gutenberg-products-block' ) }
				name={ 'location_city' }
				hideLabelFromVision={ true }
				placeholder={ __( 'City', 'woo-gutenberg-products-block' ) }
				value={ values.address.city }
				onChange={ setLocationAddressField( 'city' ) }
				autoComplete="off"
			/>
			<TextControl
				label={ __( 'Postcode / ZIP', 'woo-gutenberg-products-block' ) }
				name={ 'location_postcode' }
				hideLabelFromVision={ true }
				placeholder={ __(
					'Postcode / ZIP',
					'woo-gutenberg-products-block'
				) }
				value={ values.address.postcode }
				onChange={ setLocationAddressField( 'postcode' ) }
				autoComplete="off"
			/>
			{ ! countryHasStates && (
				<TextControl
					placeholder={ __(
						'State',
						'woo-gutenberg-products-block'
					) }
					value={ selectedState }
					onChange={ setLocationAddressField( 'state' ) }
				/>
			) }
			<SelectControl
				name="location_country_state"
				label={ __(
					'Country / State',
					'woo-gutenberg-products-block'
				) }
				hideLabelFromVision={ true }
				placeholder={ __(
					'Country / State',
					'woo-gutenberg-products-block'
				) }
				value={ ( () => {
					if ( ! selectedState && countryHasStates ) {
						return `${ selectedCountry }:${
							Object.keys( states[ selectedCountry ] )[ 0 ]
						}`;
					}

					return `${ selectedCountry }${
						selectedState &&
						states[ selectedCountry ]?.[ selectedState ]
							? ':' + selectedState
							: ''
					}`;
				} )() }
				onChange={ ( val: string ) => {
					const [ country, state = '' ] = val.split( ':' );
					setLocationAddressField( 'country' )( country );
					setLocationAddressField( 'state' )( state );
				} }
			>
				{ countryStateOptions.options.map( ( option ) => {
					if ( option.label ) {
						return (
							<optgroup
								key={ option.label }
								label={ option.label }
							>
								{ option.options.map( ( subOption ) => (
									<option
										key={ subOption.value }
										value={ subOption.value }
									>
										{ subOption.label }
									</option>
								) ) }
							</optgroup>
						);
					}

					return (
						<option
							key={ option.options[ 0 ].value }
							value={ option.options[ 0 ].value }
						>
							{ option.options[ 0 ].label }
						</option>
					);
				} ) }
			</SelectControl>

			<TextControl
				label={ __( 'Pickup details', 'woo-gutenberg-products-block' ) }
				name={ 'pickup_details' }
				value={ values.details }
				onChange={ setLocationField( 'details' ) }
				autoComplete="off"
			/>
		</form>
	);
};

export default Form;
shipping-methods/pickup-location/edit-location/index.tsx000064400000004007151551514440017537 0ustar00/**
 * External dependencies
 */
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { useRef, useState } from '@wordpress/element';
import type { UniqueIdentifier } from '@dnd-kit/core';

/**
 * Internal dependencies
 */
import { SettingsModal } from '../../shared-components';
import Form from './form';
import type { PickupLocation } from '../types';

const EditLocation = ( {
	locationData,
	editingLocation,
	onClose,
	onSave,
	onDelete,
}: {
	locationData: PickupLocation | null;
	editingLocation: UniqueIdentifier | 'new';
	onClose: () => void;
	onSave: ( location: PickupLocation ) => void;
	onDelete: () => void;
} ): JSX.Element | null => {
	const formRef = useRef( null );
	const [ values, setValues ] = useState< PickupLocation >(
		locationData as PickupLocation
	);

	if ( ! locationData ) {
		return null;
	}

	return (
		<SettingsModal
			onRequestClose={ onClose }
			title={
				editingLocation === 'new'
					? __( 'Pickup location', 'woo-gutenberg-products-block' )
					: __(
							'Edit pickup location',
							'woo-gutenberg-products-block'
					  )
			}
			actions={
				<>
					{ editingLocation !== 'new' && (
						<Button
							variant="link"
							className="button-link-delete"
							onClick={ () => {
								onDelete();
								onClose();
							} }
						>
							{ __(
								'Delete location',
								'woo-gutenberg-products-block'
							) }
						</Button>
					) }
					<Button variant="secondary" onClick={ onClose }>
						{ __( 'Cancel', 'woo-gutenberg-products-block' ) }
					</Button>
					<Button
						variant="primary"
						onClick={ () => {
							const form =
								formRef?.current as unknown as HTMLFormElement;
							if ( form.reportValidity() ) {
								onSave( values );
								onClose();
							}
						} }
					>
						{ __( 'Done', 'woo-gutenberg-products-block' ) }
					</Button>
				</>
			}
		>
			<Form
				formRef={ formRef }
				values={ values }
				setValues={ setValues }
			/>
		</SettingsModal>
	);
};

export default EditLocation;
shipping-methods/pickup-location/general-settings.tsx000064400000012717151551514440017157 0ustar00/**
 * External dependencies
 */
import { __ } from '@wordpress/i18n';
import { createInterpolateElement, useState } from '@wordpress/element';
import { ADMIN_URL, getSetting } from '@woocommerce/settings';
import { CHECKOUT_PAGE_ID } from '@woocommerce/block-settings';
import {
	CheckboxControl,
	SelectControl,
	TextControl,
	ExternalLink,
	Notice,
} from '@wordpress/components';
import styled from '@emotion/styled';

/**
 * Internal dependencies
 */
import { SettingsCard, SettingsSection } from '../shared-components';
import { useSettingsContext } from './settings-context';

const GeneralSettingsDescription = () => (
	<>
		<h2>{ __( 'General', 'woo-gutenberg-products-block' ) }</h2>
		<p>
			{ __(
				'Enable or disable local pickup on your store, and define costs. Local pickup is only available from the block checkout.',
				'woo-gutenberg-products-block'
			) }
		</p>
		<ExternalLink
			href={ `${ ADMIN_URL }post.php?post=${ CHECKOUT_PAGE_ID }&action=edit` }
		>
			{ __( 'View checkout page', 'woo-gutenberg-products-block' ) }
		</ExternalLink>
	</>
);

const StyledNotice = styled( Notice )`
	margin-left: 0;
	margin-right: 0;
`;

const GeneralSettings = () => {
	const { settings, setSettingField, readOnlySettings } =
		useSettingsContext();
	const [ showCosts, setShowCosts ] = useState( !! settings.cost );

	const shippingCostRequiresAddress = getSetting< boolean >(
		'shippingCostRequiresAddress',
		false
	);

	return (
		<SettingsSection Description={ GeneralSettingsDescription }>
			<SettingsCard>
				{ readOnlySettings.hasLegacyPickup && (
					<StyledNotice status="warning" isDismissible={ false }>
						{ createInterpolateElement(
							__(
								'Enabling this will produce duplicate options at checkout. Remove the local pickup shipping method from your <a>shipping zones</a>.',
								'woo-gutenberg-products-block'
							),
							{
								a: (
									// eslint-disable-next-line jsx-a11y/anchor-has-content
									<a
										href={ `${ ADMIN_URL }admin.php?page=wc-settings&tab=shipping` }
										target="_blank"
										rel="noopener noreferrer"
									/>
								),
							}
						) }
					</StyledNotice>
				) }
				<CheckboxControl
					checked={ settings.enabled }
					name="local_pickup_enabled"
					onChange={ setSettingField( 'enabled' ) }
					label={ __(
						'Enable local pickup',
						'woo-gutenberg-products-block'
					) }
					help={
						<span>
							{ __(
								'When enabled, local pickup will appear as an option on the block based checkout.',
								'woo-gutenberg-products-block'
							) }
							{ shippingCostRequiresAddress ? (
								<>
									<br />
									{ __(
										'If local pickup is enabled, the "Hide shipping costs until an address is entered" setting will be ignored.',
										'woo-gutenberg-products-block'
									) }
								</>
							) : null }
						</span>
					}
				/>
				<TextControl
					label={ __( 'Title', 'woo-gutenberg-products-block' ) }
					name="local_pickup_title"
					help={ __(
						'This is the shipping method title shown to customers.',
						'woo-gutenberg-products-block'
					) }
					placeholder={ __(
						'Local Pickup',
						'woo-gutenberg-products-block'
					) }
					value={ settings.title }
					onChange={ setSettingField( 'title' ) }
					disabled={ false }
					autoComplete="off"
					required={ true }
					onInvalid={ (
						event: React.InvalidEvent< HTMLInputElement >
					) => {
						event.target.setCustomValidity(
							__(
								'Local pickup title is required',
								'woo-gutenberg-products-block'
							)
						);
					} }
					onInput={ (
						event: React.ChangeEvent< HTMLInputElement >
					) => {
						event.target.setCustomValidity( '' );
					} }
				/>
				<CheckboxControl
					checked={ showCosts }
					onChange={ () => {
						setShowCosts( ! showCosts );
						setSettingField( 'cost' )( '' );
					} }
					label={ __(
						'Add a price for customers who choose local pickup',
						'woo-gutenberg-products-block'
					) }
					help={ __(
						'By default, the local pickup shipping method is free.',
						'woo-gutenberg-products-block'
					) }
				/>
				{ showCosts ? (
					<>
						<TextControl
							label={ __(
								'Cost',
								'woo-gutenberg-products-block'
							) }
							name="local_pickup_cost"
							help={ __(
								'Optional cost to charge for local pickup.',
								'woo-gutenberg-products-block'
							) }
							placeholder={ __(
								'Free',
								'woo-gutenberg-products-block'
							) }
							type="number"
							pattern="[0-9]+\.?[0-9]*"
							min={ 0 }
							value={ settings.cost }
							onChange={ setSettingField( 'cost' ) }
							disabled={ false }
							autoComplete="off"
						/>
						<SelectControl
							label={ __(
								'Taxes',
								'woo-gutenberg-products-block'
							) }
							name="local_pickup_tax_status"
							help={ __(
								'If a cost is defined, this controls if taxes are applied to that cost.',
								'woo-gutenberg-products-block'
							) }
							options={ [
								{
									label: __(
										'Taxable',
										'woo-gutenberg-products-block'
									),
									value: 'taxable',
								},
								{
									label: __(
										'Not taxable',
										'woo-gutenberg-products-block'
									),
									value: 'none',
								},
							] }
							value={ settings.tax_status }
							onChange={ setSettingField( 'tax_status' ) }
							disabled={ false }
						/>
					</>
				) : null }
			</SettingsCard>
		</SettingsSection>
	);
};

export default GeneralSettings;
shipping-methods/pickup-location/index.js000064400000000531151551514440014600 0ustar00/**
 * External dependencies
 */
import { render } from '@wordpress/element';

/**
 * Internal dependencies
 */
import SettingsPage from './settings-page';

const settingsContainer = document.getElementById(
	'wc-shipping-method-pickup-location-settings-container'
);
if ( settingsContainer ) {
	render( <SettingsPage />, settingsContainer );
}
shipping-methods/pickup-location/location-settings.tsx000064400000007716151551514440017355 0ustar00/**
 * External dependencies
 */
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import type { UniqueIdentifier } from '@dnd-kit/core';
import { isBoolean } from '@woocommerce/types';
import { ToggleControl, Button, ExternalLink } from '@wordpress/components';
import styled from '@emotion/styled';

/**
 * Internal dependencies
 */
import {
	SettingsSection,
	SortableTable,
	SortableData,
} from '../shared-components';
import EditLocation from './edit-location';
import type { SortablePickupLocation } from './types';
import { useSettingsContext } from './settings-context';
import { getUserFriendlyAddress } from './utils';

const LocationSettingsDescription = () => (
	<>
		<h2>{ __( 'Pickup locations', 'woo-gutenberg-products-block' ) }</h2>
		<p>
			{ __(
				'Define pickup locations for your customers to choose from during checkout.',
				'woo-gutenberg-products-block'
			) }
		</p>
		<ExternalLink href="https://woocommerce.com/document/local-pickup/">
			{ __( 'Learn more', 'woo-gutenberg-products-block' ) }
		</ExternalLink>
	</>
);

const StyledAddress = styled.address`
	color: #757575;
	font-style: normal;
	display: inline;
	margin-left: 12px;
`;

const LocationSettings = () => {
	const {
		pickupLocations,
		setPickupLocations,
		toggleLocation,
		updateLocation,
		readOnlySettings,
	} = useSettingsContext();
	const [ editingLocation, setEditingLocation ] =
		useState< UniqueIdentifier >( '' );

	const tableColumns = [
		{
			name: 'name',
			label: __( 'Pickup location', 'woo-gutenberg-products-block' ),
			width: '50%',
			renderCallback: ( row: SortableData ): JSX.Element => (
				<>
					{ row.name }
					<StyledAddress>
						{ getUserFriendlyAddress( row.address ) }
					</StyledAddress>
				</>
			),
		},
		{
			name: 'enabled',
			label: __( 'Enabled', 'woo-gutenberg-products-block' ),
			align: 'right',
			renderCallback: ( row: SortableData ): JSX.Element => (
				<ToggleControl
					checked={ isBoolean( row.enabled ) ? row.enabled : false }
					onChange={ () => toggleLocation( row.id ) }
				/>
			),
		},
		{
			name: 'edit',
			label: '',
			align: 'center',
			width: '1%',
			renderCallback: ( row: SortableData ): JSX.Element => (
				<button
					type="button"
					className="button-link-edit button-link"
					onClick={ () => {
						setEditingLocation( row.id );
					} }
				>
					{ __( 'Edit', 'woo-gutenberg-products-block' ) }
				</button>
			),
		},
	];

	const FooterContent = (): JSX.Element => (
		<Button
			variant="secondary"
			onClick={ () => {
				setEditingLocation( 'new' );
			} }
		>
			{ __( 'Add pickup location', 'woo-gutenberg-products-block' ) }
		</Button>
	);

	return (
		<SettingsSection Description={ LocationSettingsDescription }>
			<SortableTable
				className="pickup-locations"
				columns={ tableColumns }
				data={ pickupLocations }
				setData={ ( newData ) => {
					setPickupLocations( newData as SortablePickupLocation[] );
				} }
				placeholder={ __(
					'When you add a pickup location, it will appear here.',
					'woo-gutenberg-products-block'
				) }
				footerContent={ FooterContent }
			/>
			{ editingLocation && (
				<EditLocation
					locationData={
						editingLocation === 'new'
							? {
									name: '',
									details: '',
									enabled: true,
									address: {
										address_1: '',
										city: '',
										state: readOnlySettings.storeState,
										postcode: '',
										country: readOnlySettings.storeCountry,
									},
							  }
							: pickupLocations.find( ( { id } ) => {
									return id === editingLocation;
							  } ) || null
					}
					editingLocation={ editingLocation }
					onSave={ ( values ) => {
						updateLocation(
							editingLocation,
							values as SortablePickupLocation
						);
					} }
					onClose={ () => setEditingLocation( '' ) }
					onDelete={ () => {
						updateLocation( editingLocation, null );
						setEditingLocation( '' );
					} }
				/>
			) }
		</SettingsSection>
	);
};

export default LocationSettings;
shipping-methods/pickup-location/save.tsx000064400000002020151551514440014624 0ustar00/**
 * External dependencies
 */
import { __ } from '@wordpress/i18n';
import styled from '@emotion/styled';
import { Button } from '@wordpress/components';

/**
 * Internal dependencies
 */
import { SettingsSection } from '../shared-components';
import { useSettingsContext } from './settings-context';

const SaveSectionWrapper = styled( SettingsSection )`
	text-align: right;
	padding-top: 0;
	margin-top: 0;
`;

const SaveSettings = () => {
	const { isSaving, save } = useSettingsContext();

	return (
		<SaveSectionWrapper className={ 'submit' }>
			<Button
				variant="primary"
				isBusy={ isSaving }
				disabled={ isSaving }
				onClick={ (
					event: React.MouseEvent< HTMLButtonElement, MouseEvent >
				) => {
					event.preventDefault();
					const target = event.target as HTMLButtonElement;
					if ( target?.form?.reportValidity() ) {
						save();
					}
				} }
				type="submit"
			>
				{ __( 'Save changes', 'woo-gutenberg-products-block' ) }
			</Button>
		</SaveSectionWrapper>
	);
};

export default SaveSettings;
shipping-methods/pickup-location/settings-context.tsx000064400000010233151551514440017215 0ustar00/**
 * External dependencies
 */
import {
	createContext,
	useContext,
	useCallback,
	useState,
} from '@wordpress/element';
import { cleanForSlug } from '@wordpress/url';
import type { UniqueIdentifier } from '@dnd-kit/core';
import apiFetch from '@wordpress/api-fetch';
import { dispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import fastDeepEqual from 'fast-deep-equal/es6';

/**
 * Internal dependencies
 */
import type {
	SortablePickupLocation,
	SettingsContextType,
	ShippingMethodSettings,
} from './types';
import {
	defaultSettings,
	getInitialSettings,
	defaultReadyOnlySettings,
	readOnlySettings,
	getInitialPickupLocations,
} from './utils';

const SettingsContext = createContext< SettingsContextType >( {
	settings: defaultSettings,
	readOnlySettings: defaultReadyOnlySettings,
	setSettingField: () => () => void null,
	pickupLocations: [],
	setPickupLocations: () => void null,
	toggleLocation: () => void null,
	updateLocation: () => void null,
	isSaving: false,
	save: () => void null,
} );

export const useSettingsContext = (): SettingsContextType => {
	return useContext( SettingsContext );
};

export const SettingsProvider = ( {
	children,
}: {
	children: JSX.Element[] | JSX.Element;
} ): JSX.Element => {
	const [ isSaving, setIsSaving ] = useState( false );
	const [ pickupLocations, setPickupLocations ] = useState<
		SortablePickupLocation[]
	>( getInitialPickupLocations );
	const [ settings, setSettings ] =
		useState< ShippingMethodSettings >( getInitialSettings );

	const setSettingField = useCallback(
		( field: keyof ShippingMethodSettings ) => ( newValue: unknown ) => {
			setSettings( ( prevValue ) => ( {
				...prevValue,
				[ field ]: newValue,
			} ) );
		},
		[]
	);

	const toggleLocation = useCallback( ( rowId: UniqueIdentifier ) => {
		setPickupLocations( ( previousLocations: SortablePickupLocation[] ) => {
			const locationIndex = previousLocations.findIndex(
				( { id } ) => id === rowId
			);
			const updated = [ ...previousLocations ];
			updated[ locationIndex ].enabled =
				! previousLocations[ locationIndex ].enabled;
			return updated;
		} );
	}, [] );

	const updateLocation = (
		rowId: UniqueIdentifier | 'new',
		locationData: SortablePickupLocation
	) => {
		setPickupLocations( ( prevData ) => {
			if ( rowId === 'new' ) {
				return [
					...prevData,
					{
						...locationData,
						id:
							cleanForSlug( locationData.name ) +
							'-' +
							prevData.length,
					},
				];
			}
			return prevData
				.map( ( location ): SortablePickupLocation => {
					if ( location.id === rowId ) {
						return locationData;
					}
					return location;
				} )
				.filter( Boolean );
		} );
	};

	const save = useCallback( () => {
		const data = {
			pickup_location_settings: {
				enabled: settings.enabled ? 'yes' : 'no',
				title: settings.title,
				tax_status: [ 'taxable', 'none' ].includes(
					settings.tax_status
				)
					? settings.tax_status
					: 'taxable',
				cost: settings.cost,
			},
			pickup_locations: pickupLocations.map( ( location ) => ( {
				name: location.name,
				address: location.address,
				details: location.details,
				enabled: location.enabled,
			} ) ),
		};

		setIsSaving( true );

		// @todo This should be improved to include error handling in case of API failure, or invalid data being sent that
		// does not match the schema. This would fail silently on the API side.
		apiFetch( {
			path: '/wp/v2/settings',
			method: 'POST',
			data,
		} ).then( ( response ) => {
			setIsSaving( false );
			if (
				fastDeepEqual(
					response.pickup_location_settings,
					data.pickup_location_settings
				) &&
				fastDeepEqual(
					response.pickup_locations,
					data.pickup_locations
				)
			) {
				dispatch( 'core/notices' ).createSuccessNotice(
					__(
						'Local Pickup settings have been saved.',
						'woo-gutenberg-products-block'
					)
				);
			}
		} );
	}, [ settings, pickupLocations ] );

	const settingsData = {
		settings,
		setSettingField,
		readOnlySettings,
		pickupLocations,
		setPickupLocations,
		toggleLocation,
		updateLocation,
		isSaving,
		save,
	};

	return (
		<SettingsContext.Provider value={ settingsData }>
			{ children }
		</SettingsContext.Provider>
	);
};
shipping-methods/pickup-location/settings-page.tsx000064400000001332151551514440016445 0ustar00/**
 * External dependencies
 */
import styled from '@emotion/styled';

/**
 * Internal dependencies
 */
import GeneralSettings from './general-settings';
import LocationSettings from './location-settings';
import SaveSettings from './save';
import { SettingsProvider } from './settings-context';

const SettingsWrapper = styled.form`
	margin: 48px auto 0;
	max-width: 1032px;
	display: flex;
	flex-flow: column;

	@media ( min-width: 960px ) {
		padding: 0 56px;
	}
`;

const SettingsPage = () => {
	return (
		<SettingsWrapper id="local-pickup-settings">
			<SettingsProvider>
				<GeneralSettings />
				<LocationSettings />
				<SaveSettings />
			</SettingsProvider>
		</SettingsWrapper>
	);
};

export default SettingsPage;
shipping-methods/pickup-location/types.ts000064400000002260151551514440014650 0ustar00/**
 * External dependencies
 */
import type { UniqueIdentifier } from '@dnd-kit/core';

/**
 * Internal dependencies
 */
import type { SortableData } from '../shared-components';

export interface PickupLocation {
	name: string;
	details: string;
	enabled: boolean;
	address: {
		address_1: string;
		city: string;
		state: string;
		postcode: string;
		country: string;
	};
}

export interface SortablePickupLocation extends PickupLocation, SortableData {}

export type ShippingMethodSettings = {
	enabled: boolean;
	title: string;
	tax_status: string;
	cost: string;
};

export type ReadOnlySettings = {
	storeCountry: string;
	storeState: string;
	hasLegacyPickup: boolean;
};

export type SettingsContextType = {
	settings: ShippingMethodSettings;
	readOnlySettings: ReadOnlySettings;
	setSettingField: (
		field: keyof ShippingMethodSettings
	) => ( value: unknown ) => void;
	pickupLocations: SortablePickupLocation[];
	setPickupLocations: ( locations: SortablePickupLocation[] ) => void;
	toggleLocation: ( rowId: UniqueIdentifier ) => void;
	updateLocation: (
		rowId: UniqueIdentifier | 'new',
		location: SortablePickupLocation | null
	) => void;
	isSaving: boolean;
	save: () => void;
};
shipping-methods/pickup-location/utils.ts000064400000006306151551514440014651 0ustar00/**
 * External dependencies
 */
import { cleanForSlug } from '@wordpress/url';
import { __ } from '@wordpress/i18n';
import { isObject } from '@woocommerce/types';
import { getSetting } from '@woocommerce/settings';
/**
 * Internal dependencies
 */
import type {
	PickupLocation,
	SortablePickupLocation,
	ShippingMethodSettings,
} from './types';

export const indexLocationsById = (
	locations: PickupLocation[]
): SortablePickupLocation[] => {
	return locations.map( ( value, index ) => {
		return {
			...value,
			id: cleanForSlug( value.name ) + '-' + index,
		};
	} );
};

export const defaultSettings = {
	enabled: false,
	title: __( 'Local Pickup', 'woo-gutenberg-products-block' ),
	tax_status: 'taxable',
	cost: '',
};

export const defaultReadyOnlySettings = {
	hasLegacyPickup: false,
	storeCountry: '',
	storeState: '',
};
declare global {
	const hydratedScreenSettings: {
		pickupLocationSettings: {
			enabled: string;
			title: string;
			tax_status: string;
			cost: string;
		};
		pickupLocations: PickupLocation[];
		readonlySettings: typeof defaultReadyOnlySettings;
	};
}

export const getInitialSettings = (): ShippingMethodSettings => {
	const settings = hydratedScreenSettings.pickupLocationSettings;

	return {
		enabled: settings?.enabled
			? settings?.enabled === 'yes'
			: defaultSettings.enabled,
		title: settings?.title || defaultSettings.title,
		tax_status: settings?.tax_status || defaultSettings.tax_status,
		cost: settings?.cost || defaultSettings.cost,
	};
};

export const getInitialPickupLocations = (): SortablePickupLocation[] =>
	indexLocationsById( hydratedScreenSettings.pickupLocations || [] );

export const readOnlySettings =
	hydratedScreenSettings.readonlySettings || defaultReadyOnlySettings;

export const countries = getSetting< Record< string, string > >(
	'countries',
	[]
);
export const states = getSetting< Record< string, Record< string, string > > >(
	'countryStates',
	[]
);
export const getUserFriendlyAddress = ( address: unknown ) => {
	const updatedAddress = isObject( address ) && {
		...address,
		country:
			typeof address.country === 'string' && countries[ address.country ],
		state:
			typeof address.country === 'string' &&
			typeof address.state === 'string' &&
			states[ address.country ]?.[ address.state ]
				? states[ address.country ][ address.state ]
				: address.state,
	};

	return Object.values( updatedAddress )
		.filter( ( value ) => value !== '' )
		.join( ', ' );
};

// Outputs the list of countries and states in a single dropdown select.
const countryStateDropdownOptions = () => {
	const countryStateOptions = Object.keys( countries ).map( ( country ) => {
		const countryStates = states[ country ] || {};

		if ( Object.keys( countryStates ).length === 0 ) {
			return {
				options: [
					{
						value: country,
						label: countries[ country ],
					},
				],
			};
		}

		const stateOptions = Object.keys( countryStates ).map( ( state ) => ( {
			value: `${ country }:${ state }`,
			label: `${ countries[ country ] } — ${ countryStates[ state ] }`,
		} ) );
		return {
			label: countries[ country ],
			options: [ ...stateOptions ],
		};
	} );
	return {
		options: countryStateOptions,
	};
};
export const countryStateOptions = countryStateDropdownOptions();
shipping-methods/shared-components/index.ts000064400000000333151551514440015142 0ustar00export { default as SettingsCard } from './settings-card';
export { default as SettingsSection } from './settings-section';
export { default as SettingsModal } from './settings-modal';
export * from './sortable-table';
shipping-methods/shared-components/settings-card/index.tsx000064400000002164151551514440020105 0ustar00/**
 * External dependencies
 */
import { Card, CardBody } from '@wordpress/components';
import styled from '@emotion/styled';
import type { ReactNode } from 'react';

const StyledCard = styled( Card )`
	border-radius: 3px;
`;

const StyledCardBody = styled( CardBody )`
	padding: 24px;

	// increasing the specificity of the styles to override the Gutenberg ones
	&.is-size-medium.is-size-medium {
		padding: 24px;
	}

	h4 {
		margin-top: 0;
		margin-bottom: 1em;
	}

	> * {
		margin-top: 0;
		margin-bottom: 1.5em;

		// fixing the spacing on the inputs and their help text, to ensure it is consistent
		&:last-child {
			margin-bottom: 0;

			> :last-child {
				margin-bottom: 0;
			}
		}
	}

	input,
	select {
		margin: 0;
	}

	// spacing adjustment on "Express checkouts > Show express checkouts on" list
	ul > li:last-child {
		margin-bottom: 0;

		.components-base-control__field {
			margin-bottom: 0;
		}
	}
`;

const SettingsCard = ( {
	children,
	...props
}: {
	children: ReactNode;
} ): JSX.Element => (
	<StyledCard>
		<StyledCardBody { ...props }>{ children }</StyledCardBody>
	</StyledCard>
);

export default SettingsCard;
shipping-methods/shared-components/settings-modal/index.tsx000064400000002706151551514440020272 0ustar00/**
 * External dependencies
 */
import { Modal } from '@wordpress/components';
import styled from '@emotion/styled';

const StyledModal = styled( Modal )`
	max-width: 600px;
	border-radius: 4px;
	@media ( min-width: 600px ) {
		min-width: 560px;
	}

	.components-modal__header {
		padding: 12px 24px;
		border-bottom: 1px solid #e0e0e0;
		position: relative;
		height: auto;
		width: auto;
		margin: 0 -24px 16px;

		@media ( max-width: 599px ) {
			button {
				display: none;
			}
		}
	}

	.components-modal__content {
		margin: 0;
		padding: 0 24px;

		@media ( max-width: 599px ) {
			display: flex;
			flex-direction: column;

			hr:last-of-type {
				margin-top: auto;
			}
		}

		.components-base-control {
			label {
				margin-top: 8px;
				text-transform: none !important;
			}
		}
	}
`;

const StyledFooter = styled.div`
	display: flex;
	justify-content: flex-end;
	border-top: 1px solid #e0e0e0;
	margin: 24px -24px 0;
	padding: 24px;

	> * {
		&:not( :first-of-type ) {
			margin-left: 8px;
		}
	}

	.button-link-delete {
		margin-right: auto;
		color: #d63638;
	}
`;

const SettingsModal = ( {
	children,
	actions,
	title,
	onRequestClose,
	...props
}: {
	children: React.ReactNode;
	actions: React.ReactNode;
	title: string;
	onRequestClose: () => void;
} ): JSX.Element => (
	<StyledModal title={ title } onRequestClose={ onRequestClose } { ...props }>
		{ children }
		<StyledFooter>{ actions }</StyledFooter>
	</StyledModal>
);

export default SettingsModal;
shipping-methods/shared-components/settings-section/index.tsx000064400000002365151551514440020643 0ustar00/**
 * External dependencies
 */
import React from '@wordpress/element';
import styled from '@emotion/styled';

const StyledSectionWrapper = styled.div`
	display: flex;
	flex-flow: column;
	margin-bottom: 24px;
	&:last-child {
		margin-bottom: 0;
	}
	@media ( min-width: 800px ) {
		flex-flow: row;
	}
	.components-base-control {
		label {
			text-transform: none !important;
		}
	}
`;

const StyledDescriptionWrapper = styled.div`
	flex: 0 1 auto;
	margin-bottom: 24px;
	@media ( min-width: 800px ) {
		flex: 0 0 250px;
		margin: 0 32px 0 0;
	}
	h2 {
		font-size: 16px;
		line-height: 24px;
	}
	p {
		font-size: 13px;
		line-height: 17.89px;
		margin: 12px 0;
	}
	> :last-child {
		margin-bottom: 0;
	}
`;

const StyledSectionControls = styled.div`
	flex: 1 1 auto;
	margin-bottom: 12px;
`;

const SettingsSection = ( {
	Description = () => null,
	children,
	...props
}: {
	// eslint-disable-next-line @typescript-eslint/naming-convention
	Description?: () => JSX.Element | null;
	children: React.ReactNode;
} ): JSX.Element => (
	<StyledSectionWrapper { ...props }>
		<StyledDescriptionWrapper>
			<Description />
		</StyledDescriptionWrapper>
		<StyledSectionControls>{ children }</StyledSectionControls>
	</StyledSectionWrapper>
);

export default SettingsSection;
shipping-methods/shared-components/sortable-table/index.tsx000064400000013143151551514440020235 0ustar00/**
 * External dependencies
 */
import styled from '@emotion/styled';
import { Icon, dragHandle } from '@wordpress/icons';
import { useMemo } from '@wordpress/element';
import {
	closestCenter,
	DndContext,
	KeyboardSensor,
	MouseSensor,
	TouchSensor,
	useSensor,
	useSensors,
	DragEndEvent,
	UniqueIdentifier,
} from '@dnd-kit/core';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import {
	SortableContext,
	verticalListSortingStrategy,
	useSortable,
	arrayMove,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { objectHasProp } from '@woocommerce/types';

export interface SortableData extends Record< string, unknown > {
	id: UniqueIdentifier;
}

type ColumnProps = {
	name: string;
	label: string;
	width?: string;
	align?: string;
	renderCallback?: ( row: SortableData ) => JSX.Element;
};

const TableRow = ( {
	children,
	id,
}: {
	children: JSX.Element[];
	id: UniqueIdentifier;
} ): JSX.Element => {
	const { attributes, listeners, transform, transition, setNodeRef } =
		useSortable( {
			id,
		} );
	const style = {
		transform: CSS.Transform.toString( transform ),
		transition,
	};
	return (
		<tr ref={ setNodeRef } style={ style }>
			<>
				<td style={ { width: '1%' } }>
					<Icon
						icon={ dragHandle }
						size={ 14 }
						className={ 'sortable-table__handle' }
						{ ...attributes }
						{ ...listeners }
					/>
				</td>
				{ children }
			</>
		</tr>
	);
};

const StyledTable = styled.table`
	background: #fff;
	border: 0;
	border-radius: 3px;
	box-shadow: 0 0 0 1px rgb( 0 0 0 / 10% );
	border-spacing: 0;
	width: 100%;
	clear: both;
	margin: 0;
	font-size: 14px;

	.align-left {
		text-align: left;
		.components-flex {
			justify-content: flex-start;
			gap: 0;
		}
	}
	.align-right {
		text-align: right;
		.components-flex {
			justify-content: flex-end;
			gap: 0;
		}
	}
	.align-center {
		text-align: center;
		> * {
			margin: 0 auto;
		}
		.components-flex {
			display: block;
		}
	}

	.sortable-table__handle {
		cursor: move;
	}

	th {
		position: relative;
		color: #2c3338;
		text-align: left;
		vertical-align: middle;
		vertical-align: top;
		word-wrap: break-word;
	}

	tbody {
		td {
			vertical-align: top;
			margin-bottom: 9px;
		}
	}

	tfoot {
		td {
			text-align: left;
			vertical-align: middle;
		}
	}

	thead,
	tfoot,
	tbody {
		td,
		th {
			border-top: 1px solid rgb( 0 0 0 / 10% );
			border-bottom: 1px solid rgb( 0 0 0 / 10% );
			padding: 16px 0 16px 24px;
			line-height: 1.5;

			&:last-child {
				padding-right: 24px;
			}

			> svg,
			> .components-base-control {
				margin: 3px 0;
			}
		}
	}

	thead th {
		border-top: 0;
	}

	tfoot td {
		border-bottom: 0;
	}
`;

export const SortableTable = ( {
	columns,
	data,
	setData,
	className,
	footerContent: FooterContent,
	placeholder,
}: {
	columns: ColumnProps[];
	data: SortableData[];
	setData: ( data: SortableData[] ) => void;
	className?: string;
	placeholder?: string | ( () => JSX.Element );
	footerContent?: () => JSX.Element;
} ): JSX.Element => {
	const items = useMemo( () => data.map( ( { id } ) => id ), [ data ] );

	const sensors = useSensors(
		useSensor( MouseSensor, {} ),
		useSensor( TouchSensor, {} ),
		useSensor( KeyboardSensor, {} )
	);

	function handleDragEnd( event: DragEndEvent ) {
		const { active, over } = event;

		if ( active !== null && over !== null && active?.id !== over?.id ) {
			const newData = arrayMove(
				data,
				items.indexOf( active.id ),
				items.indexOf( over.id )
			);
			setData( newData );
		}
	}

	const getColumnProps = ( column: ColumnProps, parentClassName: string ) => {
		const align = column?.align || 'left';
		const width = column?.width || 'auto';

		return {
			className: `${ parentClassName }-${ column.name } align-${ align }`,
			style: { width },
		};
	};

	return (
		<DndContext
			sensors={ sensors }
			onDragEnd={ handleDragEnd }
			collisionDetection={ closestCenter }
			modifiers={ [ restrictToVerticalAxis ] }
		>
			<StyledTable className={ `${ className } sortable-table` }>
				<thead>
					<tr>
						{ columns.map( ( column, index ) => (
							<th
								key={ column.name }
								{ ...getColumnProps(
									column,
									`sortable-table__column`
								) }
								colSpan={ index === 0 ? 2 : 1 }
							>
								{ column.label }
							</th>
						) ) }
					</tr>
				</thead>
				{ FooterContent && (
					<tfoot>
						<tr>
							<td colSpan={ columns.length + 1 }>
								<FooterContent />
							</td>
						</tr>
					</tfoot>
				) }
				<tbody>
					<SortableContext
						items={ items }
						strategy={ verticalListSortingStrategy }
					>
						{ !! data.length ? (
							data.map(
								( row ) =>
									row && (
										<TableRow
											key={ row.id }
											id={ row.id }
											className={ className }
										>
											{ columns.map( ( column ) => (
												<td
													key={ `${ row.id }-${ column.name }` }
													{ ...getColumnProps(
														column,
														`sortable-table__column`
													) }
												>
													{ column.renderCallback ? (
														column.renderCallback(
															row
														)
													) : (
														<>
															{ objectHasProp(
																row,
																column.name
															) &&
																row[
																	column.name
																] }
														</>
													) }
												</td>
											) ) }
										</TableRow>
									)
							)
						) : (
							<tr>
								<td colSpan={ columns.length + 1 }>
									{ placeholder }
								</td>
							</tr>
						) }
					</SortableContext>
				</tbody>
			</StyledTable>
		</DndContext>
	);
};

export default SortableTable;