refactor: Paypal App UI & UX (#11528)

* refactor(ui): price input margin

* refactor(ui): price input border

* refactor(ui): inputs spacing

* fix: Use USD as the default currency

* fix: dynamic currency symbols

* undo

* apply same styles for stripe app

* type fixes

---------

Co-authored-by: Alan <alannnc@gmail.com>
pull/11625/head^2
Lauris Skraucis 2023-09-29 22:19:04 +02:00 committed by GitHub
parent e568d70e65
commit e1f47ef40e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 36 deletions

View File

@ -1,9 +1,13 @@
import { useRouter } from "next/router";
import { useState } from "react";
import { useState, useEffect } from "react";
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
import AppCard from "@calcom/app-store/_components/AppCard";
import { currencyOptions } from "@calcom/app-store/paypal/lib/currencyOptions";
import {
currencyOptions,
currencySymbols,
isAcceptedCurrencyCode,
} from "@calcom/app-store/paypal/lib/currencyOptions";
import type { EventTypeAppCardComponent } from "@calcom/app-store/types";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -18,12 +22,11 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
const { asPath } = useRouter();
const { getAppData, setAppData } = useAppContextWithSchema<typeof appDataSchema>();
const price = getAppData("price");
const currency = getAppData("currency");
const [selectedCurrency, setSelectedCurrency] = useState(
currencyOptions.find((c) => c.value === currency) || {
label: currencyOptions[0].label,
value: currencyOptions[0].value,
}
const [selectedCurrency, setSelectedCurrency] = useState(currencyOptions.find((c) => c.value === currency));
const [currencySymbol, setCurrencySymbol] = useState(
isAcceptedCurrencyCode(currency) ? currencySymbols[currency] : ""
);
const paymentOption = getAppData("paymentOption");
@ -36,6 +39,17 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
const { t } = useLocale();
const recurringEventDefined = eventType.recurringEvent?.count !== undefined;
useEffect(() => {
if (requirePayment) {
if (!getAppData("currency")) {
setAppData("currency", currencyOptions[0].value);
}
if (!getAppData("paymentOption")) {
setAppData("paymentOption", paymentOptions[0].value);
}
}
}, []);
return (
<AppCard
returnTo={WEBAPP_URL + asPath}
@ -55,13 +69,13 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
<TextField
label="Price"
labelSrOnly
addOnLeading="$"
addOnSuffix={selectedCurrency.value || "No selected currency"}
addOnLeading={currencySymbol}
addOnSuffix={currency}
step="0.01"
min="0.5"
type="number"
required
className="block w-full rounded-sm border-gray-300 pl-2 pr-12 text-sm"
className="block w-full rounded-sm pl-2 text-sm"
placeholder="Price"
onChange={(e) => {
setAppData("price", Number(e.target.value) * 100);
@ -73,7 +87,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
/>
</div>
<div className="mt-5 w-60">
<label className="text-default block text-sm font-medium" htmlFor="currency">
<label className="text-default mb-1 block text-sm font-medium" htmlFor="currency">
{t("currency")}
</label>
<Select
@ -85,14 +99,15 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
onChange={(e) => {
if (e) {
setSelectedCurrency(e);
setCurrencySymbol(currencySymbols[e.value]);
setAppData("currency", e.value);
}
}}
/>
</div>
<div className="mt-2 w-60">
<label className="text-default block text-sm font-medium" htmlFor="currency">
<div className="mt-4 w-60">
<label className="text-default mb-1 block text-sm font-medium" htmlFor="currency">
Payment option
</label>
<Select<Option>

View File

@ -24,4 +24,38 @@ export const currencyOptions = [
{ label: "Swedish krona", value: "SEK" },
{ label: "Swiss franc", value: "CHF" },
{ label: "Thai baht", value: "THB" },
];
] as const;
type CurrencyCode = (typeof currencyOptions)[number]["value"];
export const currencySymbols: Record<CurrencyCode, string> = {
USD: "$",
AUD: "$",
BRL: "R$",
CAD: "$",
CNY: "¥",
CZK: "Kč",
DKK: "kr",
EUR: "€",
HKD: "$",
HUF: "Ft",
ILS: "₪",
JPY: "¥",
MYR: "RM",
MXN: "$",
TWD: "$",
NZD: "$",
NOK: "kr",
PHP: "₱",
PLN: "zł",
GBP: "£",
RUB: "₽",
SGD: "$",
SEK: "kr",
CHF: "Fr",
THB: "฿",
};
export function isAcceptedCurrencyCode(currencyCode: string): currencyCode is CurrencyCode {
return Object.keys(currencySymbols).includes(currencyCode);
}

View File

@ -22,7 +22,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
const pathname = usePathname();
const { getAppData, setAppData, disabled } = useAppContextWithSchema<typeof appDataSchema>();
const price = getAppData("price");
const currency = getAppData("currency");
const currency = getAppData("currency") || currencyOptions[0].value;
const [selectedCurrency, setSelectedCurrency] = useState(
currencyOptions.find((c) => c.value === currency) || {
label: currencyOptions[0].label,
@ -73,8 +73,30 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
)}
{!recurringEventDefined && requirePayment && (
<>
<div className="mt-4 block items-center justify-start sm:flex sm:space-x-2">
<TextField
data-testid="price-input-stripe"
label={t("price")}
className="h-[38px]"
addOnLeading={
<>{selectedCurrency.value ? getCurrencySymbol("en", selectedCurrency.value) : ""}</>
}
addOnSuffix={currency.toUpperCase()}
addOnClassname="h-[38px]"
step="0.01"
min="0.5"
type="number"
required
placeholder="Price"
disabled={disabled}
onChange={(e) => {
setAppData("price", convertToSmallestCurrencyUnit(Number(e.target.value), currency));
}}
value={price > 0 ? convertFromSmallestToPresentableCurrencyUnit(price, currency) : undefined}
/>
</div>
<div className="mt-5 w-60">
<label className="text-default block text-sm font-medium" htmlFor="currency">
<label className="text-default mb-1 block text-sm font-medium" htmlFor="currency">
{t("currency")}
</label>
<Select
@ -92,26 +114,10 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
}}
/>
</div>
<div className="mt-4 block items-center justify-start sm:flex sm:space-x-2">
<TextField
data-testid="price-input-stripe"
label={t("price")}
className="h-[38px]"
addOnLeading={
<>{selectedCurrency.value ? getCurrencySymbol("en", selectedCurrency.value) : ""}</>
}
addOnClassname="h-[38px]"
step="0.01"
min="0.5"
type="number"
required
placeholder="Price"
disabled={disabled}
onChange={(e) => {
setAppData("price", convertToSmallestCurrencyUnit(Number(e.target.value), currency));
}}
value={price > 0 ? convertFromSmallestToPresentableCurrencyUnit(price, currency) : undefined}
/>
<div className="mt-4 w-60">
<label className="text-default mb-1 block text-sm font-medium" htmlFor="currency">
Payment option
</label>
<Select<Option>
defaultValue={
paymentOptionSelectValue
@ -128,6 +134,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
isDisabled={seatsEnabled || disabled}
/>
</div>
{seatsEnabled && paymentOption === "HOLD" && (
<Alert className="mt-2" severity="warning" title={t("seats_and_no_show_fee_error")} />
)}