diff --git a/src/components/Announcements/SocialMediaComposer.jsx b/src/components/Announcements/SocialMediaComposer.jsx
index f52f68f1ee..b354d7d9eb 100644
--- a/src/components/Announcements/SocialMediaComposer.jsx
+++ b/src/components/Announcements/SocialMediaComposer.jsx
@@ -1,4 +1,7 @@
import React, { useState } from 'react';
+import RedditPanel from './platforms/reddit/RedditPanel.jsx';
+import Scheduled from './platforms/reddit/Scheduled.jsx';
+import SubmittedPosts from './platforms/reddit/SubmittedPosts.jsx';
export default function SocialMediaComposer({ platform }) {
const [postContent, setPostContent] = useState('');
@@ -27,6 +30,26 @@ export default function SocialMediaComposer({ platform }) {
};
};
+ const renderPlatformComponent = () => {
+ const platformMap = {
+ reddit: {
+ composer: ,
+ scheduled: ,
+ history: ,
+ },
+ // add other platforms here:
+ // facebook: { composer: , scheduled: , ... }
+ };
+
+ return (
+ platformMap[platform]?.[activeSubTab] ?? (
+
+ No component available for {platform} / {activeSubTab}
+
+ )
+ );
+ };
+
return (
{platform}
@@ -48,7 +71,7 @@ export default function SocialMediaComposer({ platform }) {
))}
- {activeSubTab === 'composer' && (
+ {activeSubTab === 'composer' && platform !== 'reddit' && (
)}
- {activeSubTab === 'scheduled' && (
+ {activeSubTab === 'scheduled' && platform !== 'reddit' && (
Scheduled Posts for {platform}
@@ -134,7 +157,7 @@ export default function SocialMediaComposer({ platform }) {
)}
- {activeSubTab === 'history' && (
+ {activeSubTab === 'history' && platform !== 'reddit' && (
Post History for {platform}
@@ -146,7 +169,7 @@ export default function SocialMediaComposer({ platform }) {
)}
- {activeSubTab === 'details' && (
+ {activeSubTab === 'details' && platform !== 'reddit' && (
{platform}-Specific Details
@@ -159,6 +182,8 @@ export default function SocialMediaComposer({ platform }) {
)}
+
+ {renderPlatformComponent()}
);
}
diff --git a/src/components/Announcements/platforms/reddit/RedditPanel.jsx b/src/components/Announcements/platforms/reddit/RedditPanel.jsx
new file mode 100644
index 0000000000..29541ba98f
--- /dev/null
+++ b/src/components/Announcements/platforms/reddit/RedditPanel.jsx
@@ -0,0 +1,49 @@
+import './reddit.css';
+import { useEffect, useState } from 'react';
+import { ENDPOINTS } from '../../../../utils/URL';
+import axios from 'axios';
+import SubmitPost from './SubmitPost';
+
+export default function RedditPanel() {
+ const [hasToken, setHasToken] = useState(false);
+
+ useEffect(() => {
+ async function validateToken() {
+ try {
+ const res = await axios.get(`${ENDPOINTS.AP_REDDIT_AUTH_TOKEN}`);
+ setHasToken(res.data.exists);
+ } catch (error) {
+ setHasToken(false);
+ }
+ }
+ validateToken();
+ }, []);
+
+ const handleConnectToReddit = () => {
+ // Code to display Reddit Manager
+ const authorizationUrl = `https://www.reddit.com/api/v1/authorize?
+ client_id=${process.env.REACT_APP_REDDIT_CLIENT_ID}&response_type=code
+ &state=random-string
+ &redirect_uri=${process.env.REACT_APP_REDDIT_REDIRECT_URL}
+ &duration=permanent&scope=identity,submit,save`;
+ window.open(authorizationUrl, '_self');
+ };
+
+ return (
+
+
+ {hasToken === false && (
+
+ )}
+ {hasToken === true && (
+
+ {/* Render Reddit post editor here */}
+
+
+ )}
+
+
+ );
+}
diff --git a/src/components/Announcements/platforms/reddit/Redirect.jsx b/src/components/Announcements/platforms/reddit/Redirect.jsx
new file mode 100644
index 0000000000..a7a680088a
--- /dev/null
+++ b/src/components/Announcements/platforms/reddit/Redirect.jsx
@@ -0,0 +1,33 @@
+import { useEffect, useState } from 'react';
+import { useLocation, useHistory } from 'react-router-dom';
+import { toast } from 'react-toastify';
+import axios from 'axios';
+import { ENDPOINTS } from '../../../../utils/URL';
+
+function Redirect() {
+ const { search } = useLocation();
+ const [redditCode, setRedditCode] = useState(null);
+ const history = useHistory();
+
+ const fetchRedditRefreshToken = async oneTimeCode => {
+ try {
+ await axios.get(`${ENDPOINTS.AP_REDDIT_AUTH_LOGIN}?code=${encodeURIComponent(oneTimeCode)}`);
+ history.push('/announcements');
+ } catch (error) {
+ toast.error(error.message || ' Failed to connect to Reddit');
+ }
+ };
+
+ useEffect(() => {
+ const query = new URLSearchParams(search);
+ if (query.has('code')) {
+ const code = query.get('code');
+ setRedditCode(code);
+ fetchRedditRefreshToken(code);
+ }
+ }, []);
+
+ return Recived code from Reddit {redditCode}
;
+}
+
+export default Redirect;
diff --git a/src/components/Announcements/platforms/reddit/Scheduled.jsx b/src/components/Announcements/platforms/reddit/Scheduled.jsx
new file mode 100644
index 0000000000..963eb9be97
--- /dev/null
+++ b/src/components/Announcements/platforms/reddit/Scheduled.jsx
@@ -0,0 +1,160 @@
+import { useState, useEffect } from 'react';
+import axios from 'axios';
+import { toast } from 'react-toastify';
+import { ENDPOINTS } from '../../../../utils/URL';
+import SubmitPost from './SubmitPost';
+import { Modal, ModalFooter, Button } from 'reactstrap';
+
+export default function Scheduled() {
+ const [posts, setPosts] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [modal, setModal] = useState(false);
+
+ // editing state to open SubmitPost with initial data
+ const [editingPost, setEditingPost] = useState(null);
+
+ useEffect(() => {
+ fetchScheduledPosts();
+ }, []);
+
+ async function fetchScheduledPosts() {
+ setLoading(true);
+ setError(null);
+ try {
+ const res = await axios.get(`${ENDPOINTS.AP_REDDIT_POST}`, {
+ params: { status: 'scheduled' },
+ });
+
+ setPosts(res.data.posts || []);
+ } catch (error) {
+ setError('Unable to load scheduled posts');
+ toast.error('Unable to load scheduled posts for Reddit');
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ const formatSchedule = dateToFormat => {
+ const d = new Date(dateToFormat);
+ const options = { month: 'short', day: 'numeric' };
+ return d.toLocaleDateString('en-US', options);
+ };
+
+ const handleCancelPost = async postId => {
+ const confirmDelete = window.confirm(`Are you sure you wnat to delete this post?`);
+ if (!confirmDelete) return;
+ try {
+ const res = await axios.delete(`${ENDPOINTS.AP_REDDIT_POST}/${postId}`);
+
+ if (res?.status === 200) {
+ toast.success(res.data?.message || 'Scheduled post cancelled');
+ fetchScheduledPosts();
+ } else {
+ toast.error(res?.data?.message || 'ubable to cancel scheduled post');
+ }
+ } catch (err) {
+ toast.error('Unable to cancel scheduled post');
+ }
+ };
+
+ const handleEditPost = post => {
+ setEditingPost({
+ id: post.id || post._id,
+ title: post.title || '',
+ content: post.content || '',
+ subreddit: post.subreddit || '',
+ postType: post.postType || 'text',
+ link: post.link || post.url || '',
+ scheduledAt: post.scheduledAt || post.scheduled_at || '',
+ });
+ };
+
+ // called by SubmitPost after successful update
+ const handleEditedSaved = () => {
+ setEditingPost(null);
+ fetchScheduledPosts();
+ };
+
+ const handleEditClose = () => setEditingPost(null);
+
+ const handleEditChange = (field, value) => {
+ setEditingPost(prev => ({ ...prev, [field]: value }));
+ };
+
+ const toggle = () => setModal(!modal);
+
+ return (
+ <>
+ {/* render SubmitPost as overlay/modal when editingPost is set */}
+
+ {editingPost && (
+
+ )}
+
+
+
+
+
+
+ Scheduled Posts for Reddit
+
+
+ {posts.map(p => (
+ -
+
+
+ {p.scheduled_at ? formatSchedule(p.scheduled_at) : 'No Scheduled time'}
+ {` - ${p.title}`}
+
+
+
+
+
+
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/src/components/Announcements/platforms/reddit/SubmitPost.jsx b/src/components/Announcements/platforms/reddit/SubmitPost.jsx
new file mode 100644
index 0000000000..cf3f61c83a
--- /dev/null
+++ b/src/components/Announcements/platforms/reddit/SubmitPost.jsx
@@ -0,0 +1,298 @@
+import { useEffect, useState } from 'react';
+import './reddit.css';
+import { useSelector } from 'react-redux';
+import { ENDPOINTS } from '../../../../utils/URL';
+import axios from 'axios';
+import { isValidMediaUrl } from '~/utils/checkValidURL';
+import { toast } from 'react-toastify';
+import { init } from '@sentry/browser';
+
+export default function SubmitPost({
+ initialData = null,
+ onSaved = () => {},
+ onCancelEdit = () => {},
+}) {
+ const darkMode = useSelector(state => state.theme.darkMode);
+ const isEditing = Boolean(initialData && (initialData.id || initialData._id));
+
+ const [postType, setPostType] = useState('text');
+ const [title, setTitle] = useState('');
+ const [subreddit, setSubreddit] = useState('');
+ const [content, setContent] = useState('');
+ const [url, setUrl] = useState('');
+ const [errors, setErrors] = useState({});
+ const [redditScheduleTime, setRedditScheduleTime] = useState('');
+
+ // Calculate max schedule date (6 months from now)
+ const maxScheduleDate = new Date();
+ maxScheduleDate.setMonth(maxScheduleDate.getMonth() + 6);
+
+ // helper function to convert ISO/date to "YYYY-MM-DDTHH:MM" to local string for
+ const toInputDateTime = iso => {
+ if (!iso) return '';
+ const d = new Date(iso);
+ if (Number.isNaN(d.getTime())) return '';
+ // convert to local ISO without timezone offset
+ const tzOffsetMs = d.getTimezoneOffset() * 60000;
+ const local = new Date(d.getTime() - tzOffsetMs);
+ return local.toISOString().slice(0, 16); // "YYYY-MM-DDTHH:MM"
+ };
+
+ //initialize form when editing
+ useEffect(() => {
+ if (!initialData) return;
+
+ (async () => {
+ await fetchScheduledPostById(initialData.id);
+ setErrors({});
+ })();
+ }, [initialData]);
+
+ const fetchScheduledPostById = async postId => {
+ const res = await axios.get(`${ENDPOINTS.AP_REDDIT_POST}/${postId}`);
+ if (res.status !== 200) {
+ onCancelEdit();
+ return false;
+ }
+ const post = res.data.post;
+
+ setTitle(post.title || '');
+ setSubreddit(post.subreddit || '');
+ if (post.content && post.content != '') {
+ setContent(post.content || '');
+ setPostType('text');
+ } else if (post.url && post.url != '') {
+ setUrl(post.url);
+ setPostType('link');
+ }
+
+ // use the fetched post date and convert to datetime-local format
+ const scheduleValue =
+ post.scheduledAt || post.scheduled_at || initialData.scheduledAt || initialData.scheduled_at;
+ setRedditScheduleTime(toInputDateTime(scheduleValue));
+
+ return true;
+ };
+
+ const handleSubmit = async e => {
+ e.preventDefault();
+ const newErrors = {};
+
+ if (!title.trim()) newErrors.title = 'Title is required.';
+ if (!subreddit.trim()) newErrors.subreddit = 'Subreddit is required.';
+ if (postType === 'text' && !content.trim()) newErrors.content = 'Content is required.';
+ if (postType === 'link') {
+ if (!url.trim()) {
+ newErrors.url = 'URL is required.';
+ } else if (!isValidMediaUrl(url.trim())) {
+ newErrors.url = 'Please enter valid URL. ';
+ }
+ }
+
+ setErrors(newErrors);
+ if (Object.keys(newErrors).length > 0) return;
+
+ let postData;
+ postData = {
+ title,
+ subreddit,
+ postType,
+ ...(postType === 'text' && { content }),
+ ...(postType === 'link' && { link: url }),
+ };
+
+ try {
+ const res = await axios.post(`${ENDPOINTS.AP_REDDIT_SUBMIT_POST}`, postData);
+ if (res.status === 201) {
+ toast.success(`Reddit post submitted successfully`);
+ } else {
+ throw new Error('Unable to submit reddit post');
+ }
+ } catch (error) {
+ toast.error(error.message || 'Unable to submit post to reddit');
+ }
+ };
+
+ const handleSchedulePost = async () => {
+ const newErrors = {};
+
+ if (!title.trim()) newErrors.title = 'Title is required.';
+ if (!subreddit.trim()) newErrors.subreddit = 'Subreddit is required.';
+ if (postType === 'text' && !content.trim()) newErrors.content = 'Content is required.';
+ if (postType === 'link') {
+ if (!url.trim()) {
+ newErrors.url = 'URL is required.';
+ } else if (!isValidMediaUrl(url.trim())) {
+ newErrors.url = 'Please enter valid URL. ';
+ }
+ }
+
+ if (!redditScheduleTime) {
+ newErrors.redditScheduleTime = 'Please select a schedule time.';
+ } else {
+ const selected = new Date(redditScheduleTime);
+ const now = new Date();
+
+ if (Number.isNaN(selected.getTime())) {
+ newErrors.redditScheduleTime = 'Invalid date/time.';
+ } else if (selected <= now) {
+ newErrors.redditScheduleTime = 'Please choose a future date/time.';
+ } else if (selected > maxScheduleDate) {
+ newErrors.redditScheduleTime = 'Schedule time must be within 6 months.';
+ }
+ }
+
+ setErrors(prev => ({ ...prev, ...newErrors }));
+
+ if (Object.keys(newErrors).length > 0) return;
+
+ // if valid, save post into database
+ const payload = {
+ title,
+ subreddit,
+ postType,
+ ...(postType === 'text' && { content }),
+ ...(postType === 'link' && { link: url }),
+ scheduledAt: redditScheduleTime,
+ };
+
+ try {
+ let res;
+ if (isEditing) {
+ //update existing post (PUT)
+ res = await axios.put(
+ `${ENDPOINTS.AP_REDDIT_POST}/${initialData.id || initialData._id}`,
+ payload,
+ );
+ } else {
+ res = await axios.post(`${ENDPOINTS.AP_REDDIT_POST}/schedule`, payload);
+ }
+
+ if (res?.status === 201) {
+ toast.success(`Reddit post schduled scuccessfully`);
+ setPostType('text');
+ setTitle('');
+ setSubreddit('');
+ setContent('');
+ setUrl('');
+ } else if (isEditing && res.status === 200) {
+ toast.success(`Scheduled post updated successfully`);
+ onSaved();
+ } else {
+ throw new Error('Unable to create reddit post for scheduled date');
+ }
+ } catch (error) {
+ toast.error(error.message || 'unable to schedule reddit post');
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/Announcements/platforms/reddit/SubmittedPosts.jsx b/src/components/Announcements/platforms/reddit/SubmittedPosts.jsx
new file mode 100644
index 0000000000..2e5f43762d
--- /dev/null
+++ b/src/components/Announcements/platforms/reddit/SubmittedPosts.jsx
@@ -0,0 +1,50 @@
+import { useEffect, useState } from 'react';
+import axios from 'axios';
+import { ENDPOINTS } from '../../../../utils/URL';
+
+export default function SubmittedPosts() {
+ const [posts, setPosts] = useState([]);
+
+ useEffect(() => {
+ fetchSubmittedPosts();
+ }, []);
+
+ const fetchSubmittedPosts = async () => {
+ try {
+ const res = await axios.get(`${ENDPOINTS.AP_REDDIT_POST}`, {
+ params: { status: 'submitted' },
+ });
+ setPosts(res.data.posts);
+ } catch (error) {
+ toast.error('Unable to load scheduled posts for Reddit');
+ }
+ };
+
+ const formatSchedule = dateToFormat => {
+ const d = new Date(dateToFormat);
+ const options = { month: 'short', day: 'numeric' };
+ return d.toLocaleDateString('en-US', options);
+ };
+
+ return (
+ <>
+
+
+ Scheduled Posts for Reddit
+
+ {posts.length > 0 ? (
+
+ {posts.map(p => (
+ -
+ {p.scheduled_at ? formatSchedule(p.scheduled_at) : 'No Scheduled time'}
+ {` - ${p.title}`}
+
+ ))}
+
+ ) : (
+
No posts found
+ )}
+
+ >
+ );
+}
diff --git a/src/components/Announcements/platforms/reddit/reddit.css b/src/components/Announcements/platforms/reddit/reddit.css
new file mode 100644
index 0000000000..a517d5add2
--- /dev/null
+++ b/src/components/Announcements/platforms/reddit/reddit.css
@@ -0,0 +1,60 @@
+.reddit-form-container {
+
+ margin : 50px;
+}
+
+.reddit-btn {
+ background: #ff4500;
+ color:#fff;
+ border: none;
+ border-radius: 4px;
+ padding:10px 18px;
+ margin-top: 10px;
+}
+.form-group {
+ margin-bottom: 1.5rem;
+}
+
+
+/* Input Fields */
+.form-group .form-select{
+ width: 180px;
+ min-width: 150px;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ background: white;
+ font-family: inherit;
+ font-size: 14px;
+ padding: 10px 12px;
+ padding-right: 30px;
+}
+
+
+.form-group .form-input,
+.form-group .form-textarea {
+ padding: 10px 12px;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ font-size: 14px;
+ font-family: inherit;
+ transition: all 0.3s ease;
+ background: white;
+}
+.form-textarea {
+ resize: vertical;
+ min-height: 200px;
+}
+
+/* Dark Mode */
+.reddit-form-container.dark-mode {
+ background: #2d2d2d;
+ color: #e0e0e0;
+}
+
+.reddit-form-container.dark-mode .form-select,
+.reddit-form-container.dark-mode .form-input,
+.reddit-form-container.dark-mode .form-textarea {
+ background: #3a3a3a;
+ border-color: #555;
+ color: #e0e0e0;
+}
\ No newline at end of file
diff --git a/src/routes.jsx b/src/routes.jsx
index 73a489e9ec..81fbb2dff3 100644
--- a/src/routes.jsx
+++ b/src/routes.jsx
@@ -252,6 +252,10 @@ const JobAnalyticsPage = lazy(() =>
);
const SuggestedJobsListBuilder = lazy(() => import('./components/Collaboration/SuggestedJobsList'));
+
+//Autoposter routes
+import RedditRedirect from './components/Announcements/platforms/reddit/Redirect';
+
export default (
{/* ----- LB Dashboard Routing Starts----- */}
@@ -500,6 +504,12 @@ export default (
component={Announcements}
routePermissions={RoutePermissions.announcements}
/>
+