| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- import * as React from 'react';
- import { AIGCVideo, ScheduledPost, SocialAccount } from '../types';
- import { Icon } from './ui/Icon';
- import { format, startOfMonth, endOfMonth, startOfWeek, endOfWeek, eachDayOfInterval, addMonths, subMonths, addWeeks, subWeeks, addDays, subDays, isSameMonth, isToday, parseISO, isPast, getDay, isSameDay } from 'date-fns';
- import { useTranslation } from '../hooks/useI18n';
- const initialSocialAccounts: SocialAccount[] = [
- {
- id: 'tw',
- platform: 'Twitter',
- username: 'greenpage_ai',
- icon: <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 fill-current text-[#1DA1F2]">
- <title>Twitter</title>
- <path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.223.085a4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z" />
- </svg>
- },
- {
- id: 'fb',
- platform: 'Facebook',
- username: 'GreenPageApp',
- icon: <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 fill-current text-[#1877F2]">
- <title>Facebook</title>
- <path d="M22.675 0H1.325C.593 0 0 .593 0 1.325v21.351C0 23.407.593 24 1.325 24H12.82v-9.294H9.692v-3.622h3.128V8.413c0-3.1 1.893-4.788 4.659-4.788 1.325 0 2.463.099 2.795.143v3.24l-1.918.001c-1.504 0-1.795.715-1.795 1.763v2.313h3.587l-.467 3.622h-3.12V24h6.116c.732 0 1.325-.593 1.325-1.325V1.325C24 .593 23.407 0 22.675 0z" />
- </svg>
- },
- {
- id: 'ig',
- platform: 'Instagram',
- username: 'greenpage.ai',
- icon: <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 fill-current">
- <title>Instagram</title>
- <defs>
- <radialGradient id="ig-grad" gradientUnits="userSpaceOnUse" r="150%" cx="30%" cy="107%">
- <stop stopColor="#fdf497" offset="0" />
- <stop stopColor="#fdf497" offset="0.05" />
- <stop stopColor="#fd5949" offset="0.45" />
- <stop stopColor="#d6249f" offset="0.6" />
- <stop stopColor="#285AEB" offset="0.9" />
- </radialGradient>
- </defs>
- <path fill="url(#ig-grad)" d="M12 0C8.74 0 8.333.015 7.053.072 5.775.132 4.905.333 4.14.63c-.784.3-.986.623-1.77.933-.448.174-.9.34-1.356.59-.446.243-.7.4-.933.71-.24.3-.466.737-.62 1.14-.156.4-.3 1.07-.343 1.76-.05.8-.07 1.37-.07 4.23s.02 3.43.07 4.23c.044.68.2 1.36.343 1.76.155.4.38.84.62 1.14.233.3.486.467.933.71.457.25.908.417 1.356.59.783.31 1.2.63 1.77.933.765.3 1.635.5 2.913.56.05.002.1.003.15.003.25 0 .5 0 .75-.004 1.28-.056 2.15-.26 2.913-.56.784-.3 1.2-.623 1.77-.933.448-.174-.9.34 1.356.59.446-.243-.7-.4.933-.71.24-.3.466.737-.62-1.14.156-.4.3-1.07.343-1.76.05-.8.07-1.37.07-4.23s-.02-3.43-.07-4.23c-.044-.68-.2-1.36-.343-1.76-.155-.4-.38-.84-.62-1.14-.233-.3-.486-.467-.933-.71-.457-.25-.908.417-1.356.59-.783-.31-1.2-.63-1.77-.933-.765-.3-1.635-.5-2.913-.56-.25-.003-.5-.004-.75-.004zm0 2.16c3.203 0 3.585.016 4.85.07 1.17.055 1.805.248 2.227.415.562.217.96.477 1.382.896.413.42.67.824.896 1.383.167.422.36 1.057.413 2.227.055 1.265.07 1.646.07 4.85s-.015 3.585-.07 4.85c-.055 1.17-.248 1.805-.413 2.227-.228.562-.483-.96-.896 1.383-.42.413-.824-.67-1.382-.896-.422-.167-1.057.36-2.227-.413-1.265.055-1.646.07-4.85.07s-3.585-.015-4.85-.07c-1.17-.055-1.805-.248-2.227-.413-.562-.228-.96-.483-1.382-.896-.413-.42-.67-.824-.896-1.383-.167-.422-.36-1.057-.413-2.227-.055-1.265-.07-1.646-.07-4.85s.015-3.585.07-4.85c.055-1.17.248 1.805.413-2.227.228.562.483.96.896-1.382.42-.413.824-.67 1.382-.896.422-.167 1.057.36 2.227-.413 1.265-.055 1.646-.07 4.85-.07zm0 3.27c-3.405 0-6.167 2.76-6.167 6.167s2.762 6.167 6.167 6.167 6.167-2.76 6.167-6.167-2.762-6.167-6.167-6.167zm0 10.167c-2.209 0-4-1.79-4-4s1.791-4 4-4 4 1.79 4 4-1.791 4-4 4zm4.865-9.865c0 .765-.623 1.385-1.39 1.385s-1.39-.62-1.39-1.385.623-1.385 1.39-1.385 1.39.62 1.39 1.385z" />
- </svg>
- },
- ];
- const CheckCircleIcon: React.FC<{ className?: string }> = ({ className }) => (
- <Icon className={className || "h-4 w-4 text-green-400"}><path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></Icon>
- );
- // --- MODAL COMPONENTS ---
- const SchedulePostModal: React.FC<{
- video: AIGCVideo | null;
- post: ScheduledPost | null;
- date: string;
- accounts: SocialAccount[];
- onClose: () => void;
- onSchedule: (post: Omit<ScheduledPost, 'id' | 'status'>) => void;
- onUpdate: (post: ScheduledPost) => void;
- onDelete: (postId: string) => void;
- }> = ({ video, post, date, accounts, onClose, onSchedule, onUpdate, onDelete }) => {
- const { t, dateLocale } = useTranslation();
- const [time, setTime] = React.useState(post ? post.time : '10:00');
- const [caption, setCaption] = React.useState(post ? post.caption : (video ? `Check out our new video: ${video.title}!` : ""));
- const [selectedAccounts, setSelectedAccounts] = React.useState<string[]>(post ? post.socialAccountIds : []);
- const handleAccountToggle = (accountId: string) => {
- setSelectedAccounts(prev =>
- prev.includes(accountId) ? prev.filter(id => id !== accountId) : [...prev, accountId]
- );
- };
- const handleSubmit = () => {
- if (selectedAccounts.length === 0 || !time) {
- alert('Please select at least one social account and set a time.');
- return;
- }
- if (post) {
- onUpdate({ ...post, time, caption, socialAccountIds: selectedAccounts });
- } else if (video) {
- onSchedule({ videoId: video.id, date, time, caption, socialAccountIds: selectedAccounts });
- }
- onClose();
- };
- if (!video) return null;
- return (
- <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50" onClick={() => onClose()}>
- <div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md" onClick={e => e.stopPropagation()}>
- <div className="p-6 border-b border-gray-200 dark:border-gray-700">
- <h3 className="text-lg font-bold text-gray-900 dark:text-white">{post ? t('aigc.scheduler.title') : t('aigc.scheduler.title')}</h3>
- <p className="text-sm text-gray-500 dark:text-gray-400">{`${t('for')} ${format(parseISO(date), 'MMMM d, yyyy', { locale: dateLocale })}`}</p>
- </div>
- <div className="p-6 space-y-4">
- <div className="flex items-center gap-4">
- <img src={video.thumbnailUrl} alt={video.title} className="w-32 h-20 rounded object-cover" />
- <p className="font-semibold text-gray-900 dark:text-white">{video.title}</p>
- </div>
- <div>
- <label htmlFor="time" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('aigc.scheduler.time')}</label>
- <input type="time" id="time" value={time} onChange={e => setTime(e.target.value)} className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600"/>
- </div>
- <div>
- <label htmlFor="caption" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{t('aigc.scheduler.caption')}</label>
- <textarea id="caption" rows={3} value={caption} onChange={e => setCaption(e.target.value)} className="w-full bg-gray-100 dark:bg-gray-700 p-2 rounded-md border border-gray-300 dark:border-gray-600" />
- </div>
- <div>
- <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{t('aigc.scheduler.post_to')}</label>
- <div className="space-y-2">
- {accounts.map(acc => (
- <label key={acc.id} className="flex items-center gap-3 p-2 bg-gray-100 dark:bg-gray-700 rounded-md cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600">
- <input type="checkbox" className="h-4 w-4 rounded bg-gray-200 dark:bg-gray-900 border-gray-300 dark:border-gray-600 text-brand-primary focus:ring-brand-secondary" checked={selectedAccounts.includes(acc.id)} onChange={() => handleAccountToggle(acc.id)} />
- {acc.icon}
- <span className="font-semibold">{acc.platform}</span>
- <span className="text-gray-500 dark:text-gray-400 text-sm ml-auto">@{acc.username}</span>
- </label>
- ))}
- </div>
- </div>
- </div>
- <div className={`p-4 bg-gray-50 dark:bg-gray-900/50 flex ${post ? 'justify-between' : 'justify-end'} gap-3`}>
- {post && <button onClick={() => { onDelete(post.id); onClose(); }} className="px-4 py-2 rounded-md text-sm font-semibold text-red-600 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/50">{t('delete')}</button>}
- <div className="flex gap-3">
- <button onClick={onClose} className="px-4 py-2 rounded-md text-sm font-semibold text-gray-700 dark:text-gray-300 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600">{t('cancel')}</button>
- <button onClick={handleSubmit} className="px-4 py-2 rounded-md text-sm font-semibold text-white bg-brand-primary hover:bg-brand-secondary">{post ? t('update') : t('aigc.scheduler.schedule')}</button>
- </div>
- </div>
- </div>
- </div>
- );
- };
- const VideoLibrarySidebar: React.FC<{
- videos: AIGCVideo[];
- onDragStart: (e: React.DragEvent, videoId: string) => void;
- activeFilter: 'all' | 'scheduled' | 'unscheduled';
- onFilterChange: (filter: 'all' | 'scheduled' | 'unscheduled') => void;
- }> = ({ videos, onDragStart, activeFilter, onFilterChange }) => {
- const { t } = useTranslation();
- return (
- <aside className="w-80 bg-white dark:bg-gray-800 p-4 flex flex-col border-r border-gray-200 dark:border-gray-700">
- <h3 className="text-lg font-bold text-gray-900 dark:text-white mb-4">{t('aigc.scheduler.video_library')}</h3>
- <div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-900 p-1 rounded-lg mb-4">
- {(['all', 'scheduled', 'unscheduled'] as const).map(filter => (
- <button
- key={filter}
- onClick={() => onFilterChange(filter)}
- className={`flex-1 px-3 py-1 text-sm rounded-md capitalize transition-colors ${activeFilter === filter ? 'bg-brand-primary text-white shadow' : 'hover:bg-gray-200 dark:hover:bg-gray-600'}`}>
- {t(filter)}
- </button>
- ))}
- </div>
- <div className="flex-1 overflow-y-auto pr-2 -mr-4 space-y-3">
- {videos.map(video => (
- <div key={video.id} draggable onDragStart={e => onDragStart(e, video.id)} className="flex items-center gap-3 p-2 bg-gray-50 dark:bg-gray-900/50 rounded-lg cursor-grab active:cursor-grabbing">
- <img src={video.thumbnailUrl} alt={video.title} className="w-24 h-16 rounded object-cover" />
- <p className="flex-1 font-semibold text-sm text-gray-900 dark:text-white truncate">{video.title}</p>
- </div>
- ))}
- {videos.length === 0 && <p className="text-center text-sm text-gray-400 dark:text-gray-500 py-10">{t('aigc.scheduler.no_videos_filter')}</p>}
- </div>
- </aside>
- );
- };
- // --- CALENDAR COMPONENTS ---
- const CalendarHeader: React.FC<{ view: string; currentDate: Date; onPrev: () => void; onNext: () => void; onViewChange: (view: 'month' | 'week' | 'day') => void; }> = ({ view, currentDate, onPrev, onNext, onViewChange }) => {
- const { dateLocale } = useTranslation();
- const formatString = view === 'month' ? 'MMMM yyyy' : (view === 'week' ? "MMM d, yyyy" : 'MMMM d, yyyy');
- return (
- <div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
- <div>
- <h2 className="text-xl font-bold text-gray-900 dark:text-white">{format(currentDate, formatString, { locale: dateLocale })}</h2>
- {view === 'week' && <p className="text-sm text-gray-500 dark:text-gray-400">{`Week of ${format(startOfWeek(currentDate, { locale: dateLocale }), 'MMM d')} - ${format(endOfWeek(currentDate, { locale: dateLocale }), 'MMM d')}`}</p>}
- </div>
- <div className="flex items-center gap-4">
- <div className="flex items-center">
- <button onClick={onPrev} className="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"><Icon className="h-5 w-5"><path d="M15 19l-7-7 7-7" /></Icon></button>
- <button onClick={onNext} className="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"><Icon className="h-5 w-5"><path d="M9 5l7 7-7 7" /></Icon></button>
- </div>
- <div className="flex items-center gap-1 bg-gray-200 dark:bg-gray-700 p-1 rounded-lg">
- {(['month', 'week', 'day'] as const).map(v => (
- <button key={v} onClick={() => onViewChange(v)} className={`px-3 py-1 text-sm rounded-md capitalize ${view === v ? 'bg-brand-primary text-white' : 'hover:bg-gray-300 dark:hover:bg-gray-600'}`}>{v}</button>
- ))}
- </div>
- </div>
- </div>
- );
- };
- const ScheduledPostItem: React.FC<{ post: ScheduledPost; video: AIGCVideo | undefined; onClick: (e: React.MouseEvent) => void; }> = ({ post, video, onClick }) => {
- if (!video) return null;
- const postDate = parseISO(`${post.date}T${post.time}`);
- const isDistributed = post.status === 'distributed' || isPast(postDate);
- return (
- <button onClick={onClick} className={`w-full text-left p-2 rounded-md mb-1 ${isDistributed ? 'bg-green-500/10 hover:bg-green-500/20' : 'bg-blue-500/10 hover:bg-blue-500/20'}`}>
- <div className="flex items-center gap-2">
- {isDistributed ? <CheckCircleIcon className="h-4 w-4 text-green-400" /> : <div className="h-4 w-4" />}
- <p className={`text-xs font-semibold ${isDistributed ? 'text-green-600 dark:text-green-400' : 'text-blue-600 dark:text-blue-400'}`}>{post.time}</p>
- </div>
- <p className="text-sm font-semibold truncate mt-1 text-gray-800 dark:text-gray-200">{video.title}</p>
- </button>
- );
- };
- const Calendar: React.FC<{
- view: 'month' | 'week' | 'day';
- currentDate: Date;
- schedule: ScheduledPost[];
- videos: AIGCVideo[];
- onDateSelect: (date: string) => void;
- onPostSelect: (post: ScheduledPost) => void;
- onDrop: (e: React.DragEvent, date: string) => void;
- }> = ({ view, currentDate, schedule, videos, onDateSelect, onPostSelect, onDrop }) => {
- const { dateLocale } = useTranslation();
- const [dragOverDate, setDragOverDate] = React.useState<string | null>(null);
-
- const renderMonthView = () => {
- const monthStart = startOfMonth(currentDate);
- const monthEnd = endOfMonth(monthStart);
- const days = eachDayOfInterval({ start: startOfWeek(monthStart, { locale: dateLocale }), end: endOfWeek(monthEnd, { locale: dateLocale }) });
- const weekdays = eachDayOfInterval({
- start: startOfWeek(currentDate, { locale: dateLocale }),
- end: endOfWeek(currentDate, { locale: dateLocale }),
- });
- return (
- <>
- <div className="grid grid-cols-7 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700">
- {weekdays.map(day => <div key={day.toString()} className="py-2">{format(day, 'E', { locale: dateLocale })}</div>)}
- </div>
- <div className="grid grid-cols-7 flex-1">
- {days.map(day => {
- const dayStr = format(day, 'yyyy-MM-dd');
- const isDragOver = dragOverDate === dayStr;
- const postsForDay = schedule.filter(p => isSameDay(parseISO(p.date), day)).sort((a,b) => a.time.localeCompare(b.time));
- return (
- <div
- key={day.toString()}
- onDragEnter={(e) => { e.preventDefault(); setDragOverDate(dayStr); }}
- onDragLeave={(e) => { e.preventDefault(); setDragOverDate(null); }}
- onDragOver={e => e.preventDefault()}
- onDrop={e => { onDrop(e, dayStr); setDragOverDate(null); }}
- onClick={() => onDateSelect(dayStr)}
- className={`relative border-r border-b border-gray-200 dark:border-gray-700 p-2 transition-all duration-200 ${!isSameMonth(day, currentDate) ? 'bg-gray-50 dark:bg-gray-900/50' : ''} ${isDragOver ? 'bg-brand-primary/20 border-2 border-brand-primary' : ''}`}
- >
- <time dateTime={dayStr} className={`text-sm font-semibold ${isToday(day) ? 'bg-brand-primary text-white rounded-full h-6 w-6 flex items-center justify-center' : ''}`}>{format(day, 'd')}</time>
- <div className="mt-2">{postsForDay.map(post => <ScheduledPostItem key={post.id} post={post} video={videos.find(v => v.id === post.videoId)} onClick={e => { e.stopPropagation(); onPostSelect(post); }} />)}</div>
- </div>
- );
- })}
- </div>
- </>
- );
- };
-
- // Day and Week views could be implemented similarly if needed
-
- return (
- <div className="flex-1 flex flex-col overflow-y-auto">
- {renderMonthView()}
- </div>
- );
- };
- // --- MAIN COMPONENT ---
- interface ContentSchedulerProps {
- videos: AIGCVideo[];
- schedule: ScheduledPost[];
- onScheduleUpdate: (newSchedule: ScheduledPost[]) => void;
- }
- export const ContentScheduler: React.FC<ContentSchedulerProps> = ({ videos, schedule, onScheduleUpdate }) => {
- const [currentDate, setCurrentDate] = React.useState(new Date());
- const [view, setView] = React.useState<'month' | 'week' | 'day'>('month');
- const [isModalOpen, setIsModalOpen] = React.useState(false);
- const [modalData, setModalData] = React.useState<{ videoId: string | null; postId: string | null; date: string }>({ videoId: null, postId: null, date: '' });
- const [videoFilter, setVideoFilter] = React.useState<'all' | 'scheduled' | 'unscheduled'>('all');
- const scheduledVideoIds = React.useMemo(() => new Set(schedule.map(p => p.videoId)), [schedule]);
- const filteredVideos = React.useMemo(() => {
- if (videoFilter === 'scheduled') {
- return videos.filter(v => scheduledVideoIds.has(v.id));
- }
- if (videoFilter === 'unscheduled') {
- return videos.filter(v => !scheduledVideoIds.has(v.id));
- }
- return videos;
- }, [videos, videoFilter, scheduledVideoIds]);
- const handleSchedule = (newPostData: Omit<ScheduledPost, 'id' | 'status'>) => {
- const newPost: ScheduledPost = { ...newPostData, id: `post-${Date.now()}`, status: 'scheduled' };
- onScheduleUpdate([...schedule, newPost]);
- };
- const handleUpdate = (updatedPost: ScheduledPost) => onScheduleUpdate(schedule.map(p => p.id === updatedPost.id ? updatedPost : p));
- const handleDelete = (postId: string) => onScheduleUpdate(schedule.filter(p => p.id !== postId));
- const handleDragStart = (e: React.DragEvent, videoId: string) => e.dataTransfer.setData("videoId", videoId);
- const handleDrop = (e: React.DragEvent, date: string) => {
- const videoId = e.dataTransfer.getData("videoId");
- if(videoId) {
- setModalData({ videoId, postId: null, date });
- setIsModalOpen(true);
- }
- };
-
- const changeDate = (direction: 'prev' | 'next') => {
- const op = direction === 'prev' ? 'sub' : 'add';
- const unit = view === 'month' ? 'Months' : view === 'week' ? 'Weeks' : 'Days';
- const fn = {
- sub: { Months: subMonths, Weeks: subWeeks, Days: subDays },
- add: { Months: addMonths, Weeks: addWeeks, Days: addDays }
- }[op][unit];
- setCurrentDate(fn(currentDate, 1));
- };
- const modalPost = modalData.postId ? schedule.find(p => p.id === modalData.postId) : null;
- const modalVideo = videos.find(v => v.id === (modalPost ? modalPost.videoId : modalData.videoId));
-
- return (
- <div className="flex h-full">
- {isModalOpen && <SchedulePostModal
- video={modalVideo || null}
- post={modalPost || null}
- date={modalData.date}
- accounts={initialSocialAccounts}
- onClose={() => setIsModalOpen(false)}
- onSchedule={handleSchedule}
- onUpdate={handleUpdate}
- onDelete={handleDelete}
- />}
- <VideoLibrarySidebar
- videos={filteredVideos}
- onDragStart={handleDragStart}
- activeFilter={videoFilter}
- onFilterChange={setVideoFilter}
- />
- <main className="flex-1 flex flex-col bg-white dark:bg-gray-800/50">
- <CalendarHeader
- view={view}
- currentDate={currentDate}
- onPrev={() => changeDate('prev')}
- onNext={() => changeDate('next')}
- onViewChange={setView}
- />
- <Calendar
- view={view}
- currentDate={currentDate}
- schedule={schedule}
- videos={videos}
- onDateSelect={(date) => { setModalData({ videoId: null, postId: null, date }); setIsModalOpen(true); }}
- onPostSelect={post => { setModalData({ videoId: null, postId: post.id, date: post.date }); setIsModalOpen(true); }}
- onDrop={handleDrop}
- />
- </main>
- </div>
- );
- };
|