Skip to content
Merged
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
148 changes: 148 additions & 0 deletions apps/page/components/roadmap/TriageSubmissionModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { Dialog } from "@headlessui/react";
import { XIcon } from "@heroicons/react/outline";
import { useState } from "react";
import { httpPost } from "../../utils/http";

interface TriageSubmissionModalProps {
isOpen: boolean;
onClose: () => void;
boardId: string;
onSuccess?: () => void;
}

export default function TriageSubmissionModal({
isOpen,
onClose,
boardId,
onSuccess,
}: TriageSubmissionModalProps) {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setError("");

try {
await httpPost({
url: "/api/roadmap/submit-triage",
data: {
board_id: boardId,
title: title.trim(),
description: description.trim() || null,
},
});

setTitle("");
setDescription("");
onSuccess?.();
onClose();
} catch (err) {
setError(
err instanceof Error
? err.message
: "Something went wrong. Please try again."
);
} finally {
setIsLoading(false);
}
};

const handleClose = () => {
if (isLoading) return;
setTitle("");
setDescription("");
setError("");
onClose();
};

return (
<Dialog open={isOpen} onClose={handleClose} className="relative z-50">
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />

<div className="fixed inset-0 flex items-center justify-center p-4">
<Dialog.Panel className="mx-auto max-w-lg w-full rounded-lg bg-white p-6 shadow-lg dark:bg-gray-800">
<div className="flex items-center justify-between mb-4">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-gray-100">
Submit Your Idea
</Dialog.Title>
<button
type="button"
onClick={handleClose}
disabled={isLoading}
aria-label="Close modal"
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-50 disabled:cursor-not-allowed"
>
<XIcon className="h-5 w-5" />
</button>
</div>

<form onSubmit={handleSubmit}>
<div className="mb-4">
<label
htmlFor="title"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
>
Title *
</label>
<input
type="text"
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
maxLength={200}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="Brief description of your idea"
/>
</div>

<div className="mb-4">
<label
htmlFor="description"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
>
Description (optional)
</label>
<textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={5}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="Provide more details about your idea..."
/>
</div>

{error && (
<div className="mb-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md">
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
</div>
)}

<div className="flex justify-end gap-3">
<button
type="button"
onClick={handleClose}
disabled={isLoading}
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
>
Cancel
</button>
<button
type="submit"
disabled={isLoading || !title.trim()}
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed dark:bg-indigo-700 dark:hover:bg-indigo-600"
>
{isLoading ? "Submitting..." : "Submit Idea"}
</button>
</div>
</form>
</Dialog.Panel>
</div>
</Dialog>
);
}
47 changes: 39 additions & 8 deletions apps/page/pages/_sites/[site]/roadmap/[roadmap_slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Footer from "../../../../components/footer";
import PageHeader from "../../../../components/page-header";
import SeoTags from "../../../../components/seo-tags";
import VisitorAuthModal from "../../../../components/visitor-auth-modal";
import TriageSubmissionModal from "../../../../components/roadmap/TriageSubmissionModal";
import { usePageTheme } from "../../../../hooks/usePageTheme";
import { useVisitorAuth } from "../../../../hooks/useVisitorAuth";
import {
Expand Down Expand Up @@ -53,6 +54,7 @@ export default function RoadmapPage({
const [selectedItem, setSelectedItem] = useState<RoadmapItem | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [isTriageModalOpen, setIsTriageModalOpen] = useState(false);
const [votes, setVotes] = useState<
Record<string, { count: number; voted: boolean }>
>({});
Expand All @@ -78,6 +80,18 @@ export default function RoadmapPage({
setSelectedItem(null);
};

const handleContributeClick = () => {
if (!visitor) {
setIsAuthModalOpen(true);
return;
}
setIsTriageModalOpen(true);
};

const handleTriageSuccess = () => {
alert("Thank you for your contribution! We'll review your idea soon.");
};

const handleVote = async (itemId: string) => {
if (!visitor) {
setIsAuthModalOpen(true);
Expand Down Expand Up @@ -194,14 +208,24 @@ export default function RoadmapPage({
{/* Roadmap Header */}
<div className="pb-6 text-left">
<div className="max-w-5xl mx-auto">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white text-left">
{board.title}
</h1>
{board.description && (
<p className="mt-2 text-gray-600 dark:text-gray-400 text-left">
{board.description}
</p>
)}
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div className="flex-1">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white text-left">
{board.title}
</h1>
{board.description && (
<p className="mt-2 text-gray-600 dark:text-gray-400 text-left">
{board.description}
</p>
)}
</div>
<button
onClick={handleContributeClick}
className="w-full md:w-auto flex-shrink-0 px-4 py-2 bg-indigo-600 hover:bg-indigo-700 dark:bg-indigo-700 dark:hover:bg-indigo-600 text-white text-sm font-medium rounded-lg transition-colors shadow-sm"
>
Contribute Idea
</button>
</div>
</div>
</div>

Expand Down Expand Up @@ -546,6 +570,13 @@ export default function RoadmapPage({
isOpen={isAuthModalOpen}
onClose={() => setIsAuthModalOpen(false)}
/>

<TriageSubmissionModal
isOpen={isTriageModalOpen}
onClose={() => setIsTriageModalOpen(false)}
boardId={board.id}
onSuccess={handleTriageSuccess}
/>
</>
);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/page/pages/api/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async function handler(
);

res.status(200).json(postsWithUrl);
} catch (e: Error | any) {
} catch (e: unknown) {
console.log("Failed to fetch posts [Error]", e);
res.status(200).json([]);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/page/pages/api/latest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ async function handler(
});

res.status(200).json(postsWithUrl[0] ?? null);
} catch (e: Error | any) {
} catch (e: unknown) {
console.log("Failed to fetch latest post [Error]", e);
res.status(404).json(null);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/page/pages/api/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ ${post.content}
});

res.status(200).send(markdown);
} catch (e: Error | any) {
} catch (e: unknown) {
console.log("Failed to fetch posts [changes.md] [Error]", e);
res.status(200).send("## No posts Found");
}
Expand Down
7 changes: 5 additions & 2 deletions apps/page/pages/api/notifications/subscribe-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,12 @@ async function handler(
});

res.status(200).json({ success: true });
} catch (e: Error | any) {
} catch (e: unknown) {
console.log("notifications/email: [Error]", e);
res.status(400).json({ success: false, message: e.message ?? String(e) });
res.status(400).json({
success: false,
message: e instanceof Error ? e.message : String(e),
});
}
} else {
res.setHeader("Allow", "POST");
Expand Down
2 changes: 1 addition & 1 deletion apps/page/pages/api/pa/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ async function pageAnalyticsView(
}

res.status(200).json({ success: true });
} catch (e: Error | any) {
} catch (e: unknown) {
console.log("pageAnalyticsView [Error]", e);
res.status(200).json({ success: true });
}
Expand Down
2 changes: 1 addition & 1 deletion apps/page/pages/api/pinned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async function handler(
});

res.status(200).json(postsWithUrl[0]);
} catch (e: Error | any) {
} catch (e: unknown) {
console.log("Failed to fetch pinned post [Error]", e);
res.status(404).json(null);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/page/pages/api/post/[id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ async function handler(
url: getPostUrl(pageUrl, post),
plain_text_content: convertMarkdownToPlainText(post.content),
});
} catch (e: Error | any) {
} catch (e: unknown) {
console.log("Failed to fetch post [Error]", e);
res.status(404).json(null);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/page/pages/api/post/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default async function reactToPost(
}

res.status(200).json({ success: true });
} catch (e: Error | any) {
} catch (e: unknown) {
console.log("reactToPost [Error]", e);
res.status(200).json({ success: false });
}
Expand Down
2 changes: 1 addition & 1 deletion apps/page/pages/api/post/reactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default async function getPostReactions(
: null,
user,
});
} catch (e: Error | any) {
} catch (e: unknown) {
console.log("getPostReactions [Error]", e);
res.status(200).json({ success: false, aggregate: null, user: null });
}
Expand Down
9 changes: 7 additions & 2 deletions apps/page/pages/api/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ async function handler(
.order("publication_date", { ascending: false });

res.status(200).json(posts as Array<IPost>);
} catch (e: Error | any) {
res.status(500).json({ error: { statusCode: 500, message: e.message } });
} catch (e: unknown) {
res.status(500).json({
error: {
statusCode: 500,
message: e instanceof Error ? e.message : String(e),
},
});
}
}

Expand Down
Loading