Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 0 additions & 10 deletions backend/src/cosmos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { BulkOperationType, SqlParameter } from "@azure/cosmos";
import {
Task,
Event,
Assignment,
Feedback,
Recipe,
Ingredient,
Expand Down Expand Up @@ -183,15 +182,6 @@ export const getEvent = (id: string) =>
export const deleteEvent = (id: string) =>
deleteItem(id, id, SCHEDULING_DB, "Events");

// Assignments
export const addAssignment = (assignment: Assignment) =>
addItem(assignment, SCHEDULING_DB, "Assignments");
export const getAssignments = () =>
getAllItems<Assignment>(SCHEDULING_DB, "Assignments");
export const getAssignment = (id: string) =>
getItem<Assignment>(id, SCHEDULING_DB, "Assignments");
export const deleteAssignment = (id: string) =>
deleteItem(id, id, SCHEDULING_DB, "Assignments");

// Feedback
export const addFeedback = (feedback: Feedback) =>
Expand Down
56 changes: 9 additions & 47 deletions backend/src/routes/scheduler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Request, Response, Router } from "express";
import {
addAssignment,
addEvent,
addFeedback,
addTask,
Expand All @@ -10,10 +9,10 @@ import {
queryItems,
} from "../cosmos";
import {
Assignment,
Event,
Feedback,
Task,
TaskCategory,
} from "../../../rise-dc-app/src/shared/types";

const SCHEDULING_DB = process.env.COSMOS_SCHEDULING_DB || "Scheduling";
Expand Down Expand Up @@ -88,17 +87,20 @@ schedulerRouter.delete("/tasks/:id", async (req: Request, res: Response) => {
}
});

// TODO: Fix this!
schedulerRouter.post("/events", async (req: Request, res: Response) => {
try {
const { name, icon, tasks, image, complete } = req.body;
const { userId, name, icon, steps, image, startTime, endTime, category, complete } = req.body;
const event: Event = {
id: `event_${Date.now()}`,
userId: userId || "",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Consider requiring userId instead of defaulting to empty string.

Defaulting userId to an empty string could complicate user-based filtering, ownership tracking, and access control. If events must be associated with a user, validate that userId is provided and return a 400 error if missing.

Apply this diff to require userId:

-      userId: userId || "",
+      userId,

Then add validation before creating the event:

if (!userId) {
  return res.status(400).json({ error: "userId is required" });
}
🤖 Prompt for AI Agents
In backend/src/routes/scheduler.ts around line 95, the code currently defaults
userId to an empty string which hides missing-user errors; remove the defaulting
and require a userId value by validating before creating the event—if userId is
falsy return a 400 response with a JSON error { error: "userId is required"
}—then proceed to create the event using the provided userId.

name,
icon: icon || "",
complete: complete || false,
tasks: tasks || [],
steps: steps || [],
image: image || { id: "", caption: "" },
startTime: startTime || { hour: 0, minute: 0, period: "AM" },
endTime: endTime || { hour: 0, minute: 0, period: "AM" },
category: (category as TaskCategory) || TaskCategory.Misc,
};
const createdEvent = await addEvent(event);
return res.status(201).json(createdEvent);
Expand All @@ -118,54 +120,14 @@ schedulerRouter.get("/events", async (_, res: Response) => {
}
});

// ===== ASSIGNMENTS =====
schedulerRouter.post("/assignments", async (req: Request, res: Response) => {
try {
const { complete, date, startTime, endTime, event } = req.body;
const assignment: Assignment = {
id: `assignment_${Date.now()}`,
complete: complete || false,
date,
startTime,
endTime,
event,
};
const createdAssignment = await addAssignment(assignment);
return res.status(201).json(createdAssignment);
} catch (error) {
console.error(error);
return res.status(500).json({ error: "Failed to create assignment" });
}
});

schedulerRouter.get("/assignments", async (req: Request, res: Response) => {
try {
const { date } = req.query;
let assignments: Assignment[];
if (date) {
assignments = await queryItems<Assignment>(
SCHEDULING_DB,
"Assignments",
"SELECT * FROM c WHERE c.date = @date",
[{ name: "@date", value: date as string }],
);
} else {
assignments = await getAllItems<Assignment>(SCHEDULING_DB, "Assignments");
}
return res.json(assignments);
} catch (error) {
console.error(error);
return res.status(500).json({ error: "Failed to fetch assignments" });
}
});

// ===== FEEDBACK =====
schedulerRouter.post("/feedback", async (req: Request, res: Response) => {
try {
const { taskAssignmentId, taskId, reaction } = req.body;
const { taskId, reaction } = req.body;
const feedback: Feedback = {
id: `feedback_${Date.now()}`,
taskAssignmentId,
taskAssignmentId: "",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the scheduler.ts file around line 130
cat -n backend/src/routes/scheduler.ts | sed -n '115,145p'

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 1057


🏁 Script executed:

# Search for Feedback interface definition
fd -t f . | xargs rg -l 'interface Feedback'

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 228


🏁 Script executed:

# Search for taskAssignmentId usage patterns throughout the codebase
rg -n 'taskAssignmentId' --type=ts -C 2

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 1080


taskAssignmentId is hardcoded to an empty string despite being a required field.

The Feedback interface explicitly requires taskAssignmentId: string (line 116 in rise-dc-app/src/shared/types/index.ts), but line 130 hardcodes it to an empty string without accepting it from the request body. This violates the interface contract and will create records with missing assignment tracking.

Either extract taskAssignmentId from the request body at line 127, or update the Feedback and FeedbackPayload interfaces to make this field optional if it's not yet implemented.

🤖 Prompt for AI Agents
In backend/src/routes/scheduler.ts around line 130 (and where the request body
is read at ~line 127), taskAssignmentId is hardcoded to "" which violates the
Feedback interface; update the code to extract taskAssignmentId from the
incoming request body (add it to the destructured fields or read
req.body.taskAssignmentId), validate it (ensure it's a string / non-empty if
required), and assign that value to taskAssignmentId instead of the empty
string; if this field genuinely may be absent, alternatively update the Feedback
and FeedbackPayload interfaces to make taskAssignmentId optional and adjust
downstream code accordingly.

taskId,
reaction,
};
Expand Down
12 changes: 8 additions & 4 deletions rise-dc-app/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { QueryClientProvider } from "@tanstack/react-query";
import { queryClient } from "./shared/constants/query";
import "./index.css";
import App from "./App";
import { FavoritesProvider } from "./shared/state/FavoritesContext";

createRoot(document.getElementById("root")).render(
<StrictMode>
<FavoritesProvider>
<App />
</FavoritesProvider>
</StrictMode>,
<QueryClientProvider client={queryClient}>
<FavoritesProvider>
<App />
</FavoritesProvider>
</QueryClientProvider>
</StrictMode>
);
41 changes: 39 additions & 2 deletions rise-dc-app/src/scheduling_components/PlanYourDayButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import "./EventSelectionModal.css"; // Don't forget to import the CSS
import EventSelectionModal from "./EventSelectionModal";
import EventCard from "./EventCard";
import styles from "./PlanYourDay.module.css";
import WhatsNextButton from "../shared/components/WhatsNextButton";
import AISuggestionsPopup from "./AISuggestionsPopup";
import { useAllTasks } from "../hooks/useTask";
import { Task } from "../shared/types";

type EventData = {
name: string;
Expand All @@ -16,6 +18,38 @@ export function PlanYourDayButton() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [events, setEvents] = useState<EventData[]>([]);
const [showAISuggestions, setShowAISuggestions] = useState(false); // Add AI popup state
const { data: tasks, isLoading, isSuccess } = useAllTasks();

// Sync events state with tasks from Cosmos
useEffect(() => {
if (!isSuccess || !tasks) return;

const convertedEvents: EventData[] = tasks
.map((task: Task) => ({
name: task.name,
startTime: task.startTime,
endTime: task.endTime,
}))
.sort((a, b) => {
// Sort by start time
const aHour24 =
a.startTime.period === "PM" && a.startTime.hour !== 12
? a.startTime.hour + 12
: a.startTime.hour === 12 && a.startTime.period === "AM"
? 0
: a.startTime.hour;
const bHour24 =
b.startTime.period === "PM" && b.startTime.hour !== 12
? b.startTime.hour + 12
: b.startTime.hour === 12 && b.startTime.period === "AM"
? 0
: b.startTime.hour;
if (aHour24 !== bHour24) return aHour24 - bHour24;
return a.startTime.minute - b.startTime.minute;
});

setEvents(convertedEvents);
}, [tasks, isSuccess]);

const openPlanner = () => {
setIsModalOpen(true);
Expand Down Expand Up @@ -72,7 +106,10 @@ export function PlanYourDayButton() {

return (
<>
{events.length === 0 && (
{isLoading && (
<div className="text-center text-white mt-8">Loading tasks...</div>
)}
{!isLoading && events.length === 0 && (
<div
className="bg-[#EB5904] text-white font-light pt-6 pr-25 pb-6 pl-25 rounded-lg text-[1.2rem] mt-[15rem] cursor-pointer"
onClick={openPlanner}
Expand Down
58 changes: 43 additions & 15 deletions rise-dc-app/src/scheduling_components/TimeSelectionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React, { useState } from "react";
import "./EventSelectionModal.css";
import back from "./icon_components/back.png";

interface TimeSlot {
hour: number;
minute: number;
period: "AM" | "PM";
}
import { useCreateTask } from "../hooks/useTask";
import { useQueryClient } from "@tanstack/react-query";
import { TaskPayload } from "../services/taskService";
import { TaskCategory, TimeSlot } from "../shared/types";

interface TimeSelectionModalProps {
isOpen: boolean;
Expand All @@ -23,6 +21,8 @@ const TimeSelectionModal: React.FC<TimeSelectionModalProps> = ({
selectedEvent = "Sweep floor",
onTimeConfirmed,
}) => {
const queryClient = useQueryClient();
const createTaskMutation = useCreateTask();
const [currentStep, setCurrentStep] =
useState<TimeSelectionStep>("START_TIME");
const [startTime, setStartTime] = useState<TimeSlot>({
Expand All @@ -41,7 +41,7 @@ const TimeSelectionModal: React.FC<TimeSelectionModalProps> = ({
const updateTime = (
timeType: "start" | "end",
field: "hour" | "minute",
increment: boolean,
increment: boolean
) => {
const currentTime = timeType === "start" ? startTime : endTime;
const setTime = timeType === "start" ? setStartTime : setEndTime;
Expand All @@ -64,7 +64,7 @@ const TimeSelectionModal: React.FC<TimeSelectionModalProps> = ({
const updateTimeDirect = (
timeType: "start" | "end",
field: "hour" | "minute",
value: number,
value: number
) => {
const setTime = timeType === "start" ? setStartTime : setEndTime;
const currentTime = timeType === "start" ? startTime : endTime;
Expand Down Expand Up @@ -100,9 +100,34 @@ const TimeSelectionModal: React.FC<TimeSelectionModalProps> = ({
};

const handleFinish = () => {
onTimeConfirmed?.(startTime, endTime);
setCurrentStep("START_TIME");
onClose();
// TODO: Get specific userId, icon, and category
const taskPayload: TaskPayload = {
userId: "",
name: selectedEvent,
icon: "",
startTime: startTime,
endTime: endTime,
category: TaskCategory.Misc,
};
Comment on lines 102 to +111
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's understand the repository structure
git ls-files | head -20

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 228


🏁 Script executed:

# Look for backend directory structure
fd -type d -name backend | head -10

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 256


🏁 Script executed:

# Search for Task/Event model definitions and validation
rg -n "userId.*required|userId.*validation|interface Task|type Task" --type ts --type js -A 3

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 1927


🏁 Script executed:

# Check for scheduler route handlers
fd -type f -name "*scheduler*" | grep -E "\.(ts|js)$"

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 256


🏁 Script executed:

# Get full TaskPayload definition and surrounding context
cat -n rise-dc-app/src/services/taskService.ts | head -50

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 1209


🏁 Script executed:

# Search for createTask mutation and how it's used
rg -n "createTask|useCreateTask" rise-dc-app/src --type ts --type tsx -B 2 -A 5

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 113


🏁 Script executed:

# Look for any validation logic in task-related files
rg -n "userId.*empty|validate|required" rise-dc-app/src -B 2 -A 2

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 1997


🏁 Script executed:

# Check if there's a backend directory anywhere
find . -maxdepth 3 -type d -name "*backend*" -o -name "*server*" -o -name "*api*" 2>/dev/null | grep -v node_modules

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 119


🏁 Script executed:

# Explore backend structure
ls -la backend/

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 578


🏁 Script executed:

# Look for Task model/schema and validation
fd . backend -type f \( -name "*task*" -o -name "*model*" -o -name "*schema*" \) | head -20

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 256


🏁 Script executed:

# Search for the /api/scheduler/tasks endpoint handler
rg -n "scheduler/tasks|TaskPayload|userId" backend/ -B 2 -A 5

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 2905


🏁 Script executed:

# Check for validation middleware or rules
rg -n "validate|required|empty" backend/ -i | head -30

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 68


🏁 Script executed:

# Check how tasks are retrieved/filtered
rg -n "listTasks|GET.*tasks|userId" backend/src/routes/scheduler.ts -B 2 -A 5

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 916


Empty userId and icon will be persisted without validation, breaking task ownership.

The backend's POST /tasks endpoint (line 28, 30 in backend/src/routes/scheduler.ts) accepts and stores userId and icon without validation. Tasks created with empty strings will lack user association, making it impossible to filter tasks by user. This breaks core functionality.

Either retrieve the actual userId before creating the task, add backend validation to reject empty values, or handle the error gracefully when these are unavailable.


createTaskMutation.mutate(taskPayload, {
onSuccess: (data) => {
console.log("Task created successfully:", data);

// Reset and close the modal
onTimeConfirmed?.(startTime, endTime);
setCurrentStep("START_TIME");
onClose();
},
onError: (error) => {
console.error("Failed to create task:", error);
alert("Failed to create task. Please try again.");
},
onSettled: () => {
// Refresh the tasks list
queryClient.invalidateQueries({ queryKey: ["tasks"] });
},
});
};

const handleClose = () => {
Expand All @@ -115,7 +140,7 @@ const TimeSelectionModal: React.FC<TimeSelectionModalProps> = ({
time: TimeSlot,
isActive: boolean,
label: string,
icon: string = "🕐",
icon: string = "🕐"
) => (
<div
className={`time-selector ${isActive ? "active" : "disabled"} ${timeType}-time-selector`}
Expand Down Expand Up @@ -237,13 +262,13 @@ const TimeSelectionModal: React.FC<TimeSelectionModalProps> = ({
"start",
startTime,
currentStep === "START_TIME",
"Start time",
"Start time"
)}
{renderTimeSelector(
"end",
endTime,
currentStep === "END_TIME",
"End time",
"End time"
)}
</div>

Expand All @@ -253,8 +278,11 @@ const TimeSelectionModal: React.FC<TimeSelectionModalProps> = ({
<button
className="finish-adding-task-button"
onClick={handleFinish}
disabled={createTaskMutation.isPending}
>
Finish Adding Task
{createTaskMutation.isPending
? "Creating Task..."
: "Finish Adding Task"}
</button>
) : (
<>
Expand Down
2 changes: 1 addition & 1 deletion rise-dc-app/src/services/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
GroceryList,
} from "../shared/types";

const API_URL = "http://localhost:3000"; // TODO: change this to be actual backend URL once deployed
const API_URL = ""; // TODO: change this to be actual backend URL once deployed

export type RecipePayload = Omit<Recipe, "id">;
export type IngredientPayload = Omit<Ingredient, "id">;
Expand Down
14 changes: 7 additions & 7 deletions rise-dc-app/src/services/taskService.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import { TaskCategory } from "../scheduling_components/quiet_hobbies/types";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify TaskCategory definitions and usages across the codebase
echo "=== TaskCategory in quiet_hobbies/types.ts ==="
find rise-dc-app -name "quiet_hobbies" -type d 2>/dev/null | head -1
rg -n "TaskCategory" rise-dc-app/src/scheduling_components/quiet_hobbies/types.ts 2>/dev/null || echo "File not found or no matches"

echo -e "\n=== TaskCategory in shared/types ==="
find rise-dc-app/src/shared/types -type f -name "*.ts" 2>/dev/null | head -5
rg -n "TaskCategory" rise-dc-app/src/shared/types/ 2>/dev/null || echo "No matches in shared/types"

echo -e "\n=== All TaskCategory imports ==="
rg -n "import.*TaskCategory" rise-dc-app/src/ 2>/dev/null || echo "No imports found"

echo -e "\n=== Repository structure check ==="
ls -la rise-dc-app/src/ 2>/dev/null | head -20

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 1666


🏁 Script executed:

#!/bin/bash
echo "=== quiet_hobbies/types.ts - TaskCategory definition ==="
sed -n '30,40p' rise-dc-app/src/scheduling_components/quiet_hobbies/types.ts

echo -e "\n=== shared/types/index.ts - TaskCategory definition ==="
sed -n '105,120p' rise-dc-app/src/shared/types/index.ts

echo -e "\n=== Full context in quiet_hobbies/types.ts (lines 25-45) ==="
sed -n '25,45p' rise-dc-app/src/scheduling_components/quiet_hobbies/types.ts

echo -e "\n=== Full context in shared/types/index.ts (lines 100-125) ==="
sed -n '100,125p' rise-dc-app/src/shared/types/index.ts

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 1671


🏁 Script executed:

#!/bin/bash
echo "=== taskService.ts full content ==="
cat -n rise-dc-app/src/services/taskService.ts

echo -e "\n=== Check where taskService functions are called ==="
rg -n "taskService\|from.*taskService" rise-dc-app/src/ -A 2 | head -40

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 1299


🏁 Script executed:

#!/bin/bash
echo "=== Task interface in shared/types ==="
rg -n "export interface Task" rise-dc-app/src/shared/types/index.ts -A 10

Repository: Hack4Impact-UMD/microsoft-hackathon-fall25

Length of output: 355


Fix TaskCategory import to use shared/types enum consistently.

taskService.ts imports TaskCategory from quiet_hobbies/types, which defines it as a union type with "Quiet Hobbies" (with space) and "Miscellaneous". However, the Task interface from shared/types uses the TaskCategory enum with QuietHobbies (no space) and Misc as the key. This causes a type mismatch in createTask(): the function accepts TaskPayload with the union type but returns Task with the enum type, creating incompatible string literal values.

Update the import to use the TaskCategory enum from shared/types:

-import { TaskCategory } from "../scheduling_components/quiet_hobbies/types";
+import { TaskCategory } from "../shared/types";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { TaskCategory } from "../scheduling_components/quiet_hobbies/types";
import { TaskCategory } from "../shared/types";
🤖 Prompt for AI Agents
In rise-dc-app/src/services/taskService.ts around line 1, the file currently
imports TaskCategory from quiet_hobbies/types (a union of string literals like
"Quiet Hobbies" and "Miscellaneous") which conflicts with the Task interface
that expects the TaskCategory enum from shared/types (QuietHobbies, Misc).
Replace the import to pull TaskCategory from shared/types (the enum) and update
any usages in createTask() (and related payload typing) to use the enum values
rather than the union string literals so the returned Task matches the
shared/types TaskCategory enum.

import { Image, Task } from "../shared/types";
import { Image, Task, TimeSlot } from "../shared/types";
import { delJSON, getJSON, postJSON } from "./service";

export interface TaskPayload {
userId: string;
icon: string;
name: string;
image?: Image;
startTime: string;
endTime: string;
startTime: TimeSlot;
endTime: TimeSlot;
category: TaskCategory;
}

export async function createTask(payload: TaskPayload): Promise<Task> {
return postJSON<Task, TaskPayload>("/api/tasks", payload);
return postJSON<Task, TaskPayload>("/api/scheduler/tasks", payload);
}

export async function getTask(id: string): Promise<Task> {
return getJSON<Task>(`/api/tasks/${id}`);
return getJSON<Task>(`/api/scheduler/tasks/${id}`);
}

export async function listTasks(): Promise<Task[]> {
return getJSON<Task[]>("/api/tasks");
return getJSON<Task[]>("/api/scheduler/tasks");
}

export async function deleteTask(
id: string,
): Promise<{ message: string; id: string }> {
return delJSON<{ message: string; id: string }>(`/api/tasks/${id}`);
return delJSON<{ message: string; id: string }>(`/api/scheduler/tasks/${id}`);
}
Loading