feat: replace mobile agent card slider with horizontal swipe carousel and improve messaging header responsiveness
This commit is contained in:
@@ -33,14 +33,14 @@ export function ConnectionCard({
|
|||||||
const isPlaceholder = !avatar || avatar === placeholder;
|
const isPlaceholder = !avatar || avatar === placeholder;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 py-4">
|
<div className="flex flex-row items-center gap-3 sm:gap-4 py-4">
|
||||||
{/* Avatar */}
|
{/* Avatar */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative bg-[#e8e8e8]">
|
<div className="w-[56px] h-[56px] sm:w-[80px] sm:h-[80px] rounded-full overflow-hidden relative bg-[#e8e8e8]">
|
||||||
{/* Shimmer while loading, initials only on error */}
|
{/* Shimmer while loading, initials only on error */}
|
||||||
{avatarError || isPlaceholder ? (
|
{avatarError || isPlaceholder ? (
|
||||||
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
<div className="absolute inset-0 rounded-full bg-[#c4d9d4] flex items-center justify-center">
|
||||||
<span className="font-bold text-[#00293d] text-[32px]">{name?.[0]?.toUpperCase() || '?'}</span>
|
<span className="font-bold text-[#00293d] text-[22px] sm:text-[32px]">{name?.[0]?.toUpperCase() || '?'}</span>
|
||||||
</div>
|
</div>
|
||||||
) : !avatarLoaded ? (
|
) : !avatarLoaded ? (
|
||||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
||||||
@@ -58,40 +58,37 @@ export function ConnectionCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content + Message button (stacks on mobile, inline on desktop) */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0 flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4">
|
||||||
{/* Name */}
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="font-serif font-bold text-[14px] leading-[19px] text-[#00293D] mb-1">
|
<h3 className="font-serif font-bold text-[14px] leading-[19px] text-[#00293D] mb-1 truncate">
|
||||||
{name}
|
{name}
|
||||||
</h3>
|
</h3>
|
||||||
|
<p className="font-serif font-normal text-[13px] leading-[18px] text-[#00293D]/70 mb-1 truncate">
|
||||||
{/* Email */}
|
{email}
|
||||||
<p className="font-serif font-normal text-[13px] leading-[18px] text-[#00293D]/70 mb-1">
|
|
||||||
{email}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Connected date */}
|
|
||||||
{connectedAt && (
|
|
||||||
<p className="font-serif font-normal text-[12px] text-[#00293D]/50">
|
|
||||||
Connected on {formatDate(connectedAt)}
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
{connectedAt && (
|
||||||
</div>
|
<p className="font-serif font-normal text-[12px] text-[#00293D]/50">
|
||||||
|
Connected on {formatDate(connectedAt)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Action Button */}
|
||||||
<div className="flex items-center gap-3 flex-shrink-0">
|
<div className="flex items-center gap-3 flex-shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => onMessage?.(id)}
|
onClick={() => onMessage?.(id)}
|
||||||
className="h-[27px] px-4 rounded-[15px] bg-[#e58625] font-fractul font-medium text-[14px] text-[#00293D] hover:bg-[#d47920] transition-colors cursor-pointer flex items-center gap-2"
|
className="h-[27px] px-4 rounded-[15px] bg-[#e58625] font-fractul font-medium text-[14px] text-[#00293D] hover:bg-[#d47920] transition-colors cursor-pointer flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
src="/assets/icons/message-dark-icon.svg"
|
src="/assets/icons/message-dark-icon.svg"
|
||||||
alt="Message"
|
alt="Message"
|
||||||
width={14}
|
width={14}
|
||||||
height={13}
|
height={13}
|
||||||
/>
|
/>
|
||||||
Message
|
Message
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -106,18 +106,30 @@ export function FeaturesSection({ content }: { content?: FeaturesContent }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mobile Agent Cards - Tab style with dots */}
|
{/* Mobile Agent Cards - Horizontal swipe carousel */}
|
||||||
{agents.length > 0 && (
|
{agents.length > 0 && (
|
||||||
<div className="lg:hidden">
|
<div className="lg:hidden">
|
||||||
<div className="flex justify-center">
|
<div
|
||||||
<AgentCard agent={agents[activeCard]} className="shadow-lg" />
|
className="flex gap-4 overflow-x-auto snap-x snap-mandatory scroll-smooth pb-2 -mx-4 px-4"
|
||||||
|
onScroll={(e) => {
|
||||||
|
const el = e.currentTarget;
|
||||||
|
const cardWidth = el.children[0]?.clientWidth || 254;
|
||||||
|
const gap = 16;
|
||||||
|
const idx = Math.round(el.scrollLeft / (cardWidth + gap));
|
||||||
|
if (idx !== activeCard) setActiveCard(idx);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{agents.map((agent, index) => (
|
||||||
|
<div key={index} className="snap-center flex-shrink-0">
|
||||||
|
<AgentCard agent={agent} className="shadow-lg" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
{/* Dot indicators */}
|
{/* Dot indicators (visual only — swipe to change) */}
|
||||||
<div className="flex justify-center gap-2 mt-4">
|
<div className="flex justify-center gap-2 mt-4">
|
||||||
{agents.map((_, index) => (
|
{agents.map((_, index) => (
|
||||||
<button
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => setActiveCard(index)}
|
|
||||||
className={`rounded-full transition-all ${
|
className={`rounded-full transition-all ${
|
||||||
index === activeCard
|
index === activeCard
|
||||||
? 'w-[10px] h-[10px] bg-[#00293d]'
|
? 'w-[10px] h-[10px] bg-[#00293d]'
|
||||||
|
|||||||
@@ -351,8 +351,7 @@ export function MessageInput({ onSend, onSendGif, onSendFile, onTypingStart, onT
|
|||||||
{showGifPicker && (
|
{showGifPicker && (
|
||||||
<div
|
<div
|
||||||
ref={gifPickerRef}
|
ref={gifPickerRef}
|
||||||
className="absolute bottom-full left-0 mb-2 z-50 bg-white border border-[#00293d]/10 rounded-[15px] shadow-lg overflow-hidden"
|
className="absolute bottom-full left-0 mb-2 z-50 bg-white border border-[#00293d]/10 rounded-[15px] shadow-lg overflow-hidden w-[min(360px,calc(100vw-32px))] h-[420px]"
|
||||||
style={{ width: '360px', height: '420px' }}
|
|
||||||
>
|
>
|
||||||
<div className="p-3 border-b border-[#00293d]/10">
|
<div className="p-3 border-b border-[#00293d]/10">
|
||||||
<input
|
<input
|
||||||
@@ -368,7 +367,7 @@ export function MessageInput({ onSend, onSendGif, onSendFile, onTypingStart, onT
|
|||||||
<Grid
|
<Grid
|
||||||
key={gifSearchQuery}
|
key={gifSearchQuery}
|
||||||
fetchGifs={fetchGifs}
|
fetchGifs={fetchGifs}
|
||||||
width={356}
|
width={typeof window !== 'undefined' ? Math.min(356, window.innerWidth - 40) : 356}
|
||||||
columns={2}
|
columns={2}
|
||||||
gutter={6}
|
gutter={6}
|
||||||
noLink
|
noLink
|
||||||
|
|||||||
@@ -665,7 +665,7 @@ export function MessagingPage({ initialConversationId, initialAgentProfileId }:
|
|||||||
|
|
||||||
<div className="border border-[#00293d]/10 rounded-[20px] bg-white overflow-hidden">
|
<div className="border border-[#00293d]/10 rounded-[20px] bg-white overflow-hidden">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center gap-4 p-4 border-b border-[#00293d]/10">
|
<div className="flex flex-wrap md:flex-nowrap items-center gap-3 md:gap-4 p-4 border-b border-[#00293d]/10">
|
||||||
<h1 className="font-fractul font-bold text-[18px] leading-[22px] text-[#00293D]">
|
<h1 className="font-fractul font-bold text-[18px] leading-[22px] text-[#00293D]">
|
||||||
Messaging
|
Messaging
|
||||||
</h1>
|
</h1>
|
||||||
@@ -678,8 +678,8 @@ export function MessagingPage({ initialConversationId, initialAgentProfileId }:
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search bar */}
|
{/* Search bar — full width below on mobile, inline on desktop */}
|
||||||
<div className="flex-1 max-w-[600px]">
|
<div className="order-3 md:order-none w-full md:w-auto md:flex-1 md:max-w-[600px]">
|
||||||
<div className="flex items-center gap-2 border border-[#00293d]/10 rounded-[15px] px-4 py-2">
|
<div className="flex items-center gap-2 border border-[#00293d]/10 rounded-[15px] px-4 py-2">
|
||||||
<Image
|
<Image
|
||||||
src="/assets/icons/search-icon.svg"
|
src="/assets/icons/search-icon.svg"
|
||||||
@@ -692,7 +692,7 @@ export function MessagingPage({ initialConversationId, initialAgentProfileId }:
|
|||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
placeholder="Search Message"
|
placeholder="Search Message"
|
||||||
className="flex-1 font-serif font-normal text-[18px] leading-[24px] text-[#00293D] placeholder-[#00293D]/50 focus:outline-none"
|
className="flex-1 min-w-0 font-serif font-normal text-[14px] md:text-[18px] leading-[24px] text-[#00293D] placeholder-[#00293D]/50 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export function FilterModal({ isOpen, onClose, filters, filterFields, onToggleFi
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{expandedSections[field.slug] && (
|
{expandedSections[field.slug] && (
|
||||||
<div className={`grid ${field.options.length > 6 ? 'grid-cols-3' : 'grid-cols-3'} gap-x-6 gap-y-3`}>
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-x-4 md:gap-x-6 gap-y-3">
|
||||||
{field.options.map((option) => (
|
{field.options.map((option) => (
|
||||||
<label key={option.value} className="flex items-start gap-2 cursor-pointer">
|
<label key={option.value} className="flex items-start gap-2 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -278,13 +278,13 @@ export function NotificationsForm({ onSave }: NotificationsFormProps) {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Frequency Tabs */}
|
{/* Frequency Tabs */}
|
||||||
<div className="inline-flex items-center border border-[#00293D]/10 rounded-[7px] h-[49px] px-2">
|
<div className="flex items-center border border-[#00293D]/10 rounded-[7px] h-[49px] px-2 w-full md:w-fit">
|
||||||
{frequencyOptions.map((option, index) => (
|
{frequencyOptions.map((option, index) => (
|
||||||
<div key={option.value} className="flex items-center h-full">
|
<div key={option.value} className="flex items-center h-full flex-1 md:flex-initial">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setDigestFrequency(option.value)}
|
onClick={() => setDigestFrequency(option.value)}
|
||||||
className={`px-5 py-1.5 text-[14px] font-fractul transition-colors rounded-[15px] ${
|
className={`w-full md:w-auto px-3 md:px-5 py-1.5 text-[14px] font-fractul transition-colors rounded-[15px] ${
|
||||||
digestFrequency === option.value
|
digestFrequency === option.value
|
||||||
? 'bg-[#e58625] text-[#00293D]'
|
? 'bg-[#e58625] text-[#00293D]'
|
||||||
: 'text-[#00293D] hover:bg-[#00293D]/5'
|
: 'text-[#00293D] hover:bg-[#00293D]/5'
|
||||||
|
|||||||
Reference in New Issue
Block a user