diff options
| author | Rafi Zadanly <zadanlyr@gmail.com> | 2023-01-12 17:12:31 +0700 |
|---|---|---|
| committer | Rafi Zadanly <zadanlyr@gmail.com> | 2023-01-12 17:12:31 +0700 |
| commit | 9aeb019257787b355f7b51f401d7f417899252f5 (patch) | |
| tree | d159c7da9195d4ad058eb0fb9cfa72b1e86baa30 /src | |
| parent | 3770bcca65e85af7273a3f565b3ca616f852e936 (diff) | |
form validation, fix cart, fix checkout, create address
Diffstat (limited to 'src')
| -rw-r--r-- | src/helpers/formValidation.js | 106 | ||||
| -rw-r--r-- | src/pages/my/address/create.js | 178 | ||||
| -rw-r--r-- | src/pages/shop/cart.js | 239 | ||||
| -rw-r--r-- | src/pages/shop/checkout.js | 9 | ||||
| -rw-r--r-- | src/styles/globals.css | 13 |
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 |
