From 02a3ad9f960e4a0e4cc6096e6c52b5d070a70482 Mon Sep 17 00:00:00 2001 From: Maithili Kalkar Date: Thu, 21 May 2026 15:51:36 -0400 Subject: [PATCH 01/10] add: reddit auto poster files --- .../platforms/reddit/Reddit.module.css | 489 +++++++++ .../Announcements/platforms/reddit/index.jsx | 940 ++++++++++++++++++ 2 files changed, 1429 insertions(+) create mode 100644 src/components/Announcements/platforms/reddit/Reddit.module.css create mode 100644 src/components/Announcements/platforms/reddit/index.jsx diff --git a/src/components/Announcements/platforms/reddit/Reddit.module.css b/src/components/Announcements/platforms/reddit/Reddit.module.css new file mode 100644 index 0000000000..2d7e094dc7 --- /dev/null +++ b/src/components/Announcements/platforms/reddit/Reddit.module.css @@ -0,0 +1,489 @@ +.reddit-autoposter { + width: 100%; + margin: 0 auto; + display: grid; + gap: 24px; +} + +.reddit-autoposter.dark { + color: #dbe6ff; +} + +.reddit-autoposter.dark label { + color: #dbe6ff; +} + +.reddit-autoposter.dark p { + color: #dbe6ff; +} + +/* ── Sub-tabs ── */ +.reddit-subtabs { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 18px; + border-bottom: 1px solid #ccd4e0; +} + +.reddit-subtab { + padding: 9px 16px; + border-radius: 6px 6px 0 0; + border: 1px solid transparent; + border-bottom: none; + background: #d9d9d9; + color: #333; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.reddit-subtab:hover { + background: #cfcfcf; +} + +.reddit-subtab.active { + background: #fff0eb; + color: #ff4500; + border-color: #ffb59e; + box-shadow: inset 0 1px 0 rgb(255 255 255 / 60%); +} + +.reddit-autoposter.dark .reddit-subtab { + background: #2d3c53; + color: #cdd8f6; + border-color: transparent; +} + +.reddit-autoposter.dark .reddit-subtab.active { + background: #5c1f00; + border-color: #a03000; + color: #ffb89e; +} + +/* ── Cards ── */ +.reddit-card { + background: #fff; + border: 1px solid #d6dde7; + border-radius: 12px; + padding: 20px 22px; + box-shadow: 0 10px 24px rgb(15 37 80 / 8%); + transition: border-color 0.2s ease, box-shadow 0.2s ease; +} + +.reddit-autoposter.dark .reddit-card { + background: #14233a; + border-color: #25354d; + box-shadow: none; +} + +.reddit-card.invalid { + border-color: #d9534f; + box-shadow: 0 0 0 1px rgb(217 83 79 / 18%); +} + +.reddit-autoposter.dark .reddit-card.invalid { + border-color: #ff7b72; + box-shadow: none; +} + +.reddit-card--wide { + grid-column: 1 / -1; +} + +/* ── Grid ── */ +.reddit-grid { + display: grid; + gap: 20px; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); +} + +/* ── Post type toggle ── */ +.reddit-type-toggle { + display: flex; + align-items: center; + gap: 16px; + margin-top: 16px; + flex-wrap: wrap; +} + +.reddit-toggle-group { + display: flex; + gap: 8px; +} + +.reddit-toggle-btn { + padding: 8px 18px; + border-radius: 999px; + border: 1px solid #d6dde7; + background: #f4f7fd; + color: #555; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.reddit-toggle-btn:hover { + background: #ffe8df; + border-color: #ffb59e; +} + +.reddit-toggle-btn.active { + background: #ff4500; + color: #fff; + border-color: #ff4500; +} + +.reddit-autoposter.dark .reddit-toggle-btn { + background: #1c2b44; + border-color: #2b3b55; + color: #cdd8f6; +} + +.reddit-autoposter.dark .reddit-toggle-btn.active { + background: #c43300; + border-color: #c43300; + color: #fff; +} + +/* ── Field header ── */ +.reddit-field__header { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 12px; + margin-bottom: 8px; +} + +.reddit-field__meta { + font-size: 0.85rem; + color: #6c757d; +} + +.reddit-field__meta.invalid { + color: #d9534f; +} + +.reddit-autoposter.dark .reddit-field__meta { + color: #9aa9c6; +} + +.reddit-field__required { + color: #d9534f; + margin-left: 4px; +} + +.reddit-autoposter.dark .reddit-field__required { + color: #ff9384; +} + +/* ── Inputs ── */ +.reddit-field__input { + width: 100%; + border: 1px solid #c7d1e5; + border-radius: 8px; + padding: 12px 14px; + font-size: 0.95rem; + background: #fff; + color: #1b1f29; + box-sizing: border-box; +} + +.reddit-autoposter.dark .reddit-field__input { + background: #0f1c2d; + border-color: #2b3b55; + color: #e4edff; +} + +.reddit-autoposter.dark .reddit-field__input[type='date'], +.reddit-autoposter.dark .reddit-field__input[type='time'] { + color-scheme: dark; +} + +.reddit-field__input--invalid { + border-color: #d9534f; + box-shadow: 0 0 0 1px rgb(217 83 79 / 20%); +} + +.reddit-autoposter.dark .reddit-field__input--invalid { + border-color: #ff9384; + box-shadow: 0 0 0 1px rgb(255 147 132 / 30%); +} + +/* Subreddit prefix wrapper */ +.reddit-subreddit-input-wrap { + display: flex; + align-items: center; + border: 1px solid #c7d1e5; + border-radius: 8px; + overflow: hidden; + background: #fff; +} + +.reddit-autoposter.dark .reddit-subreddit-input-wrap { + background: #0f1c2d; + border-color: #2b3b55; +} + +.reddit-subreddit-prefix { + padding: 12px 10px 12px 14px; + background: #f4f7fd; + color: #6c757d; + font-weight: 700; + font-size: 0.95rem; + border-right: 1px solid #c7d1e5; + flex-shrink: 0; +} + +.reddit-autoposter.dark .reddit-subreddit-prefix { + background: #0d1a2b; + color: #9aa9c6; + border-right-color: #2b3b55; +} + +.reddit-field__input--subreddit { + border: none; + border-radius: 0; + box-shadow: none; + flex: 1; +} + +.reddit-field__input--subreddit:focus { + outline: none; +} + +.reddit-field__textarea { + resize: vertical; + min-height: 110px; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.reddit-field__error { + color: #d9534f; + font-size: 0.85rem; + margin-top: 8px; +} + +.reddit-autoposter.dark .reddit-field__error { + color: #ff9384; +} + +.reddit-field__hint { + color: #6c757d; + font-size: 0.85rem; + margin-top: 8px; +} + +.reddit-autoposter.dark .reddit-field__hint { + color: #9aa9c6; +} + +/* ── Preview ── */ +.reddit-preview__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 12px; + flex-wrap: wrap; +} + +.reddit-preview__actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: flex-end; +} + +.reddit-preview__body { + white-space: pre-wrap; + font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; + background: #f8f9fb; + border: 1px solid #d6dde7; + border-radius: 8px; + padding: 16px; + color: #27324b; + max-height: 240px; + overflow: auto; + overflow-wrap: anywhere; +} + +.reddit-autoposter.dark .reddit-preview__body { + background: #0f1c2d; + border-color: #2b3b55; + color: #e4edff; +} + +.reddit-preview__hint { + font-size: 0.85rem; + color: #6c757d; + margin-top: 12px; +} + +.reddit-autoposter.dark .reddit-preview__hint { + color: #9aa9c6; +} + +/* ── Scheduler ── */ +.reddit-card--scheduler { + width: 100%; +} + +.reddit-scheduler__grid { + display: grid; + gap: 20px; + grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); + align-items: start; +} + +.reddit-card--saved { + max-width: 100%; +} + +.reddit-scheduler__note { + font-size: 0.85rem; + color: #6c757d; + margin-top: 12px; +} + +.reddit-autoposter.dark .reddit-scheduler__note { + color: #9aa9c6; +} + +.reddit-scheduler__controls { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin: 18px 0; +} + +.reddit-scheduler__field { + flex: 1 1 200px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.reddit-scheduler__textarea { + min-height: 220px; +} + +.reddit-scheduler__actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 18px; +} + +.reddit-scheduler__empty { + font-size: 0.9rem; + color: #6c757d; + margin: 8px 0 0; +} + +.reddit-autoposter.dark .reddit-scheduler__empty { + color: #9aa9c6; +} + +/* ── Saved list ── */ +.reddit-saved__list { + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 18px; +} + +.reddit-saved__item { + border: 1px solid #d6dde7; + border-radius: 10px; + background: #f4f7fd; + padding: 12px 14px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.reddit-autoposter.dark .reddit-saved__item { + border-color: #2b3b55; + background: #0f1c2d; +} + +.reddit-saved__item--active { + border-color: #ff4500; + box-shadow: 0 0 0 1px rgb(255 69 0 / 24%); +} + +.reddit-autoposter.dark .reddit-saved__item--active { + border-color: #ff6030; + box-shadow: 0 0 0 1px rgb(255 96 48 / 32%); +} + +.reddit-saved__header { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 12px; +} + +.reddit-saved__title { + font-size: 1rem; + font-weight: 600; + margin: 0; + color: #1b1f29; +} + +.reddit-autoposter.dark .reddit-saved__title { + color: #dbe6ff; +} + +.reddit-saved__meta { + font-size: 0.85rem; + color: #6c757d; +} + +.reddit-autoposter.dark .reddit-saved__meta { + color: #9aa9c6; +} + +.reddit-saved__excerpt { + font-size: 0.9rem; + color: #4f5a73; + margin: 0; +} + +.reddit-autoposter.dark .reddit-saved__excerpt { + color: #cfd9f8; +} + +.reddit-saved__actions { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +/* ── Responsive ── */ +@media (width <= 960px) { + .reddit-scheduler__grid { + grid-template-columns: 1fr; + } +} + +@media (width <= 640px) { + .reddit-autoposter { + gap: 18px; + } + + .reddit-card { + padding: 18px; + } + + .reddit-preview__header { + flex-direction: column; + align-items: flex-start; + } + + .reddit-preview__actions { + justify-content: flex-start; + } +} \ No newline at end of file diff --git a/src/components/Announcements/platforms/reddit/index.jsx b/src/components/Announcements/platforms/reddit/index.jsx new file mode 100644 index 0000000000..ff02fcc651 --- /dev/null +++ b/src/components/Announcements/platforms/reddit/index.jsx @@ -0,0 +1,940 @@ +import React, { useMemo, useState } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { useSelector } from 'react-redux'; +import { toast } from 'react-toastify'; + +import styles from './Reddit.module.css'; + +const TITLE_MIN = 5; +const TITLE_MAX = 300; +const BODY_MAX = 40000; + +const STOP_WORDS = new Set([ + 'about', + 'after', + 'also', + 'another', + 'because', + 'been', + 'being', + 'between', + 'can', + 'could', + 'during', + 'each', + 'from', + 'have', + 'into', + 'more', + 'other', + 'over', + 'since', + 'some', + 'than', + 'that', + 'their', + 'there', + 'these', + 'they', + 'this', + 'through', + 'under', + 'until', + 'where', + 'which', + 'while', + 'with', + 'within', +]); + +const FLAIR_RULES = [ + { + flair: 'Question', + patterns: [/\?/, /\bhow\b/, /\bwhat\b/, /\bwhy\b/, /\bhelp\b/, /\bissue\b/, /\bproblem\b/], + }, + { + flair: 'Discussion', + patterns: [/\bdiscussion\b/, /\bthoughts\b/, /\bopinion\b/, /\bdebate\b/, /\bshould\b/], + }, + { + flair: 'News', + patterns: [ + /\bnews\b/, + /\breleased\b/, + /\blaunch\b/, + /\bannouncement\b/, + /\bupdate\b/, + /\bbreaking\b/, + ], + }, + { + flair: 'Tutorial', + patterns: [/\btutorial\b/, /\bguide\b/, /\bstep[- ]by[- ]step\b/, /\blearn\b/], + }, + { + flair: 'Showcase', + patterns: [/\bshowcase\b/, /\bproject\b/, /\bbuilt\b/, /\bmade\b/, /\bcreated\b/], + }, + { + flair: 'Bug', + patterns: [/\bbug\b/, /\berror\b/, /\bfix\b/, /\bcrash\b/, /\bissue\b/], + }, +]; + +// Subreddit name must be 3-21 chars, letters/digits/underscores only +const sanitizeSubreddit = raw => + raw + .trim() + .replace(/^r\//, '') + .replace(/[^a-zA-Z0-9_]/g, '') + .slice(0, 21); + +const extractFlairSuggestions = (title, body, subreddit = '') => { + const text = `${title} ${body}`.toLowerCase(); + + // subreddit-specific suggestions + const subredditSuggestions = { + reactjs: ['Help', 'Discussion', 'Showcase'], + javascript: ['Question', 'Discussion', 'News'], + programming: ['Discussion', 'News', 'Tutorial'], + webdev: ['Showcase', 'Tutorial', 'Question'], + }; + + const normalizedSubreddit = subreddit.toLowerCase(); + + if (subredditSuggestions[normalizedSubreddit]) { + return subredditSuggestions[normalizedSubreddit]; + } + + const matchedFlairs = []; + + for (const rule of FLAIR_RULES) { + const matched = rule.patterns.some(pattern => pattern.test(text)); + + if (matched && !matchedFlairs.includes(rule.flair)) { + matchedFlairs.push(rule.flair); + } + } + + // fallback keyword extraction + if (matchedFlairs.length === 0) { + const words = text.match(/[a-z0-9']+/g) || []; + + const keywords = words + .map(word => word.replace(/'/g, '')) + .filter(word => word.length >= 4 && !STOP_WORDS.has(word)) + .filter((word, index, arr) => arr.indexOf(word) === index) + .slice(0, 3); + + return keywords; + } + + return matchedFlairs; +}; + +const buildPreview = ({ title, url, subreddit, flair, body }) => + `Subreddit\nr/${subreddit?.trim() || '—'}\n\nTitle\n${title?.trim() || + '—'}\n\nURL\n${url?.trim() || '—'}\n\nBody\n${body?.trim() || '—'}\n\nFlair\n${flair?.trim() || + '(none)'}\n`; + +const padTimeUnit = value => String(value).padStart(2, '0'); + +const formatLocalDate = date => + `${date.getFullYear()}-${padTimeUnit(date.getMonth() + 1)}-${padTimeUnit(date.getDate())}`; + +const formatLocalTime = date => `${padTimeUnit(date.getHours())}:${padTimeUnit(date.getMinutes())}`; + +const getSecureBase36 = length => { + const chars = []; + const max = 36 * 7; + while (chars.length < length) { + const bytes = new Uint8Array(length); + globalThis.crypto.getRandomValues(bytes); + for (const byte of bytes) { + if (byte >= max) continue; + chars.push((byte % 36).toString(36)); + if (chars.length === length) break; + } + } + return chars.join(''); +}; + +const createScheduleId = () => `schedule-${Date.now().toString(36)}-${getSecureBase36(6)}`; + +const formatDisplayDateTime = (dateString, timeString) => { + if (!dateString) return '—'; + try { + const composed = `${dateString}T${timeString || '00:00'}`; + const parsed = new Date(composed); + if (Number.isNaN(parsed.getTime())) { + return `${dateString}${timeString ? `, ${timeString}` : ''}`; + } + const formattedDate = parsed.toLocaleDateString(undefined, { + month: 'short', + day: 'numeric', + year: 'numeric', + }); + const formattedTime = timeString + ? parsed.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' }) + : ''; + return formattedTime ? `${formattedDate} • ${formattedTime}` : formattedDate; + } catch { + return `${dateString}${timeString ? `, ${timeString}` : ''}`; + } +}; + +const topCardActions = () => ({ + display: 'flex', + flexWrap: 'wrap', + gap: '12px', + marginTop: '16px', +}); + +const buttonStyle = (variant, darkMode) => { + const base = { + borderRadius: '999px', + border: 'none', + cursor: 'pointer', + fontWeight: 600, + padding: '10px 18px', + transition: 'filter 0.2s ease', + }; + if (variant === 'primary') return { ...base, backgroundColor: '#ff4500', color: '#fff' }; + if (variant === 'outline') + return { + ...base, + backgroundColor: 'transparent', + color: darkMode ? '#ff8060' : '#ff4500', + border: `1px solid ${darkMode ? '#6b3020' : '#ff4500'}`, + }; + return { + ...base, + backgroundColor: darkMode ? '#1c2b44' : '#fff0eb', + color: darkMode ? '#ffb8a0' : '#a33000', + }; +}; + +const fieldActionRow = { display: 'flex', flexWrap: 'wrap', gap: '10px', marginTop: '12px' }; + +function RedditAutoPoster({ platform }) { + const darkMode = useSelector(state => state.theme.darkMode); + + const [title, setTitle] = useState(''); + const [url, setUrl] = useState(''); + const [subreddit, setSubreddit] = useState(''); + const [flair, setFlair] = useState(''); + const [body, setBody] = useState(''); + const [activeSubTab, setActiveSubTab] = useState('make'); + const [scheduledDraft, setScheduledDraft] = useState(''); + const [scheduledDate, setScheduledDate] = useState(() => formatLocalDate(new Date())); + const [scheduledTime, setScheduledTime] = useState(() => formatLocalTime(new Date())); + const [savedSchedules, setSavedSchedules] = useState([]); + const [editingScheduleId, setEditingScheduleId] = useState(null); + const [scheduleAttemptedSave, setScheduleAttemptedSave] = useState(false); + const [flairSuggestions, setFlairSuggestions] = useState([]); + + const subTabs = useMemo( + () => [ + { id: 'make', label: '📝 Make Post' }, + { id: 'schedule', label: '⏰ Scheduled Post' }, + ], + [], + ); + + const trimmedTitle = title.trim(); + const trimmedUrl = url.trim(); + const trimmedSubreddit = subreddit.trim(); + const trimmedBody = body.trim(); + const trimmedFlair = flair.trim(); + + const titleInRange = trimmedTitle.length >= TITLE_MIN && trimmedTitle.length <= TITLE_MAX; + const urlValid = trimmedUrl.length === 0 || /^https?:\/\//i.test(trimmedUrl); + const subredditValid = trimmedSubreddit.length >= 3 && trimmedSubreddit.length <= 21; + const bodyValid = trimmedBody.length <= BODY_MAX; + + const readyToCopy = titleInRange && subredditValid; + + const highlightTitle = trimmedTitle.length > 0 && !titleInRange; + const highlightUrl = trimmedUrl.length > 0 && !urlValid; + const highlightSubreddit = trimmedSubreddit.length > 0 && !subredditValid; + const highlightBody = trimmedBody.length > 0 && !bodyValid; + + const hasAnyInput = Boolean( + trimmedTitle || trimmedUrl || trimmedSubreddit || trimmedBody || trimmedFlair, + ); + + const preview = useMemo(() => { + if (!hasAnyInput) return ''; + return buildPreview({ title, url, subreddit: trimmedSubreddit, flair, body }); + }, [title, url, trimmedSubreddit, flair, body, hasAnyInput]); + + const scheduleHasDraft = scheduledDraft.trim().length > 0; + const editingSchedule = useMemo( + () => savedSchedules.find(s => s.id === editingScheduleId) || null, + [editingScheduleId, savedSchedules], + ); + + const copyText = async (text, label) => { + const value = text?.trim(); + if (!value) { + toast.warn(`Nothing to copy for ${label}.`); + return; + } + try { + await navigator.clipboard.writeText(value); + toast.success(`${label} copied to clipboard`); + } catch { + toast.error(`Could not copy ${label.toLowerCase()}.`); + } + }; + + const handleReset = () => { + setTitle(''); + setUrl(''); + setSubreddit(''); + setFlair(''); + setBody(''); + setFlairSuggestions([]); + }; + + const openRedditSubmit = () => { + const sub = trimmedSubreddit ? `r/${trimmedSubreddit}/` : ''; + window.open(`https://www.reddit.com/${sub}submit`, '_blank', 'noopener,noreferrer'); + }; + + const handleScheduleClick = () => { + if (!hasAnyInput) { + toast.error('Nothing to schedule yet. Add details in Make Post first.'); + return; + } + const missing = []; + if (!trimmedTitle) missing.push('Title'); + if (!trimmedSubreddit) missing.push('Subreddit'); + if (missing.length > 0) { + toast.error(`Add ${missing.join(', ')} before scheduling.`); + return; + } + const now = new Date(); + setScheduledDate(formatLocalDate(now)); + setScheduledTime(formatLocalTime(now)); + setScheduledDraft(preview); + setScheduleAttemptedSave(false); + setActiveSubTab('schedule'); + toast.success('Draft moved to Schedule tab.'); + }; + + const now = new Date(); + const today = formatLocalDate(now); + const currentTime = formatLocalTime(now); + const scheduleTimeMin = scheduledDate === today ? currentTime : '00:00'; + + const handleScheduleDateChange = event => { + const nextDateRaw = event.target.value; + if (!nextDateRaw) return; + const nextDate = nextDateRaw < today ? today : nextDateRaw; + setScheduledDate(nextDate); + setScheduleAttemptedSave(false); + if (nextDate === today) { + const refreshedTime = formatLocalTime(new Date()); + setScheduledTime(prev => (prev && prev >= refreshedTime ? prev : refreshedTime)); + } + }; + + const handleScheduleTimeChange = event => { + const nextTimeRaw = event.target.value; + if (!nextTimeRaw) return; + if (scheduledDate === today) { + const refreshedTime = formatLocalTime(new Date()); + setScheduledTime(nextTimeRaw >= refreshedTime ? nextTimeRaw : refreshedTime); + setScheduleAttemptedSave(false); + return; + } + setScheduledTime(nextTimeRaw); + setScheduleAttemptedSave(false); + }; + + const handleBackToMake = () => { + setScheduleAttemptedSave(false); + setActiveSubTab('make'); + }; + + const handleSaveSchedule = () => { + setScheduleAttemptedSave(true); + if (!scheduleHasDraft) { + toast.warn('Add content to the schedule before saving.'); + return; + } + if (!scheduledDate || !scheduledTime) { + toast.error('Choose a schedule date and time.'); + return; + } + const isEditing = Boolean(editingScheduleId); + const recordId = isEditing ? editingScheduleId : createScheduleId(); + const record = { + id: recordId, + title, + url, + subreddit: trimmedSubreddit, + flair, + body, + scheduledDraft: scheduledDraft.trim(), + scheduledDate, + scheduledTime, + updatedAt: new Date().toISOString(), + }; + setSavedSchedules(prev => [record, ...prev.filter(item => item.id !== record.id)]); + toast.success(isEditing ? 'Scheduled post updated.' : 'Scheduled post saved.'); + setScheduleAttemptedSave(false); + setEditingScheduleId(null); + }; + + const handleEditSchedule = scheduleId => { + const target = savedSchedules.find(s => s.id === scheduleId); + if (!target) return; + const refreshedToday = formatLocalDate(new Date()); + let nextDate = target.scheduledDate || refreshedToday; + if (nextDate < refreshedToday) nextDate = refreshedToday; + let nextTime = target.scheduledTime || '00:00'; + if (nextDate === refreshedToday) { + const refreshedTime = formatLocalTime(new Date()); + if (!nextTime || nextTime < refreshedTime) nextTime = refreshedTime; + } + setTitle(target.title || ''); + setUrl(target.url || ''); + setSubreddit(target.subreddit || ''); + setFlair(target.flair || ''); + setBody(target.body || ''); + setScheduledDraft(target.scheduledDraft || ''); + setScheduledDate(nextDate); + setScheduledTime(nextTime); + setScheduleAttemptedSave(false); + setEditingScheduleId(target.id); + setActiveSubTab('schedule'); + toast.info('Loaded scheduled post for editing.'); + }; + + return ( +
+
+ {subTabs.map(({ id, label }) => ( + + ))} +
+ + {activeSubTab === 'make' ? ( + <> + {/* Top card – post type toggle */} +
+

Reddit Auto Poster

+

Compose your Reddit post, then copy each field or open Reddit directly.

+ +
+ +
+
+ +
+ {/* Subreddit */} +
+
+ + + r/ + +
+
+ r/ + setSubreddit(sanitizeSubreddit(e.target.value))} + className={classNames( + styles['reddit-field__input'], + styles['reddit-field__input--subreddit'], + { + [styles['reddit-field__input--invalid']]: highlightSubreddit, + }, + )} + placeholder="programming" + maxLength={21} + /> +
+ {!trimmedSubreddit && ( +

+ Enter the subreddit name without the "r/" prefix (3–21 characters). +

+ )} + {highlightSubreddit && ( +

+ Subreddit name must be 3–21 characters (letters, digits, underscores only). +

+ )} +
+ +
+
+ + {/* Title */} +
+
+ + + {title.trim().length}/{TITLE_MAX} + +
+ setTitle(e.target.value)} + className={classNames(styles['reddit-field__input'], { + [styles['reddit-field__input--invalid']]: highlightTitle, + })} + placeholder="e.g. Open-source tool achieves 10x performance improvement" + maxLength={TITLE_MAX} + /> + {!trimmedTitle && ( +

+ Keep titles clear and specific. Reddit allows up to {TITLE_MAX} characters. +

+ )} + {highlightTitle && ( +

+ Title must be at least {TITLE_MIN} characters. +

+ )} +
+ + +
+
+ + {/* URL */} +
+
+ + optional +
+ setUrl(e.target.value)} + className={classNames(styles['reddit-field__input'], { + [styles['reddit-field__input--invalid']]: highlightUrl, + })} + placeholder="https://" + /> + {!trimmedUrl && ( +

+ Paste the full URL you want to share. Must start with http:// or https://. +

+ )} + {highlightUrl && ( +

+ Enter a valid URL starting with http:// or https://. +

+ )} +
+ +
+
+ + {/* Flair */} +
+
+ + optional +
+ setFlair(e.target.value)} + className={styles['reddit-field__input']} + placeholder="e.g. Discussion, News, Question" + /> + {!trimmedFlair && ( +

+ Smart flair suggestions are based on your title and content. +

+ )} +
+ + + + +
+ {flairSuggestions.length > 0 && ( +
+ {flairSuggestions.map(item => ( + + ))} +
+ )} +
+ + {/* Body */} +
+
+ + + {body.trim().length}/{BODY_MAX} + +
+