summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/helpers/formValidation.js106
-rw-r--r--src/pages/my/address/create.js178
-rw-r--r--src/pages/shop/cart.js239
-rw-r--r--src/pages/shop/checkout.js9
-rw-r--r--src/styles/globals.css13
5 files changed, 361 insertions, 184 deletions
diff --git a/src/helpers/formValidation.js b/src/helpers/formValidation.js
new file mode 100644
index 00000000..10c36e01
--- /dev/null
+++ b/src/helpers/formValidation.js
@@ -0,0 +1,106 @@
+import { useCallback, useEffect, useState } from "react";
+
+const validateForm = (data, queries, hasChangedInputs = null) => {
+ let result = { valid: true, errors: {} };
+
+ for (const query in queries) {
+ if (!hasChangedInputs || (hasChangedInputs && hasChangedInputs[query])) {
+ const value = data[query];
+ const rules = queries[query];
+ let errors = [];
+ let label = null;
+ for (const rule of rules) {
+ let emailValidationRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ if (rule.startsWith('label:')) {
+ label = rule.replace('label:', '');
+ } else if (rule === 'required' && !value) {
+ errors.push('tidak boleh kosong');
+ } else if (rule === 'email' && !value.match(emailValidationRegex)) {
+ errors.push('harus format johndoe@example.com');
+ } else if (rule.startsWith('maxLength:')) {
+ let maxLength = parseInt(rule.replace('maxLength:', ''));
+ if (value && value.length > maxLength) errors.push(`maksimal ${maxLength} karakter`);
+ }
+ }
+ if (errors.length > 0) {
+ result.errors[query] = (label || query) + ' ' + errors.join(', ');
+ }
+ }
+ }
+
+ if (Object.keys(result.errors).length > 0) {
+ result.valid = false;
+ }
+
+ return result;
+}
+
+const useFormValidation = ({ initialFormValue = {}, validationScheme = {} }) => {
+ const [ formInputs, setFormInputs ] = useState(initialFormValue);
+ const [ formErrors, setFormErrors ] = useState({});
+ const [ formValidation ] = useState(validationScheme);
+ const [ hasChangedInputs, setHasChangedInputs ] = useState({});
+
+ const handleFormSubmit = (event, func) => {
+ if (event) {
+ event.preventDefault();
+
+ // Make all input to be has changed mode to revalidate
+ const changedInputs = {};
+ for (const key in formInputs) changedInputs[key] = true;
+ setHasChangedInputs(changedInputs);
+
+ const { valid, errors } = validateForm(formInputs, formValidation, changedInputs);
+ setFormErrors(errors);
+
+ if (valid) func();
+ }
+ };
+
+ const setChangedInput = (name, value = true) => {
+ setHasChangedInputs((hasChangedInputs) => ({
+ ...hasChangedInputs,
+ [name]: value
+ }));
+ };
+
+ const handleInputChange = (event) => {
+ setFormInputs((formInputs) => ({
+ ...formInputs,
+ [event.target.name]: event.target.value
+ }));
+ setChangedInput(event.target.name);
+ };
+
+ const handleSelectChange = useCallback((name, value) => {
+ setFormInputs((formInputs) => ({
+ ...formInputs,
+ [name]: value
+ }));
+ setChangedInput(name);
+ }, []);
+
+ const handleFormReset = () => {
+ setFormInputs(initialFormValue);
+ setFormErrors({});
+ setHasChangedInputs({});
+ }
+
+ useEffect(() => {
+ if (formInputs) {
+ const { errors } = validateForm(formInputs, formValidation, hasChangedInputs);
+ setFormErrors(errors);
+ }
+ }, [ formInputs, formValidation, hasChangedInputs ])
+
+ return {
+ handleFormReset,
+ handleFormSubmit,
+ handleInputChange,
+ handleSelectChange,
+ formInputs,
+ formErrors
+ };
+ };
+
+export default useFormValidation; \ No newline at end of file
diff --git a/src/pages/my/address/create.js b/src/pages/my/address/create.js
index 128da491..d68d1f76 100644
--- a/src/pages/my/address/create.js
+++ b/src/pages/my/address/create.js
@@ -4,23 +4,57 @@ import WithAuth from "../../../components/WithAuth";
import apiOdoo from "../../../helpers/apiOdoo";
import ReactSelect from "react-select";
import { useEffect, useState } from "react";
+import { useAuth } from "../../../helpers/auth";
+import useFormValidation from "../../../helpers/formValidation";
+import { toast } from "react-hot-toast";
+import { useRouter } from "next/router";
+
+const initialFormValue = {
+ type: null,
+ name: '',
+ email: '',
+ mobile: '',
+ street: '',
+ city: null,
+ district: null,
+ subDistrict: null,
+ zip: ''
+}
+
+const validationScheme = {
+ type: ['label:Label Alamat', 'required'],
+ name: ['label:Nama', 'required'],
+ email: ['label:Email', 'required', 'email'],
+ mobile: ['label:No. Handphone', 'required', 'maxLength:16'],
+ street: ['label:Alamat', 'required'],
+ city: ['label:Kota', 'required'],
+ zip: ['label:Kode Pos', 'required']
+};
export default function CreateAddress() {
+ const [auth] = useAuth();
+ const router = useRouter();
+
// Master Data
const [cities, setCities] = useState([]);
const [districts, setDistricts] = useState([]);
const [subDistricts, setSubDistricts] = useState([]);
// Input Data
- const [city, setCity] = useState(null);
- const [district, setDistrict] = useState(null);
- const [subDistrict, setSubDistrict] = useState(null);
- const [formValue, setFormValue] = useState({});
+ const {
+ formInputs,
+ formErrors,
+ handleInputChange,
+ handleSelectChange,
+ handleFormSubmit,
+ handleFormReset
+ } = useFormValidation({ validationScheme, initialFormValue });
useEffect(() => {
if (cities.length == 0) {
const loadCities = async () => {
let dataCities = await apiOdoo('GET', '/api/v1/city');
+ dataCities = dataCities.map((city) => ({ value: city.id, label: city.name }));
setCities(dataCities);
};
loadCities();
@@ -28,122 +62,168 @@ export default function CreateAddress() {
}, [cities]);
useEffect(() => {
- setDistrict(null);
- if (city) {
+ handleSelectChange('district', null);
+ if (formInputs.city) {
const loadDistricts = async () => {
- let dataDistricts = await apiOdoo('GET', `/api/v1/district?city_id=${city.id}`);
+ let dataDistricts = await apiOdoo('GET', `/api/v1/district?city_id=${formInputs.city.value}`);
+ dataDistricts = dataDistricts.map((district) => ({ value: district.id, label: district.name }));
setDistricts(dataDistricts);
};
loadDistricts();
}
- }, [city]);
+ }, [ formInputs.city, handleSelectChange ]);
useEffect(() => {
- setSubDistrict(null);
- if (district) {
+ handleSelectChange('subDistrict', null);
+ if (formInputs.district) {
const loadSubDistricts = async () => {
- let dataSubDistricts = await apiOdoo('GET', `/api/v1/sub_district?district_id=${district.id}`);
- console.log(dataSubDistricts);
+ let dataSubDistricts = await apiOdoo('GET', `/api/v1/sub_district?district_id=${formInputs.district.value}`);
+ dataSubDistricts = dataSubDistricts.map((subDistrict) => ({ value: subDistrict.id, label: subDistrict.name }));
setSubDistricts(dataSubDistricts);
};
loadSubDistricts();
}
- }, [district]);
-
- const handleChange = (e) => {
- setFormValue({
- ...formValue,
- [e.target.name]: e.target.value
- });
- };
+ }, [ formInputs.district, handleSelectChange ]);
+
+ const addressTypes = [
+ { value: 'contact', label: 'Contact Address' },
+ { value: 'invoice', label: 'Invoice Address' },
+ { value: 'delivery', label: 'Delivery Address' },
+ { value: 'other', label: 'Other Address' },
+ ];
+
+ const onSubmit = async () => {
+ const parameters = {
+ ...formInputs,
+ city_id: formInputs.city?.value,
+ district_id: formInputs.district?.value,
+ sub_district_id: formInputs.subDistrict?.value,
+ type: formInputs.type?.value,
+ user_id: auth.id,
+ partner_id: auth.partner_id,
+ };
+
+ const address = await apiOdoo('POST', '/api/v1/partner', parameters);
+ if (address?.id) {
+ handleFormReset();
+ toast.success('Berhasil menambahkan alamat');
+ router.push('/my/address');
+ }
+ }
return (
<WithAuth>
<Layout>
<AppBar title="Tambah Alamat" />
- <form className="px-4">
+ <form className="px-4 pb-4" onSubmit={(e) => handleFormSubmit(e, onSubmit)}>
<label className="form-label mt-4 mb-2">Label Alamat</label>
- <input
- type="text"
- className="form-input"
- placeholder="Invoice Address"
- name="name"
+ <ReactSelect
+ placeholder="Pilih label alamat..."
+ classNamePrefix="form-select"
+ options={addressTypes}
+ name="type"
+ onChange={(value) => handleSelectChange('type', value)}
+ value={formInputs?.type}
/>
+ <div className="text-caption-2 text-red_r-11 mt-1">{formErrors?.type}</div>
+
<label className="form-label mt-4 mb-2">Nama Kontak</label>
<input
type="text"
- className="form-input"
+ className='form-input'
placeholder="John Doe"
name="name"
+ onChange={handleInputChange}
+ value={formInputs.name}
+ aria-invalid={formErrors?.name}
/>
- <label className="form-label mt-4 mb-2">Email <span className="text-gray_r-11">(opsional)</span></label>
+ <div className="text-caption-2 text-red_r-11 mt-1">{formErrors?.name}</div>
+
+ <label className="form-label mt-4 mb-2">Email</label>
<input
type="text"
- className="form-input"
+ className='form-input'
placeholder="johndoe@gmail.com"
- name="name"
+ name="email"
+ value={formInputs.email}
+ onChange={handleInputChange}
+ aria-invalid={formErrors?.email}
/>
+ <div className="text-caption-2 text-red_r-11 mt-1">{formErrors?.email}</div>
+
<label className="form-label mt-4 mb-2">No. Handphone</label>
<input
type="tel"
- className="form-input"
+ className='form-input'
placeholder="08xxxxxxxx"
name="mobile"
+ value={formInputs.mobile}
+ onChange={handleInputChange}
+ aria-invalid={formErrors?.mobile}
/>
+ <div className="text-caption-2 text-red_r-11 mt-1">{formErrors?.mobile}</div>
+
<label className="form-label mt-4 mb-2">Alamat</label>
<input
type="text"
- className="form-input"
+ className='form-input'
placeholder="Jl. Bandengan Utara"
name="street"
+ value={formInputs.street}
+ onChange={handleInputChange}
+ aria-invalid={formErrors?.street}
/>
+ <div className="text-caption-2 text-red_r-11 mt-1">{formErrors?.street}</div>
+
<label className="form-label mt-4 mb-2">Kota</label>
<ReactSelect
placeholder="Pilih kota..."
classNamePrefix="form-select"
classNames={{ control: (state) => state.menuIsOpen || state.isFocused ? '!border-yellow_r-9' : '' }}
options={cities}
- getOptionLabel={e => e.name}
- getOptionValue={e => e.id}
- onChange={(value) => setCity(value)}
- value={city}
+ value={formInputs.city}
+ onChange={(value) => handleSelectChange('city', value)}
/>
+ <div className="text-caption-2 text-red_r-11 mt-1">{formErrors?.city}</div>
+
<label className="form-label mt-4 mb-2">Kecamatan <span className="text-gray_r-11">(opsional)</span></label>
<ReactSelect
placeholder="Pilih Kecamatan..."
classNamePrefix="form-select"
classNames={{ control: (state) => state.menuIsOpen || state.isFocused ? '!border-yellow_r-9' : '' }}
options={districts}
- getOptionLabel={e => e.name}
- getOptionValue={e => e.id}
- onChange={(value) => setDistrict(value)}
- value={district}
- isDisabled={!city}
+ value={formInputs.district}
+ onChange={(value) => handleSelectChange('district', value)}
+ isDisabled={!formInputs.city}
/>
+
<label className="form-label mt-4 mb-2">Kelurahan <span className="text-gray_r-11">(opsional)</span></label>
<ReactSelect
placeholder="Pilih Kelurahan..."
classNamePrefix="form-select"
classNames={{ control: (state) => state.menuIsOpen || state.isFocused ? '!border-yellow_r-9' : '' }}
options={subDistricts}
- getOptionLabel={e => e.name}
- getOptionValue={e => e.id}
- onChange={(value) => setSubDistrict(value)}
- value={subDistrict}
- isDisabled={!district}
+ onChange={(value) => handleSelectChange('subDistrict', value)}
+ value={formInputs.subDistrict}
+ isDisabled={!formInputs.district}
/>
+
<label className="form-label mt-4 mb-2">Kode Pos</label>
<input
type="number"
- className="form-input"
+ className='form-input'
placeholder="10100"
name="zip"
+ value={formInputs.zip}
+ onChange={handleInputChange}
+ aria-invalid={formErrors?.zip}
/>
+ <div className="text-caption-2 text-red_r-11 mt-1">{formErrors?.zip}</div>
<button
- type="button"
- className="btn-yellow float-right mt-6 w-full"
+ type="submit"
+ className="btn-yellow mt-6 w-full"
>
Simpan
</button>
diff --git a/src/pages/shop/cart.js b/src/pages/shop/cart.js
index cdb79178..0c6bbdc3 100644
--- a/src/pages/shop/cart.js
+++ b/src/pages/shop/cart.js
@@ -149,129 +149,6 @@ export default function Cart() {
setProducts([...productsToUpdate]);
}
- // Components
- const CartEmpty = () => (
- <div className="text-center mt-14">
- <ExclamationTriangleIcon className="w-12 mx-auto"/>
- <p className="mt-2 h2">Keranjang belanja anda masih kosong.</p>
- <Link href="/" className="btn-yellow text-gray_r-12 mx-auto mt-4">Mulai Belanja</Link>
- </div>
- );
-
- const CartLoader = () => (
- <div className="flex justify-center items-center gap-x-3 mt-14">
- <Spinner className="w-10 text-gray_r-8 fill-gray_r-12" />
- </div>
- );
-
- const CartWarningAlert = () => {
- <div className="p-4">
- <Alert type="warning" className="text-caption-2 flex gap-x-3 items-center">
- <div>
- <ExclamationCircleIcon className="w-8 text-yellow_r-11"/>
- </div>
- <span>Mohon dicek kembali & pastikan pesanan kamu sudah sesuai dengan yang kamu butuhkan. Atau bisa hubungi kami.</span>
- </Alert>
- </div>
- };
-
- const CartProductList = () => (
- <div className="p-4 flex flex-col gap-y-6">
- <div className="flex justify-between items-center">
- <h2>Daftar Produk Belanja</h2>
- <Link href="/" className="text-caption-1">Cari Produk Lain</Link>
- </div>
- {products.map((product, index) => (
- <div className="flex gap-x-3" key={index}>
- <div className="w-4/12 flex items-center gap-x-2" onClick={() => toggleProductSelected(product.id)}>
- <button
- className={'p-2 rounded border-2 ' + (product.selected ? 'border-yellow_r-9 bg-yellow_r-9' : 'border-gray_r-12')}
- ></button>
- <Image
- src={product.parent.image}
- alt={product.parent.name}
- className="object-contain object-center border border-gray_r-6 h-32 w-full rounded-md"
- />
- </div>
- <div className="w-8/12 flex flex-col">
- <Link href={'/shop/product/' + createSlug(product.parent.name, product.parent.id)} className="product-card__title wrap-line-ellipsis-2">
- {product.parent.name}
- </Link>
- <p className="text-caption-2 text-gray_r-11 mt-1">
- {product.code || '-'}
- {product.attributes.length > 0 ? ` | ${product.attributes.join(', ')}` : ''}
- </p>
- <div className="flex flex-wrap gap-x-1 items-center mb-2 mt-auto">
- <p className="text-caption-2 text-gray_r-12">{currencyFormat(product.price.price_discount)}</p>
- {product.price.discount_percentage > 0 && (
- <>
- <span className="badge-red">{product.price.discount_percentage}%</span>
- <p className="text-caption-2 text-gray_r-11 line-through">{currencyFormat(product.price.price)}</p>
- </>
- )}
- </div>
- <div className="flex items-center">
- <p className="mr-auto text-caption-2 text-gray_r-12 font-bold">{currencyFormat(product.quantity * product.price.price_discount)}</p>
- <div className="flex gap-x-2 items-center">
- <button
- className="btn-red p-2 rounded"
- onClick={() => showDeleteConfirmation(product.id)}
- >
- <TrashIcon className="text-red_r-11 w-3"/>
- </button>
- <button
- className="btn-light p-2 rounded"
- disabled={product.quantity == 1}
- onClick={() => minusQuantity(product.id)}
- >
- <MinusIcon className={'text-gray_r-12 w-3' + (product.quantity == 1 ? ' text-gray_r-11' : '')}/>
- </button>
- <input
- type="number"
- className="bg-transparent border-none w-6 text-center outline-none"
- onBlur={(e) => blurQuantity(product.id, e.target.value)}
- onChange={(e) => updateQuantity(product.id, e.target.value)}
- value={product.quantity}
- />
- <button className="btn-light p-2 rounded" onClick={() => plusQuantity(product.id)}>
- <PlusIcon className="text-gray_r-12 w-3"/>
- </button>
- </div>
- </div>
- </div>
- </div>
- ))}
- </div>
- );
-
- const ActionButton = () => (
- <div className="p-4 bg-gray_r-1 sticky bottom-0 border-t-4 border-gray_r-4">
- <div className="flex">
- <p>Total</p>
- <p className="text-gray_r-11 ml-1">{getProductsSelected().length > 0 && (
- <>({ getProductsSelected().length } Barang)</>
- )}</p>
- <p className="font-semibold text-red_r-11 ml-auto">{currencyFormat(totalPriceBeforeTax + totalTaxAmount - totalDiscountAmount)}</p>
- </div>
-
- <div className="flex gap-x-3 mt-4">
- <button
- className="flex-1 btn-light"
- disabled={getProductsSelected().length == 0}
- >
- Quotation {getProductsSelected().length > 0 && `(${getProductsSelected().length})`}
- </button>
- <button
- className="flex-1 btn-yellow"
- disabled={getProductsSelected().length == 0}
- onClick={() => router.push('/shop/checkout')}
- >
- Checkout {getProductsSelected().length > 0 && `(${getProductsSelected().length})`}
- </button>
- </div>
- </div>
- );
-
return (
<>
<ConfirmAlert
@@ -285,9 +162,19 @@ export default function Cart() {
<Layout>
<AppBar title="Keranjang Saya" />
- {isLoadingProducts && <CartLoader /> }
+ {isLoadingProducts && (
+ <div className="flex justify-center items-center gap-x-3 mt-14">
+ <Spinner className="w-10 text-gray_r-8 fill-gray_r-12" />
+ </div>
+ ) }
- { !isLoadingProducts && products.length == 0 && <CartEmpty /> }
+ { !isLoadingProducts && products.length == 0 && (
+ <div className="text-center mt-14">
+ <ExclamationTriangleIcon className="w-12 mx-auto"/>
+ <p className="mt-2 h2">Keranjang belanja anda masih kosong.</p>
+ <Link href="/" className="btn-yellow text-gray_r-12 mx-auto mt-4">Mulai Belanja</Link>
+ </div>
+ ) }
{ !isLoadingProducts && products.length > 0 && (
<>
@@ -298,13 +185,109 @@ export default function Cart() {
<LineDivider />
- <CartWarningAlert />
+ <div className="p-4">
+ <Alert type="warning" className="text-caption-2 flex gap-x-3 items-center">
+ <div>
+ <ExclamationCircleIcon className="w-8 text-yellow_r-11"/>
+ </div>
+ <span>Mohon dicek kembali & pastikan pesanan kamu sudah sesuai dengan yang kamu butuhkan. Atau bisa hubungi kami.</span>
+ </Alert>
+ </div>
<LineDivider />
- <CartProductList />
+ <div className="p-4 flex flex-col gap-y-6">
+ <div className="flex justify-between items-center">
+ <h2>Daftar Produk Belanja</h2>
+ <Link href="/" className="text-caption-1">Cari Produk Lain</Link>
+ </div>
+ {products.map((product, index) => (
+ <div className="flex gap-x-3" key={index}>
+ <div className="w-4/12 flex items-center gap-x-2" onClick={() => toggleProductSelected(product.id)}>
+ <button
+ className={'p-2 rounded border-2 ' + (product.selected ? 'border-yellow_r-9 bg-yellow_r-9' : 'border-gray_r-12')}
+ ></button>
+ <Image
+ src={product.parent.image}
+ alt={product.parent.name}
+ className="object-contain object-center border border-gray_r-6 h-32 w-full rounded-md"
+ />
+ </div>
+ <div className="w-8/12 flex flex-col">
+ <Link href={'/shop/product/' + createSlug(product.parent.name, product.parent.id)} className="product-card__title wrap-line-ellipsis-2">
+ {product.parent.name}
+ </Link>
+ <p className="text-caption-2 text-gray_r-11 mt-1">
+ {product.code || '-'}
+ {product.attributes.length > 0 ? ` | ${product.attributes.join(', ')}` : ''}
+ </p>
+ <div className="flex flex-wrap gap-x-1 items-center mb-2 mt-auto">
+ <p className="text-caption-2 text-gray_r-12">{currencyFormat(product.price.price_discount)}</p>
+ {product.price.discount_percentage > 0 && (
+ <>
+ <span className="badge-red">{product.price.discount_percentage}%</span>
+ <p className="text-caption-2 text-gray_r-11 line-through">{currencyFormat(product.price.price)}</p>
+ </>
+ )}
+ </div>
+ <div className="flex items-center">
+ <p className="mr-auto text-caption-2 text-gray_r-12 font-bold">{currencyFormat(product.quantity * product.price.price_discount)}</p>
+ <div className="flex gap-x-2 items-center">
+ <button
+ className="btn-red p-2 rounded"
+ onClick={() => showDeleteConfirmation(product.id)}
+ >
+ <TrashIcon className="text-red_r-11 w-3"/>
+ </button>
+ <button
+ className="btn-light p-2 rounded"
+ disabled={product.quantity == 1}
+ onClick={() => minusQuantity(product.id)}
+ >
+ <MinusIcon className={'text-gray_r-12 w-3' + (product.quantity == 1 ? ' text-gray_r-11' : '')}/>
+ </button>
+ <input
+ type="number"
+ className="bg-transparent border-none w-6 text-center outline-none"
+ onBlur={(e) => blurQuantity(product.id, e.target.value)}
+ onChange={(e) => updateQuantity(product.id, e.target.value)}
+ value={product.quantity}
+ />
+ <button className="btn-light p-2 rounded" onClick={() => plusQuantity(product.id)}>
+ <PlusIcon className="text-gray_r-12 w-3"/>
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
- <ActionButton />
+ <div className="p-4 bg-gray_r-1 sticky bottom-0 border-t-4 border-gray_r-4">
+ <div className="flex">
+ <p>Total</p>
+ <p className="text-gray_r-11 ml-1">{getProductsSelected().length > 0 && (
+ <>({ getProductsSelected().length } Barang)</>
+ )}</p>
+ <p className="font-semibold text-red_r-11 ml-auto">{currencyFormat(totalPriceBeforeTax + totalTaxAmount - totalDiscountAmount)}</p>
+ </div>
+
+ <div className="flex gap-x-3 mt-4">
+ <button
+ className="flex-1 btn-light"
+ disabled={getProductsSelected().length == 0}
+ >
+ Quotation {getProductsSelected().length > 0 && `(${getProductsSelected().length})`}
+ </button>
+ <button
+ className="flex-1 btn-yellow"
+ disabled={getProductsSelected().length == 0}
+ onClick={() => router.push('/shop/checkout')}
+ >
+ Checkout {getProductsSelected().length > 0 && `(${getProductsSelected().length})`}
+ </button>
+ </div>
+ </div>
</>
) }
</Layout>
diff --git a/src/pages/shop/checkout.js b/src/pages/shop/checkout.js
index 5eef98e5..54f93c44 100644
--- a/src/pages/shop/checkout.js
+++ b/src/pages/shop/checkout.js
@@ -30,7 +30,6 @@ export default function Checkout() {
const [totalPriceBeforeTax, setTotalPriceBeforeTax] = useState(0);
const [totalTaxAmount, setTotalTaxAmount] = useState(0);
const [totalDiscountAmount, setTotalDiscountAmount] = useState(0);
- const [isLoading, setIsLoading] = useState(true);
const [finishCheckout, setFinishCheckout] = useState(null);
const payments = [
@@ -106,10 +105,6 @@ export default function Checkout() {
}
}, [products]);
- useEffect(() => {
- if (addresses && products) setIsLoading(false);
- }, [addresses, products]);
-
const submit = async () => {
if (!selectedPayment) {
toast.error('Mohon pilih metode pembayaran terlebih dahulu', {
@@ -151,11 +146,11 @@ export default function Checkout() {
</div>
) : (
<>
- {isLoading && (
+ { !products && !addresses && (
<div className="flex justify-center items-center gap-x-3 mt-14">
<Spinner className="w-10 text-gray_r-8 fill-gray_r-12" />
</div>
- )}
+ ) }
{ products && addresses && (
<>
diff --git a/src/styles/globals.css b/src/styles/globals.css
index e90228bd..e223036b 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -115,6 +115,13 @@ html, body {
;
}
+ .form-input[aria-invalid] {
+ @apply
+ border-red_r-10
+ focus:border-red_r-10
+ ;
+ }
+
.btn-yellow,
.btn-light,
.btn-red {
@@ -400,4 +407,10 @@ html, body {
!shadow-none
!border-gray_r-7
;
+}
+
+.form-select__control--menu-is-open {
+ @apply
+ !border-yellow_r-9
+ ;
} \ No newline at end of file