Replace entire repo content with code from /root/shahikitchen-google/
This commit is contained in:
+280
-105
@@ -2,58 +2,106 @@
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { MessageCircle, X } from 'lucide-react';
|
||||
import type { FulfillmentMode } from '@/domain/cart/inquiry';
|
||||
import type {
|
||||
FulfillmentMode,
|
||||
InquiryBranch,
|
||||
InquiryPreferredDate,
|
||||
} from '@/domain/cart/inquiry';
|
||||
import type { DeliveryInquiryDetails, PickupInquiryDetails } from '@/domain/cart/inquiry';
|
||||
import {
|
||||
DEFAULT_INQUIRY_BRANCH,
|
||||
getMinInquiryTimeForToday,
|
||||
isInquiryBranchOnlineEnabled,
|
||||
validateInquirySchedule,
|
||||
} from '@/domain/cart/inquiry';
|
||||
import type { Language } from '@/domain/language/entities';
|
||||
import { getWhatsAppInquiryLanguage } from '@/application/messaging/inquiry-language';
|
||||
import DeliveryAddressAutocomplete from '@/components/DeliveryAddressAutocomplete';
|
||||
|
||||
export interface CartInquiryModalLabels {
|
||||
titlePickup: string;
|
||||
titleDelivery: string;
|
||||
signedInAs: string;
|
||||
nameLabel: string;
|
||||
namePlaceholder: string;
|
||||
phoneLabel: string;
|
||||
phonePlaceholder: string;
|
||||
emailLabel: string;
|
||||
emailPlaceholder: string;
|
||||
emailRequired: string;
|
||||
branchLabel: string;
|
||||
branchAskim: string;
|
||||
branchBackaplan: string;
|
||||
askimOnlineUnavailable: string;
|
||||
addressLabel: string;
|
||||
addressPlaceholder: string;
|
||||
addressHint: string;
|
||||
addressFallbackPlaceholder: string;
|
||||
addressFallbackHint: string;
|
||||
addressSearching: string;
|
||||
addressNoResults: string;
|
||||
addressOutsideGothenburg: string;
|
||||
addressSelectSuggestion: string;
|
||||
addressInvalid: string;
|
||||
preferredDateLabel: string;
|
||||
preferredDatePlaceholder: string;
|
||||
dateToday: string;
|
||||
dateTomorrow: string;
|
||||
preferredTimeLabel: string;
|
||||
preferredTimePlaceholder: string;
|
||||
scheduleOptionalHint: string;
|
||||
submitPickup: string;
|
||||
submitDelivery: string;
|
||||
cancel: string;
|
||||
close: string;
|
||||
nameRequired: string;
|
||||
phoneRequired: string;
|
||||
addressRequired: string;
|
||||
timeRequired: string;
|
||||
branchRequired: string;
|
||||
scheduleIncomplete: string;
|
||||
scheduleTooSoon: string;
|
||||
}
|
||||
|
||||
interface CartInquiryModalProps {
|
||||
mode: FulfillmentMode;
|
||||
isOpen: boolean;
|
||||
language: Language;
|
||||
customerEmail: string | null;
|
||||
customerName: string | null;
|
||||
onClose: () => void;
|
||||
onSubmitPickup: (details: PickupInquiryDetails) => void;
|
||||
onSubmitDelivery: (details: DeliveryInquiryDetails) => void;
|
||||
labels: CartInquiryModalLabels;
|
||||
}
|
||||
|
||||
const emptyPickup = (): PickupInquiryDetails => ({ name: '', phone: '' });
|
||||
const emptyDelivery = (): DeliveryInquiryDetails => ({
|
||||
name: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
preferredTime: '',
|
||||
});
|
||||
function createPickupDefaults(email = '', name = ''): PickupInquiryDetails {
|
||||
return {
|
||||
name: name.trim(),
|
||||
email,
|
||||
branch: DEFAULT_INQUIRY_BRANCH,
|
||||
preferredDate: '',
|
||||
preferredTime: '',
|
||||
};
|
||||
}
|
||||
|
||||
function createDeliveryDefaults(email = '', name = ''): DeliveryInquiryDetails {
|
||||
return {
|
||||
...createPickupDefaults(email, name),
|
||||
address: '',
|
||||
};
|
||||
}
|
||||
|
||||
export default function CartInquiryModal({
|
||||
mode,
|
||||
isOpen,
|
||||
language,
|
||||
customerEmail,
|
||||
customerName,
|
||||
onClose,
|
||||
onSubmitPickup,
|
||||
onSubmitDelivery,
|
||||
labels,
|
||||
}: CartInquiryModalProps) {
|
||||
const [pickup, setPickup] = useState(emptyPickup);
|
||||
const [delivery, setDelivery] = useState(emptyDelivery);
|
||||
const [pickup, setPickup] = useState(createPickupDefaults);
|
||||
const [delivery, setDelivery] = useState(createDeliveryDefaults);
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const [addressValid, setAddressValid] = useState(true);
|
||||
const autocompleteLang = getWhatsAppInquiryLanguage(language);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
@@ -67,20 +115,86 @@ export default function CartInquiryModal({
|
||||
setErrors({});
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [isOpen, handleKeyDown, mode]);
|
||||
}, [isOpen, handleKeyDown]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setPickup(emptyPickup());
|
||||
setDelivery(emptyDelivery());
|
||||
setPickup(createPickupDefaults());
|
||||
setDelivery(createDeliveryDefaults());
|
||||
setErrors({});
|
||||
setAddressValid(true);
|
||||
return;
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const email = customerEmail ?? '';
|
||||
const name = customerName ?? '';
|
||||
setPickup(createPickupDefaults(email, name));
|
||||
setDelivery(createDeliveryDefaults(email, name));
|
||||
setErrors({});
|
||||
setAddressValid(true);
|
||||
}, [isOpen, customerEmail, customerName]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const title = mode === 'pickup' ? labels.titlePickup : labels.titleDelivery;
|
||||
const submitLabel = mode === 'pickup' ? labels.submitPickup : labels.submitDelivery;
|
||||
const formState = mode === 'pickup' ? pickup : delivery;
|
||||
const branch = formState.branch;
|
||||
const preferredDate = formState.preferredDate;
|
||||
const preferredTime = formState.preferredTime;
|
||||
const isAskimBlocked = branch === 'askim';
|
||||
const isSubmitDisabled = !isInquiryBranchOnlineEnabled(branch);
|
||||
const minTimeToday = getMinInquiryTimeForToday();
|
||||
const emailLocked = Boolean(customerEmail);
|
||||
|
||||
const setName = (value: string) => {
|
||||
if (mode === 'pickup') setPickup((p) => ({ ...p, name: value }));
|
||||
else setDelivery((d) => ({ ...d, name: value }));
|
||||
if (errors.name) setErrors((err) => ({ ...err, name: '' }));
|
||||
};
|
||||
|
||||
const setEmail = (value: string) => {
|
||||
if (emailLocked) return;
|
||||
if (mode === 'pickup') setPickup((p) => ({ ...p, email: value }));
|
||||
else setDelivery((d) => ({ ...d, email: value }));
|
||||
if (errors.email) setErrors((err) => ({ ...err, email: '' }));
|
||||
};
|
||||
|
||||
const setBranch = (value: InquiryBranch) => {
|
||||
if (mode === 'pickup') setPickup((p) => ({ ...p, branch: value }));
|
||||
else setDelivery((d) => ({ ...d, branch: value }));
|
||||
if (errors.branch) setErrors((err) => ({ ...err, branch: '' }));
|
||||
};
|
||||
|
||||
const setPreferredDate = (value: InquiryPreferredDate) => {
|
||||
if (mode === 'pickup') {
|
||||
setPickup((p) => ({ ...p, preferredDate: value }));
|
||||
} else {
|
||||
setDelivery((d) => ({ ...d, preferredDate: value }));
|
||||
}
|
||||
if (errors.schedule) setErrors((err) => ({ ...err, schedule: '' }));
|
||||
};
|
||||
|
||||
const setPreferredTime = (value: string) => {
|
||||
if (mode === 'pickup') {
|
||||
setPickup((p) => ({ ...p, preferredTime: value }));
|
||||
} else {
|
||||
setDelivery((d) => ({ ...d, preferredTime: value }));
|
||||
}
|
||||
if (errors.schedule) setErrors((err) => ({ ...err, schedule: '' }));
|
||||
};
|
||||
|
||||
const validateSchedule = (state: PickupInquiryDetails): boolean => {
|
||||
const result = validateInquirySchedule(state);
|
||||
if (result === 'ok') return true;
|
||||
|
||||
if (result === 'incomplete') {
|
||||
setErrors((err) => ({ ...err, schedule: labels.scheduleIncomplete }));
|
||||
} else if (result === 'too_soon' || result === 'invalid_time') {
|
||||
setErrors((err) => ({ ...err, schedule: labels.scheduleTooSoon }));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -88,28 +202,35 @@ export default function CartInquiryModal({
|
||||
|
||||
if (mode === 'pickup') {
|
||||
if (!pickup.name.trim()) nextErrors.name = labels.nameRequired;
|
||||
if (!pickup.phone.trim()) nextErrors.phone = labels.phoneRequired;
|
||||
if (!pickup.email.trim()) nextErrors.email = labels.emailRequired;
|
||||
if (!pickup.branch) nextErrors.branch = labels.branchRequired;
|
||||
if (Object.keys(nextErrors).length) {
|
||||
setErrors(nextErrors);
|
||||
return;
|
||||
}
|
||||
if (!validateSchedule(pickup)) return;
|
||||
if (!isInquiryBranchOnlineEnabled(pickup.branch)) return;
|
||||
onSubmitPickup(pickup);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!delivery.name.trim()) nextErrors.name = labels.nameRequired;
|
||||
if (!delivery.phone.trim()) nextErrors.phone = labels.phoneRequired;
|
||||
if (!delivery.address.trim()) nextErrors.address = labels.addressRequired;
|
||||
if (!delivery.preferredTime.trim()) nextErrors.preferredTime = labels.timeRequired;
|
||||
if (!delivery.email.trim()) nextErrors.email = labels.emailRequired;
|
||||
if (!delivery.branch) nextErrors.branch = labels.branchRequired;
|
||||
if (delivery.address.trim() && !addressValid) {
|
||||
nextErrors.address = labels.addressInvalid;
|
||||
}
|
||||
if (Object.keys(nextErrors).length) {
|
||||
setErrors(nextErrors);
|
||||
return;
|
||||
}
|
||||
if (!validateSchedule(delivery)) return;
|
||||
if (!isInquiryBranchOnlineEnabled(delivery.branch)) return;
|
||||
onSubmitDelivery(delivery);
|
||||
};
|
||||
|
||||
const inputClass =
|
||||
'mt-1.5 w-full rounded-xl border border-[#EDE6D9] bg-[#FFFCF7] px-3 py-2.5 text-sm text-[#2C2A26] placeholder:text-[#8A8478] focus:border-[#c99a2e] focus:ring-2 focus:ring-[#c99a2e]/20';
|
||||
'mt-1.5 w-full min-h-[48px] rounded-xl border border-[#EDE6D9] bg-[#FFFCF7] px-3 py-3 text-base text-[#2C2A26] placeholder:text-[#8A8478] focus:border-[#c99a2e] focus:ring-2 focus:ring-[#c99a2e]/20 touch-manipulation';
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -126,37 +247,47 @@ export default function CartInquiryModal({
|
||||
aria-labelledby="cart-inquiry-title"
|
||||
>
|
||||
<div
|
||||
className="pointer-events-auto w-full sm:max-w-md max-h-[90dvh] overflow-y-auto rounded-t-3xl sm:rounded-2xl bg-[#FFFCF7] border border-[#EDE6D9] shadow-2xl"
|
||||
className="pointer-events-auto flex max-h-[92dvh] w-full flex-col overflow-hidden rounded-t-3xl border border-[#EDE6D9] bg-[#FFFCF7] shadow-2xl sm:max-w-md sm:rounded-2xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-[#EDE6D9] px-6 py-4">
|
||||
<h2 id="cart-inquiry-title" className="font-serif text-xl tracking-[-0.3px] text-[#101724]">
|
||||
<div className="flex shrink-0 items-center justify-between border-b border-[#EDE6D9] px-4 py-3.5 sm:px-6 sm:py-4">
|
||||
<h2 id="cart-inquiry-title" className="pe-3 font-serif text-lg tracking-[-0.3px] text-[#101724] sm:text-xl">
|
||||
{title}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label={labels.close}
|
||||
className="flex h-9 w-9 items-center justify-center rounded-full border border-[#EDE6D9] bg-white text-[#6B665F] hover:text-[#101724]"
|
||||
className="flex h-11 w-11 items-center justify-center rounded-full border border-[#EDE6D9] bg-white text-[#6B665F] hover:text-[#101724] touch-manipulation"
|
||||
>
|
||||
<X className="h-4 w-4" aria-hidden />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4 p-6">
|
||||
<form onSubmit={handleSubmit} className="flex min-h-0 flex-1 flex-col">
|
||||
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto overscroll-y-contain px-4 py-4 sm:px-6 sm:py-5">
|
||||
{customerEmail && (
|
||||
<div className="flex items-center gap-3 rounded-2xl border border-[#c99a2e]/25 bg-gradient-to-r from-[#FFF6DC] to-[#FFFCF7] px-3 py-3 sm:px-4">
|
||||
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-[#101724] text-xs font-bold text-white">
|
||||
{customerEmail.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wide text-[#8f6b22]">
|
||||
{labels.signedInAs}
|
||||
</p>
|
||||
<p className="truncate text-sm font-medium text-[#101724]">{customerEmail}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-medium tracking-wide text-[#2C2A26]">
|
||||
{labels.nameLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={mode === 'pickup' ? pickup.name : delivery.name}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
if (mode === 'pickup') setPickup((p) => ({ ...p, name: v }));
|
||||
else setDelivery((d) => ({ ...d, name: v }));
|
||||
if (errors.name) setErrors((err) => ({ ...err, name: '' }));
|
||||
}}
|
||||
value={formState.name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder={labels.namePlaceholder}
|
||||
className={inputClass}
|
||||
autoComplete="name"
|
||||
@@ -168,83 +299,127 @@ export default function CartInquiryModal({
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-medium tracking-wide text-[#2C2A26]">
|
||||
{labels.phoneLabel}
|
||||
{labels.emailLabel}
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
value={mode === 'pickup' ? pickup.phone : delivery.phone}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
if (mode === 'pickup') setPickup((p) => ({ ...p, phone: v }));
|
||||
else setDelivery((d) => ({ ...d, phone: v }));
|
||||
if (errors.phone) setErrors((err) => ({ ...err, phone: '' }));
|
||||
}}
|
||||
placeholder={labels.phonePlaceholder}
|
||||
className={inputClass}
|
||||
autoComplete="tel"
|
||||
type="email"
|
||||
value={formState.email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder={labels.emailPlaceholder}
|
||||
readOnly={emailLocked}
|
||||
className={`${inputClass} ${emailLocked ? 'bg-[#F8F5F0]/80 text-[#6B665F] cursor-default' : ''}`}
|
||||
autoComplete="email"
|
||||
/>
|
||||
{errors.phone && (
|
||||
<p className="mt-1 text-xs text-[#B45309]" role="alert">{errors.phone}</p>
|
||||
{errors.email && (
|
||||
<p className="mt-1 text-xs text-[#B45309]" role="alert">{errors.email}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-medium tracking-wide text-[#2C2A26]">
|
||||
{labels.branchLabel}
|
||||
</label>
|
||||
<select
|
||||
value={branch}
|
||||
onChange={(e) => setBranch(e.target.value as InquiryBranch)}
|
||||
className={`${inputClass} ${isAskimBlocked ? 'border-red-500 focus:border-red-500 focus:ring-red-500/20' : ''}`}
|
||||
>
|
||||
<option value="backaplan">{labels.branchBackaplan}</option>
|
||||
<option value="askim">{labels.branchAskim}</option>
|
||||
</select>
|
||||
{errors.branch && (
|
||||
<p className="mt-1 text-xs text-red-600" role="alert">{errors.branch}</p>
|
||||
)}
|
||||
{isAskimBlocked && (
|
||||
<p className="mt-1 text-xs font-medium text-red-600" role="alert">
|
||||
{labels.askimOnlineUnavailable}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{mode === 'delivery' && (
|
||||
<>
|
||||
<div>
|
||||
<label className="text-xs font-medium tracking-wide text-[#2C2A26]">
|
||||
{labels.addressLabel}
|
||||
</label>
|
||||
<textarea
|
||||
value={delivery.address}
|
||||
onChange={(e) => {
|
||||
setDelivery((d) => ({ ...d, address: e.target.value }));
|
||||
if (errors.address) setErrors((err) => ({ ...err, address: '' }));
|
||||
}}
|
||||
placeholder={labels.addressPlaceholder}
|
||||
rows={3}
|
||||
className={`${inputClass} resize-none`}
|
||||
/>
|
||||
{errors.address && (
|
||||
<p className="mt-1 text-xs text-[#B45309]" role="alert">{errors.address}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-medium tracking-wide text-[#2C2A26]">
|
||||
{labels.preferredTimeLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={delivery.preferredTime}
|
||||
onChange={(e) => {
|
||||
setDelivery((d) => ({ ...d, preferredTime: e.target.value }));
|
||||
if (errors.preferredTime) setErrors((err) => ({ ...err, preferredTime: '' }));
|
||||
}}
|
||||
placeholder={labels.preferredTimePlaceholder}
|
||||
className={inputClass}
|
||||
/>
|
||||
{errors.preferredTime && (
|
||||
<p className="mt-1 text-xs text-[#B45309]" role="alert">{errors.preferredTime}</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
<div>
|
||||
<label className="text-xs font-medium tracking-wide text-[#2C2A26]">
|
||||
{labels.addressLabel}
|
||||
</label>
|
||||
<DeliveryAddressAutocomplete
|
||||
value={delivery.address}
|
||||
onChange={(address) => setDelivery((d) => ({ ...d, address }))}
|
||||
onValidationChange={setAddressValid}
|
||||
language={autocompleteLang}
|
||||
labels={{
|
||||
placeholder: labels.addressPlaceholder,
|
||||
hint: labels.addressHint,
|
||||
fallbackPlaceholder: labels.addressFallbackPlaceholder,
|
||||
fallbackHint: labels.addressFallbackHint,
|
||||
searching: labels.addressSearching,
|
||||
noResults: labels.addressNoResults,
|
||||
outsideGothenburg: labels.addressOutsideGothenburg,
|
||||
selectSuggestion: labels.addressSelectSuggestion,
|
||||
}}
|
||||
inputClassName={inputClass}
|
||||
/>
|
||||
{errors.address && (
|
||||
<p className="mt-1 text-xs text-red-600" role="alert">{errors.address}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="flex-1 rounded-full border border-[#EDE6D9] py-3 text-sm font-medium text-[#6B665F] hover:bg-[#F8F5F0]"
|
||||
>
|
||||
{labels.cancel}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-primary flex flex-1 items-center justify-center gap-2 rounded-full py-3 text-sm font-medium tracking-wide"
|
||||
>
|
||||
<MessageCircle className="h-4 w-4" aria-hidden />
|
||||
{submitLabel}
|
||||
</button>
|
||||
<div className="rounded-2xl border border-[#EDE6D9] bg-[#FFFCF7]/80 p-4 space-y-4">
|
||||
<p className="text-xs text-[#6B665F]">{labels.scheduleOptionalHint}</p>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-medium tracking-wide text-[#2C2A26]">
|
||||
{labels.preferredDateLabel}
|
||||
</label>
|
||||
<select
|
||||
value={preferredDate}
|
||||
onChange={(e) => setPreferredDate(e.target.value as InquiryPreferredDate)}
|
||||
className={inputClass}
|
||||
>
|
||||
<option value="">{labels.preferredDatePlaceholder}</option>
|
||||
<option value="today">{labels.dateToday}</option>
|
||||
<option value="tomorrow">{labels.dateTomorrow}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-xs font-medium tracking-wide text-[#2C2A26]">
|
||||
{labels.preferredTimeLabel}
|
||||
</label>
|
||||
<input
|
||||
type="time"
|
||||
value={preferredTime}
|
||||
onChange={(e) => setPreferredTime(e.target.value)}
|
||||
min={preferredDate === 'today' ? minTimeToday : undefined}
|
||||
className={inputClass}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{errors.schedule && (
|
||||
<p className="text-xs text-red-600" role="alert">{errors.schedule}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 border-t border-[#EDE6D9] bg-[#FFFCF7]/95 px-4 py-4 pb-[max(1rem,env(safe-area-inset-bottom))] backdrop-blur-sm sm:px-6">
|
||||
<div className="flex flex-col-reverse gap-3 sm:flex-row">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="flex min-h-[52px] flex-1 items-center justify-center rounded-full border border-[#EDE6D9] py-3.5 text-sm font-medium text-[#6B665F] hover:bg-[#F8F5F0] touch-manipulation active:scale-[0.985]"
|
||||
>
|
||||
{labels.cancel}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitDisabled}
|
||||
className="btn-primary flex min-h-[52px] flex-1 items-center justify-center gap-2 rounded-full py-3.5 text-sm font-medium tracking-wide touch-manipulation active:scale-[0.985] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<MessageCircle className="h-4 w-4 shrink-0" aria-hidden />
|
||||
{submitLabel}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user