Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b21309c
feat: Add Truth Social auto-poster frontend component
Jan 20, 2026
3e2299b
Merge branch 'development' into apoorva-truthsocial-frontend
apoorvajainrp21 Jan 23, 2026
d414016
Enhance modal with keyboard navigation support
apoorvajainrp21 Jan 24, 2026
7736304
Remove console.error from intermediateTasks.js
apoorvajainrp21 Jan 24, 2026
6fc2946
Remove console logs from fetchTasksFromPrimaryEndpoint
apoorvajainrp21 Jan 24, 2026
21b8492
Merge branch 'development' into apoorva-truthsocial-frontend
apoorvajainrp21 Jan 24, 2026
3e79389
Uncomment TruthSocialAutoPoster component code
apoorvajainrp21 Jan 24, 2026
6d1bad9
Refactor ConfirmationModal and update JSX structure
apoorvajainrp21 Jan 24, 2026
3fe5f73
Add visuallyHidden class for accessibility
apoorvajainrp21 Jan 24, 2026
1cb2dfa
Refactor updateParentTaskExpectedHours function
apoorvajainrp21 Jan 24, 2026
941a6b2
Refactor token handling and improve upload logic
apoorvajainrp21 Jan 24, 2026
7769410
Refactor observer setup and toggleCrossPost function
apoorvajainrp21 Jan 24, 2026
3e355ec
Update TruthSocialAutoPoster.jsx
apoorvajainrp21 Jan 24, 2026
ef61010
Update TruthSocialAutoPoster.jsx
apoorvajainrp21 Jan 24, 2026
ea423e1
Merge branch 'development' into apoorva-truthsocial-frontend
apoorvajainrp21 Jan 24, 2026
56c18e4
Update intermediateTasks.js
apoorvajainrp21 Jan 24, 2026
71f4ff4
Update TruthSocialAutoPoster.jsx
apoorvajainrp21 Jan 24, 2026
6a0707c
Update TruthSocialAutoPoster.jsx
apoorvajainrp21 Jan 24, 2026
ab33030
Update TruthSocialAutoPoster.jsx
apoorvajainrp21 Jan 24, 2026
e800dc5
Update TruthSocialAutoPoster.jsx
apoorvajainrp21 Jan 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 131 additions & 29 deletions src/actions/intermediateTasks.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,120 @@
import { toast } from 'react-toastify';
import httpService from '../services/httpService';
import { ENDPOINTS } from '~/utils/URL';
import { updateStudentTask } from './studentTasks';

// Re-export all actions from studentTasks
export {
setStudentTasksStart,
setStudentTasks,
setStudentTasksError,
updateStudentTask,
fetchStudentTasks,
markStudentTaskAsDone,
// Aliases
fetchStudentTasks as fetchIntermediateTasks,
markStudentTaskAsDone as markIntermediateTaskAsDone,
updateStudentTask as updateIntermediateTask,
} from './studentTasks';
/**
* Action types for intermediate tasks
*/
export const FETCH_INTERMEDIATE_TASKS_START = 'FETCH_INTERMEDIATE_TASKS_START';
export const FETCH_INTERMEDIATE_TASKS_SUCCESS = 'FETCH_INTERMEDIATE_TASKS_SUCCESS';
export const FETCH_INTERMEDIATE_TASKS_ERROR = 'FETCH_INTERMEDIATE_TASKS_ERROR';
export const CREATE_INTERMEDIATE_TASK_SUCCESS = 'CREATE_INTERMEDIATE_TASK_SUCCESS';
export const UPDATE_INTERMEDIATE_TASK_SUCCESS = 'UPDATE_INTERMEDIATE_TASK_SUCCESS';
export const DELETE_INTERMEDIATE_TASK_SUCCESS = 'DELETE_INTERMEDIATE_TASK_SUCCESS';
export const MARK_INTERMEDIATE_TASK_DONE = 'MARK_INTERMEDIATE_TASK_DONE';

/**
* Fetch intermediate tasks for a parent task
*/
export const fetchIntermediateTasks = taskId => {
return async dispatch => {
try {
const response = await httpService.get(ENDPOINTS.INTERMEDIATE_TASKS_BY_PARENT(taskId));
return response.data;
} catch (error) {
toast.error('Failed to fetch sub-tasks');
throw error;
}
};
};

/**
* Calculate total expected hours from intermediate tasks
*/
const calculateTotalExpectedHours = intermediateTasks => {
return intermediateTasks.reduce((total, task) => {
return total + (task.expected_hours || 0);
}, 0);
};

/**
* Update parent task's expected hours based on intermediate tasks
*/
const updateParentTaskExpectedHours = async (dispatch, getState, parentTaskId) => {
try {
const intermediateTasks = await dispatch(fetchIntermediateTasks(parentTaskId));
const totalExpectedHours = calculateTotalExpectedHours(intermediateTasks);
const state = getState();
const parentTask = state.studentTasks.taskItems.find(t => t.id === parentTaskId);

if (parentTask) {
dispatch(
updateStudentTask(parentTaskId, {
...parentTask,
suggested_total_hours: totalExpectedHours,
}),
);
}
} catch (error) {
toast.error('Failed to update parent task hours');
}
};

/**
* Create a new intermediate task
* @param {Object} taskData - The task data to create
*/
export const createIntermediateTask = (taskData) => {
export const createIntermediateTask = taskData => {
return async (dispatch, getState) => {
try {
const state = getState();
const userId = state.auth.user.userid;

const response = await httpService.post(
`${ENDPOINTS.APIEndpoint()}/education-tasks/intermediate`,
{ ...taskData, createdBy: userId }
);
const response = await httpService.post(ENDPOINTS.INTERMEDIATE_TASKS(), taskData);

if (response.data.success) {
toast.success('Task created successfully!');

if (taskData.parentTaskId) {
await updateParentTaskExpectedHours(dispatch, getState, taskData.parentTaskId);
}

return response.data.task;
}

return response.data;
} catch (error) {
const errorMessage = error.response?.data?.error || 'Failed to create task';
toast.error(errorMessage);
const errorMessage =
error.response?.data?.error || error.message || 'Failed to create sub-task';
toast.error(`Error: ${errorMessage}`);
throw error;
}
};
};

/**
* Update an intermediate task
* @param {string} id - The task ID
* @param {Object} taskData - The updated task data
*/
export const updateIntermediateTask = (id, taskData) => {
return async (dispatch, getState) => {
try {
const response = await httpService.put(ENDPOINTS.INTERMEDIATE_TASK_BY_ID(id), taskData);

if (response.data.success) {
toast.success('Task updated successfully!');

if (taskData.parentTaskId) {
await updateParentTaskExpectedHours(dispatch, getState, taskData.parentTaskId);
}

return response.data.task;
}

return response.data;
} catch (error) {
const errorMessage =
error.response?.data?.error || error.message || 'Failed to update sub-task';
toast.error(`Error: ${errorMessage}`);
throw error;
}
};
Expand All @@ -47,20 +124,45 @@ export const createIntermediateTask = (taskData) => {
* Delete an intermediate task
* @param {string} taskId - The task ID to delete
*/
export const deleteIntermediateTask = (taskId) => {
export const deleteIntermediateTask = taskId => {
return async (dispatch, getState) => {
try {
const response = await httpService.delete(
`${ENDPOINTS.APIEndpoint()}/education-tasks/intermediate/${taskId}`
);
const response = await httpService.delete(ENDPOINTS.INTERMEDIATE_TASK_BY_ID(taskId));

if (response.data.success) {
toast.success('Task deleted successfully!');
return true;
}

return true;
} catch (error) {
const errorMessage =
error.response?.data?.error || error.message || 'Failed to delete sub-task';
toast.error(`Error: ${errorMessage}`);
throw error;
}
};
};

/**
* Mark an intermediate task as done (for students)
*/
export const markIntermediateTaskAsDone = (id, parentTaskId) => {
return async dispatch => {
try {
const currentTask = await httpService.get(ENDPOINTS.INTERMEDIATE_TASK_BY_ID(id));

const response = await httpService.put(ENDPOINTS.INTERMEDIATE_TASK_BY_ID(id), {
...currentTask.data,
status: 'completed',
});

toast.success('Sub-task marked as done');
return response.data;
} catch (error) {
const errorMessage = error.response?.data?.error || 'Failed to delete task';
toast.error(errorMessage);
const errorMessage =
error.response?.data?.error || error.message || 'Failed to mark sub-task as done';
toast.error(`Error: ${errorMessage}`);
throw error;
}
};
Expand Down
9 changes: 9 additions & 0 deletions src/actions/studentTasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,13 @@
* @returns {Promise<Array>} Array of flattened tasks
*/
const fetchTasksFromPrimaryEndpoint = async () => {

const response = await httpService.get(ENDPOINTS.STUDENT_TASKS());

// The API returns grouped tasks, we need to flatten them for our UI
const groupedTasks = response.data.tasks;
const uniqueTasks = flattenGroupedTasks(groupedTasks);

return uniqueTasks;
};

Expand All @@ -128,6 +132,11 @@
* @returns {Promise<Array>} Array of tasks (from fallback or mock data)
*/
const handleApiError = async (apiError, dispatch) => {
console.error('Error response:', apiError.response?.data);

Check warning on line 135 in src/actions/studentTasks.js

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
console.error('Error status:', apiError.response?.status);

Check warning on line 136 in src/actions/studentTasks.js

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement
console.error('Error config:', apiError.config);

Check warning on line 137 in src/actions/studentTasks.js

View workflow job for this annotation

GitHub Actions / test

Unexpected console statement

// Try alternative endpoint if the first one fails
if (apiError.response?.status === 404) {
try {
const altResponse = await httpService.post(`${ENDPOINTS.APIEndpoint()}/student-tasks`);
Expand Down
6 changes: 5 additions & 1 deletion src/components/Announcements/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useSelector } from 'react-redux';
import { Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap';
import classnames from 'classnames';
import SocialMediaComposer from './SocialMediaComposer';
import TruthSocialAutoPoster from '../AutoPoster/TruthSocialAutoPoster';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faEnvelope,
Expand Down Expand Up @@ -143,6 +144,10 @@ function Announcements({ title, email: initialEmail }) {
<SocialMediaComposer platform="weeklyreport" darkMode={darkMode} />
</TabPane>

<TabPane tabId="truthsocial">
<TruthSocialAutoPoster darkMode={darkMode} />
</TabPane>

{[
'x',
'facebook',
Expand All @@ -164,7 +169,6 @@ function Announcements({ title, email: initialEmail }) {
'livejournal',
'slashdot',
'blogger',
'truthsocial',
].map(platform => (
<TabPane tabId={platform} key={platform}>
<SocialMediaComposer platform={platform} darkMode={darkMode} />
Expand Down
Loading
Loading