diff --git a/backend/controllers/routineController.js b/backend/controllers/routineController.js
index dcc47005..ba437d56 100644
--- a/backend/controllers/routineController.js
+++ b/backend/controllers/routineController.js
@@ -286,12 +286,13 @@ export const updateRoutine = async (req, res) => {
}
// fetch updated routine details
- const { name, description, items } = req.body;
+ const { name, description, items, isPublic } = req.body;
const updates = {
...(name && { name }),
...(description && { description }),
...(items && { items }),
+ ...(isPublic !== undefined && { isPublic }),
};
const routineId = req.params.id;
@@ -430,10 +431,10 @@ export const getPublicRoutine = async (req, res) => {
try {
const routineId = req.params.id;
const routine = await Routine.findById(routineId).populate("items.taskId");
- if (!routine) {
- return res.status(404).json({
+ if (!routine || !routine.isPublic) {
+ return res.status(403).json({
success: false,
- message: "Routine not found",
+ message: "Routine not found or not public",
});
}
return res.status(200).json({ success: true, routine });
diff --git a/backend/src/models/Routine.js b/backend/src/models/Routine.js
index 3ba7c2a5..6fd5417f 100644
--- a/backend/src/models/Routine.js
+++ b/backend/src/models/Routine.js
@@ -12,6 +12,10 @@ const routineSchema = mongoose.Schema(
required: true,
trim: true
},
+ isPublic: {
+ type: Boolean,
+ default: false,
+ },
description: {
type: String,
required: false,
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index d8e2a94c..b7753928 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -21,6 +21,7 @@ import ErrorBoundary from "./components/ErrorBoundary.jsx";
import PageTransition from "./components/PageTransition.jsx";
import ShareRoutine from "./pages/ShareRoutine.jsx";
import DailyJournal from "./pages/DailyJournal.jsx";
+import ForgeMode from "./pages/ForgeMode.jsx";
const AuthLayout = ({ children }) => (
@@ -38,9 +39,9 @@ const AnimatedRoutes = () => {
path="/"
element={
-
-
-
+
+
+
}
/>
@@ -48,9 +49,11 @@ const AnimatedRoutes = () => {
path="/login"
element={
-
-
-
+
+
+
+
+
}
/>
@@ -58,18 +61,22 @@ const AnimatedRoutes = () => {
path="/signup"
element={
-
-
-
+
+
+
+
+
}
/>
-
-
+
+
+
+
+
}
/>
{
}
/>
+
+
+
+
+
+ }
+ />
{
element={
-
+
+
+
}
@@ -142,7 +161,9 @@ const AnimatedRoutes = () => {
path="/forge"
element={
-
+
+
+
}
/>
@@ -150,11 +171,28 @@ const AnimatedRoutes = () => {
path="/focus"
element={
-
+
+
+
}
/>
- } />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
@@ -166,104 +204,7 @@ const App = () => {
-
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
- } />
- } />
-
+
diff --git a/frontend/src/components/Routine/RoutineCard.jsx b/frontend/src/components/Routine/RoutineCard.jsx
index bc84857b..9858d4d6 100644
--- a/frontend/src/components/Routine/RoutineCard.jsx
+++ b/frontend/src/components/Routine/RoutineCard.jsx
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState } from "react";
-import { MoreVertical, Trash2, Share2, Copy, Download, Loader2 } from "lucide-react";
+import { MoreVertical, Trash2, Share2, Copy, Download, Loader2, EyeOff } from "lucide-react";
import RoutineOverviewModal from "./RoutineOverviewModal";
import api from "../../api/axios.js";
import { exportRoutineToPDF, generateRoutineSummary } from "../../utils/routineExport.js";
@@ -36,6 +36,10 @@ export default function RoutineCard({
e.stopPropagation();
setShowMenu(false);
try {
+ if (!routine.isPublic) {
+ await api.put(`/routines/${routine._id}`, { isPublic: true });
+ await fetchRoutines();
+ }
const shareUrl = `${window.location.origin}/share/routine/${routine._id}`;
await navigator.clipboard.writeText(shareUrl);
triggerToast("Share Link Copied", "The public routine link was copied to clipboard.");
@@ -45,6 +49,19 @@ export default function RoutineCard({
}
};
+ const handleRevokeLink = async (e) => {
+ e.stopPropagation();
+ setShowMenu(false);
+ try {
+ await api.put(`/routines/${routine._id}`, { isPublic: false });
+ await fetchRoutines();
+ triggerToast("Link Revoked", "The routine is now private.");
+ } catch (err) {
+ console.error(err);
+ alert("Failed to revoke share link");
+ }
+ };
+
const handleCopySummary = async (e) => {
e.stopPropagation();
setShowMenu(false);
@@ -414,6 +431,15 @@ export default function RoutineCard({
Copy Share Link
+ {routine.isPublic && (
+
+ )}