Skip to content

Commit 94df84b

Browse files
committed
Update board settings
1 parent 44dd746 commit 94df84b

File tree

7 files changed

+125
-58
lines changed

7 files changed

+125
-58
lines changed

apps/web/components/layout/page.component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default function Page({
2222
containerClassName,
2323
}: {
2424
title: string;
25-
subtitle?: string;
25+
subtitle?: string | JSX.Element;
2626
buttons?: JSX.Element | JSX.Element[];
2727
menuItems?: JSX.Element | JSX.Element[];
2828
children?: JSX.Element | JSX.Element[];

apps/web/pages/api/billing/enable-email-notifications.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ const enableEmailNotifications = withAuth<{ status: string }>(
1818
});
1919
}
2020

21-
const { stripe_subscription_id, stripe_subscription } =
21+
const { stripe_subscription_id, stripe_subscription, pro_gifted } =
2222
await getUserById(user.id);
2323

24+
if (pro_gifted) {
25+
return res.status(200).json({ status: "ok" });
26+
}
27+
2428
if (
2529
!stripe_subscription ||
2630
(stripe_subscription as unknown as Stripe.Subscription)?.status ===

apps/web/pages/pages/[page_id]/roadmap/[board_id].tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { GlobeIcon, LockClosedIcon } from "@heroicons/react/outline";
12
import { InferGetServerSidePropsType } from "next";
23
import { useRouter } from "next/router";
34
import { type JSX } from "react";
@@ -107,9 +108,25 @@ export default function RoadmapBoardDetails({
107108
<>
108109
<Page
109110
title={board.title}
110-
subtitle={`${board.is_public ? "Public" : "Private"} Roadmap • ${
111-
board.description || "No description"
112-
}`}
111+
subtitle={
112+
<div className="flex items-center gap-3">
113+
{board.is_public ? (
114+
<div className="flex items-center gap-1.5 text-green-600 dark:text-green-400 bg-green-50 dark:bg-green-900/20 px-2.5 py-1 rounded-full text-sm font-medium">
115+
<GlobeIcon className="h-4 w-4" />
116+
Public
117+
</div>
118+
) : (
119+
<div className="flex items-center gap-1.5 text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 px-2.5 py-1 rounded-full text-sm font-medium">
120+
<LockClosedIcon className="h-4 w-4" />
121+
Private
122+
</div>
123+
)}
124+
<span className="text-gray-500 dark:text-gray-400"></span>
125+
<span className="text-gray-600 dark:text-gray-400">
126+
{board.description || "No description"}
127+
</span>
128+
</div>
129+
}
113130
showBackButton={true}
114131
backRoute={`/pages/${page_id}/roadmap`}
115132
containerClassName="lg:pb-0"

apps/web/pages/pages/[page_id]/roadmap/[board_id]/settings.tsx

Lines changed: 96 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,21 @@ import {
33
getCategoryColorOptions,
44
ROADMAP_COLORS,
55
} from "@changes-page/utils";
6-
import { MenuIcon } from "@heroicons/react/outline";
6+
import { GlobeIcon, LockClosedIcon, MenuIcon } from "@heroicons/react/outline";
77
import { PencilIcon, PlusIcon, TrashIcon } from "@heroicons/react/solid";
88
import { InferGetServerSidePropsType } from "next";
99
import { useRouter } from "next/router";
1010
import { useEffect, useMemo, useState, type JSX } from "react";
11+
import SwitchComponent from "../../../../../components/forms/switch.component";
1112
import AuthLayout from "../../../../../components/layout/auth-layout.component";
1213
import Page from "../../../../../components/layout/page.component";
14+
import { createAuditLog } from "../../../../../utils/auditLog";
1315
import usePageSettings from "../../../../../utils/hooks/usePageSettings";
1416
import { getPageUrl } from "../../../../../utils/hooks/usePageUrl";
1517
import { withSupabase } from "../../../../../utils/supabase/withSupabase";
1618
import { createOrRetrievePageSettings } from "../../../../../utils/useDatabase";
1719
import { getPage } from "../../../../../utils/useSSR";
1820
import { useUserData } from "../../../../../utils/useUser";
19-
import { createAuditLog } from "../../../../../utils/auditLog";
2021

2122
export const getServerSideProps = withSupabase(async (ctx, { supabase }) => {
2223
const { page_id } = ctx.params;
@@ -241,19 +242,19 @@ export default function BoardSettings({
241242
page_id: page_id,
242243
actor_id: user.id,
243244
action: `Updated Roadmap Board: ${boardForm.title}`,
244-
changes: {
245+
changes: {
245246
old: {
246247
title: board.title,
247248
description: board.description,
248249
slug: board.slug,
249-
is_public: board.is_public
250+
is_public: board.is_public,
250251
},
251252
new: {
252253
title: boardForm.title,
253254
description: boardForm.description,
254255
slug: boardForm.slug,
255-
is_public: boardForm.is_public
256-
}
256+
is_public: boardForm.is_public,
257+
},
257258
},
258259
});
259260

@@ -316,7 +317,7 @@ export default function BoardSettings({
316317

317318
if (error) throw error;
318319

319-
const oldCategory = boardCategories.find(cat => cat.id === categoryId);
320+
const oldCategory = boardCategories.find((cat) => cat.id === categoryId);
320321
const newCategoryData = {
321322
name: categoryToEdit.trim(),
322323
color: categoryColorToEdit,
@@ -342,9 +343,9 @@ export default function BoardSettings({
342343
page_id: page_id,
343344
actor_id: user.id,
344345
action: `Updated Roadmap Category: ${newCategoryData.name}`,
345-
changes: {
346+
changes: {
346347
old: oldCategory,
347-
new: { ...oldCategory, ...newCategoryData }
348+
new: { ...oldCategory, ...newCategoryData },
348349
},
349350
});
350351
}
@@ -380,7 +381,9 @@ export default function BoardSettings({
380381

381382
if (error) throw error;
382383

383-
const deletedCategory = boardCategories.find(cat => cat.id === categoryId);
384+
const deletedCategory = boardCategories.find(
385+
(cat) => cat.id === categoryId
386+
);
384387
setBoardCategories((prev) => prev.filter((cat) => cat.id !== categoryId));
385388

386389
// Create audit log for category deletion
@@ -446,7 +449,7 @@ export default function BoardSettings({
446449

447450
if (error) throw error;
448451

449-
const oldColumn = boardColumns.find(col => col.id === columnId);
452+
const oldColumn = boardColumns.find((col) => col.id === columnId);
450453
const newColumnName = columnToEdit.trim();
451454

452455
setBoardColumns((prev) =>
@@ -463,9 +466,9 @@ export default function BoardSettings({
463466
page_id: page_id,
464467
actor_id: user.id,
465468
action: `Updated Roadmap Column: ${newColumnName}`,
466-
changes: {
469+
changes: {
467470
old: oldColumn,
468-
new: { ...oldColumn, name: newColumnName }
471+
new: { ...oldColumn, name: newColumnName },
469472
},
470473
});
471474
}
@@ -503,7 +506,7 @@ export default function BoardSettings({
503506

504507
if (error) throw error;
505508

506-
const deletedColumn = boardColumns.find(col => col.id === columnId);
509+
const deletedColumn = boardColumns.find((col) => col.id === columnId);
507510
setBoardColumns((prev) => prev.filter((col) => col.id !== columnId));
508511

509512
// Create audit log for column deletion
@@ -570,9 +573,17 @@ export default function BoardSettings({
570573
page_id: page_id,
571574
actor_id: user.id,
572575
action: "Reordered Roadmap Columns",
573-
changes: {
574-
old_order: boardColumns.map(col => ({ id: col.id, name: col.name, position: col.position })),
575-
new_order: newColumns.map((col, index) => ({ id: col.id, name: col.name, position: index + 1 }))
576+
changes: {
577+
old_order: boardColumns.map((col) => ({
578+
id: col.id,
579+
name: col.name,
580+
position: col.position,
581+
})),
582+
new_order: newColumns.map((col, index) => ({
583+
id: col.id,
584+
name: col.name,
585+
position: index + 1,
586+
})),
576587
},
577588
});
578589

@@ -612,6 +623,30 @@ export default function BoardSettings({
612623
{/* Board Settings Tab */}
613624
{activeTab === "board" && (
614625
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
626+
<div className="mb-6">
627+
{boardForm.is_public ? (
628+
<div className="flex items-center gap-2 text-green-700 dark:text-green-400 border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900/20 rounded-lg p-3">
629+
<GlobeIcon className="h-5 w-5" />
630+
<div>
631+
<p className="font-medium">Public Board</p>
632+
<p className="text-sm text-green-600 dark:text-green-500">
633+
Anyone can view and vote on items in this board
634+
</p>
635+
</div>
636+
</div>
637+
) : (
638+
<div className="flex items-center gap-2 text-red-700 dark:text-red-400 border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-900/20 rounded-lg p-3">
639+
<LockClosedIcon className="h-5 w-5" />
640+
<div>
641+
<p className="font-medium">Private Board</p>
642+
<p className="text-sm text-red-600 dark:text-red-500">
643+
Only you and your team can see and manage this board
644+
</p>
645+
</div>
646+
</div>
647+
)}
648+
</div>
649+
615650
<form onSubmit={handleUpdateBoard} className="space-y-6">
616651
<div>
617652
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
@@ -682,39 +717,54 @@ export default function BoardSettings({
682717
{slugError}
683718
</p>
684719
)}
685-
<p className="text-sm text-gray-500 dark:text-gray-400">
686-
Public URL:{" "}
687-
<a
688-
href={`${getPageUrl(page, settings)}/roadmap/${
689-
boardForm.slug
690-
}`}
691-
target="_blank"
692-
rel="noopener noreferrer"
693-
className="text-indigo-600 dark:text-indigo-400 hover:text-indigo-500 dark:hover:text-indigo-300 underline"
694-
>
695-
{getPageUrl(page, settings)}/roadmap/{boardForm.slug}
696-
</a>
697-
</p>
720+
<div className="space-y-2">
721+
<p className="text-sm text-gray-500 dark:text-gray-400">
722+
Public URL:{" "}
723+
<a
724+
href={`${getPageUrl(page, settings)}/roadmap/${
725+
boardForm.slug
726+
}`}
727+
target="_blank"
728+
rel="noopener noreferrer"
729+
className="text-indigo-600 dark:text-indigo-400 hover:text-indigo-500 dark:hover:text-indigo-300 underline"
730+
>
731+
{getPageUrl(page, settings)}/roadmap/{boardForm.slug}
732+
</a>
733+
</p>
734+
{boardForm.is_public && (
735+
<p className="text-sm text-amber-600 dark:text-amber-400 flex items-start gap-1">
736+
<svg
737+
className="h-4 w-4 mt-0.5 flex-shrink-0"
738+
fill="currentColor"
739+
viewBox="0 0 20 20"
740+
>
741+
<path
742+
fillRule="evenodd"
743+
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
744+
clipRule="evenodd"
745+
/>
746+
</svg>
747+
Changing the slug will affect the public URL since this
748+
board is public. Users with bookmarks or shared links
749+
will need the updated URL.
750+
</p>
751+
)}
752+
</div>
698753
</div>
699754
</div>
700755

701756
<div>
702-
<label className="flex items-center">
703-
<input
704-
type="checkbox"
705-
checked={boardForm.is_public}
706-
onChange={(e) =>
707-
setBoardForm((prev) => ({
708-
...prev,
709-
is_public: e.target.checked,
710-
}))
711-
}
712-
className="rounded border-gray-300 dark:border-gray-600 text-indigo-600 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
713-
/>
714-
<span className="ml-2 text-sm text-gray-700 dark:text-gray-300">
715-
Make this board public (users can view and vote on items)
716-
</span>
717-
</label>
757+
<SwitchComponent
758+
title="Public Board"
759+
message="Make this board public (users can view and vote on items)"
760+
enabled={boardForm.is_public}
761+
onChange={(value) =>
762+
setBoardForm((prev) => ({
763+
...prev,
764+
is_public: value,
765+
}))
766+
}
767+
/>
718768
</div>
719769

720770
<div className="flex justify-end">

apps/web/utils/email.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
convertMarkdownToHtml,
55
convertMarkdownToPlainText,
66
} from "@changes-page/utils";
7-
import Stripe from "stripe";
87
import { getPageUrl, getPostUrl } from "./hooks/usePageUrl";
98
import inngestClient from "./inngest";
109
import { getUserById } from "./useDatabase";
@@ -62,11 +61,7 @@ export const sendPostEmailToSubscribers = async (
6261
}
6362

6463
const user = await getUserById(page.user_id);
65-
if (
66-
!user?.stripe_subscription ||
67-
(user?.stripe_subscription as unknown as Stripe.Subscription)?.status ===
68-
"canceled"
69-
) {
64+
if (!user?.has_active_subscription) {
7065
console.log(
7166
"sendPostEmailToSubscribers: Account not subscribed to email notifications"
7267
);

apps/web/utils/useDatabase.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ export const reportEmailUsage = async (
306306
`Update email usage count for user: ${user_id} to ${count}`,
307307
idempotencyKey
308308
);
309+
309310
try {
310311
await stripe.subscriptionItems.createUsageRecord(
311312
emailSubscriptionItem.id,

packages/supabase/migrations/18_roadmap.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ create table roadmap_boards (
55
title text not null,
66
description text,
77
slug text not null,
8-
is_public boolean not null default true,
8+
is_public boolean not null default false,
99
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
1010
updated_at timestamp with time zone default timezone('utc'::text, now()) not null
1111
);

0 commit comments

Comments
 (0)