Skip to content

Commit 6d85f8d

Browse files
committed
feat (add where poll): add delete func, polish UI, add translation
1 parent 5027a7a commit 6d85f8d

File tree

8 files changed

+319
-37
lines changed

8 files changed

+319
-37
lines changed

apps/tumeet/messages/en.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3795,6 +3795,18 @@
37953795
"more_options": "more options",
37963796
"created_by": "by",
37973797
"filter": "Filter",
3798-
"sort": "Sort"
3798+
"sort": "Sort",
3799+
"select_options": "Select options to vote",
3800+
"create_new_option": "Add your own option...",
3801+
"add": "Add",
3802+
"confirm": "Confirm",
3803+
"cancel": "Cancel",
3804+
"voted_for": "You voted for",
3805+
"vote_again": "Vote again",
3806+
"vote_for": "Vote for",
3807+
"vote_confirm": "Are you sure you want to submit your vote for these options?",
3808+
"delete_option": "Delete Option",
3809+
"delete_option_confirm": "Are you sure you want to delete this option? This action cannot be undone.",
3810+
"delete": "Delete"
37993811
}
38003812
}

apps/tumeet/messages/vi.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2068,7 +2068,19 @@
20682068
"more_options": "tùy chọn khác",
20692069
"created_by": "bởi",
20702070
"filter": "Lọc",
2071-
"sort": "Sắp xếp"
2071+
"sort": "Sắp xếp",
2072+
"select_options": "Chọn các tùy chọn để bình chọn",
2073+
"create_new_option": "Thêm tùy chọn của bạn...",
2074+
"add": "Thêm",
2075+
"confirm": "Xác nhận",
2076+
"cancel": "Hủy",
2077+
"voted_for": "Bạn đã bình chọn cho",
2078+
"vote_again": "Bình chọn lại",
2079+
"vote_for": "Bình chọn cho",
2080+
"vote_confirm": "Bạn có chắc chắn muốn gửi phiếu bầu cho những tùy chọn này không?",
2081+
"delete_option": "Xóa Tùy chọn",
2082+
"delete_option_confirm": "Bạn có chắc chắn muốn xóa tùy chọn này không? Hành động này không thể hoàn tác.",
2083+
"delete": "Xóa"
20722084
},
20732085
"ai_chat": {
20742086
"welcome_to": "Chào mừng đến với",

apps/web/messages/en.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3814,6 +3814,20 @@
38143814
"more_options": "more options",
38153815
"created_by": "by",
38163816
"filter": "Filter",
3817-
"sort": "Sort"
3817+
"sort": "Sort",
3818+
"select_options": "Select options to vote",
3819+
"create_new_option": "Add your own option...",
3820+
"add": "Add",
3821+
"confirm": "Confirm",
3822+
"cancel": "Cancel",
3823+
"voted_for": "You voted for",
3824+
"vote_again": "Vote again",
3825+
"vote_for": "Vote for",
3826+
"vote_confirm": "Are you sure you want to submit your vote for these options?",
3827+
"delete_option": "Delete Option",
3828+
"delete_option_confirm": "Are you sure you want to delete this option? This action cannot be undone.",
3829+
"delete": "Delete",
3830+
"enable_where_poll_desc": "You can enable 'Where to meet' voting for your plan.",
3831+
"enable_where_poll": "Enable 'Where to meet' voting"
38183832
}
38193833
}

apps/web/messages/vi.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2076,7 +2076,21 @@
20762076
"more_options": "tùy chọn khác",
20772077
"created_by": "bởi",
20782078
"filter": "Lọc",
2079-
"sort": "Sắp xếp"
2079+
"sort": "Sắp xếp",
2080+
"select_options": "Chọn các tùy chọn để bình chọn",
2081+
"create_new_option": "Thêm tùy chọn của bạn...",
2082+
"add": "Thêm",
2083+
"confirm": "Xác nhận",
2084+
"cancel": "Hủy",
2085+
"voted_for": "Bạn đã bình chọn cho",
2086+
"vote_again": "Bình chọn lại",
2087+
"vote_for": "Bình chọn cho",
2088+
"vote_confirm": "Bạn có chắc chắn muốn gửi phiếu bầu cho những tùy chọn này không?",
2089+
"delete_option": "Xóa Tùy chọn",
2090+
"delete_option_confirm": "Bạn có chắc chắn muốn xóa tùy chọn này không? Hành động này không thể hoàn tác.",
2091+
"delete": "Xóa",
2092+
"enable_where_poll_desc": "Bạn có thể bật tính năng bình chọn 'Nơi gặp mặt' cho kế hoạch của mình.",
2093+
"enable_where_poll": "Bật tính năng bình chọn 'Nơi gặp mặt'"
20802094
},
20812095
"ai_chat": {
20822096
"welcome_to": "Chào mừng đến với",

apps/web/src/app/[locale]/(marketing)/meet-together/plans/[planId]/plan-details-client.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export default function PlanDetailsClient({
140140
}, [plan.id, resolvedTheme]);
141141

142142
return (
143-
<div className="flex w-full max-w-6xl flex-col gap-6 p-4 text-foreground md:px-8 lg:gap-14 lg:px-14">
143+
<div className="flex w-full max-w-6xl flex-col gap-6 p-4 text-foreground md:px-6 lg:gap-14 lg:px-14">
144144
<div className="flex w-full flex-col items-center">
145145
<UtilityButtons
146146
plan={plan}
@@ -152,7 +152,7 @@ export default function PlanDetailsClient({
152152
<p className="my-4 flex max-w-xl items-center gap-2 text-center text-2xl leading-tight! font-semibold md:mb-4 lg:text-3xl">
153153
{plan.name} <EditPlanDialog plan={plan} />
154154
</p>
155-
<div className="mt-8 grid w-full items-center justify-between gap-4 md:grid-cols-2 lg:grid-cols-3">
155+
<div className="mt-8 grid w-full grid-cols-1 items-start justify-between gap-4 md:grid-cols-3 md:items-center">
156156
<PlanLogin
157157
plan={plan}
158158
timeblocks={[]}

apps/web/src/app/[locale]/(marketing)/meet-together/plans/[planId]/plan-details-polls.tsx

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@ import type {
66
MeetTogetherPlan,
77
} from '@tuturuuu/types/primitives/MeetTogetherPlan';
88
import type { User } from '@tuturuuu/types/primitives/User';
9+
import {
10+
Accordion,
11+
AccordionContent,
12+
AccordionItem,
13+
AccordionTrigger,
14+
} from '@tuturuuu/ui/accordion';
15+
import { useIsMobile } from '@tuturuuu/ui/hooks/use-mobile';
916
import MultipleChoiceVote from '@tuturuuu/ui/legacy/tumeet/multiple-choice-vote';
17+
import { useTranslations } from 'next-intl';
1018

1119
interface PlanDetailsPollsProps {
1220
plan: MeetTogetherPlan;
@@ -21,6 +29,52 @@ export default function PlanDetailsPolls({
2129
platformUser,
2230
polls,
2331
}: PlanDetailsPollsProps) {
32+
const t = useTranslations('ws-polls');
33+
const isMobile = useIsMobile();
34+
if (!plan.where_to_meet && !isCreator) {
35+
return null; // Don't render anything if "where to meet" is not enabled and user is not creator
36+
}
37+
38+
if (isMobile) {
39+
return (
40+
<Accordion
41+
type="single"
42+
collapsible
43+
className="order-first col-span-full w-full"
44+
defaultValue="item-1"
45+
>
46+
<AccordionItem value="item-1" className="w-full">
47+
<AccordionTrigger className="pl-3 text-lg">
48+
{t('plural')}
49+
</AccordionTrigger>
50+
<AccordionContent>
51+
<PlanDetailsPollContent
52+
plan={plan}
53+
isCreator={isCreator}
54+
platformUser={platformUser}
55+
polls={polls}
56+
/>
57+
</AccordionContent>
58+
</AccordionItem>
59+
</Accordion>
60+
);
61+
}
62+
return (
63+
<PlanDetailsPollContent
64+
plan={plan}
65+
isCreator={isCreator}
66+
platformUser={platformUser}
67+
polls={polls}
68+
/>
69+
);
70+
}
71+
function PlanDetailsPollContent({
72+
plan,
73+
isCreator,
74+
platformUser,
75+
polls,
76+
}: PlanDetailsPollsProps) {
77+
const t = useTranslations('ws-polls');
2478
const { user: guestUser } = useTimeBlocking();
2579

2680
const user = guestUser ?? platformUser;
@@ -36,7 +90,7 @@ export default function PlanDetailsPolls({
3690
const wherePoll = polls?.polls?.[0]; // Assuming the first poll is the "where to meet" poll
3791

3892
const onVote = async (pollId: string, optionIds: string[]) => {
39-
await fetch(`/api/meet-together/plans/${plan.id}/poll/vote`, {
93+
const res = await fetch(`/api/meet-together/plans/${plan.id}/poll/vote`, {
4094
method: 'POST',
4195
body: JSON.stringify({
4296
pollId,
@@ -46,6 +100,10 @@ export default function PlanDetailsPolls({
46100
}),
47101
headers: { 'Content-Type': 'application/json' },
48102
});
103+
if (!res.ok) {
104+
console.error('Failed to vote in poll');
105+
return;
106+
}
49107
};
50108

51109
const onAddOption = async (pollId: string, value: string) => {
@@ -59,25 +117,58 @@ export default function PlanDetailsPolls({
59117
}),
60118
headers: { 'Content-Type': 'application/json' },
61119
});
120+
if (!res.ok) {
121+
console.error('Failed to add poll option');
122+
return;
123+
}
124+
};
62125

63-
console.log('Option added:', res);
126+
const onDeleteOption = async (pollId: string, optionId: string) => {
127+
const res = await fetch(
128+
`/api/meet-together/plans/${plan.id}/poll/option/${optionId}`,
129+
{
130+
method: 'DELETE',
131+
body: JSON.stringify({
132+
pollId,
133+
userType, // 'PLATFORM' or 'GUEST'
134+
guestId: userType === 'GUEST' ? user?.id : undefined,
135+
}),
136+
headers: { 'Content-Type': 'application/json' },
137+
}
138+
);
139+
if (!res.ok) {
140+
console.error('Failed to delete poll option');
141+
}
64142
};
65143

66-
if (!plan.where_to_meet && !isCreator) {
67-
return null; // Don't render anything if "where to meet" is not enabled and user is not creator
68-
}
144+
const onToggleWhereToMeet = async (enable: boolean) => {
145+
const res = await fetch(`/api/meet-together/plans/${plan.id}/where-poll`, {
146+
method: 'PATCH',
147+
body: JSON.stringify({
148+
planId: plan.id,
149+
whereToMeet: enable,
150+
}),
151+
headers: { 'Content-Type': 'application/json' },
152+
});
153+
154+
if (!res.ok) {
155+
console.error('Failed to update "where to meet" setting');
156+
}
157+
};
69158

70159
return (
71-
<div className="sticky top-16 z-10 self-start rounded-lg border px-2 py-4 md:px-4">
160+
<div className="top-16 z-10 self-start rounded-lg border px-2 py-4 md:sticky md:px-4">
72161
{plan.where_to_meet && wherePoll ? (
73162
<MultipleChoiceVote
163+
isCreator={isCreator}
74164
pollName={wherePoll.name}
75165
pollId={wherePoll.id}
76166
options={wherePoll.options}
77167
currentUserId={currentUserId}
78168
isDisplayMode={userType === 'DISPLAY'}
79169
onAddOption={onAddOption}
80170
onVote={onVote}
171+
onDeleteOption={onDeleteOption}
81172
/>
82173
) : (
83174
isCreator && (
@@ -87,10 +178,7 @@ export default function PlanDetailsPolls({
87178
</p>
88179
<button
89180
className="rounded bg-dynamic-blue px-4 py-2 font-medium text-white shadow transition hover:bg-dynamic-blue/80"
90-
onClick={async () => {
91-
// call your backend here to enable where_to_meet
92-
// await onUpdateWhereToMeet(true);
93-
}}
181+
onClick={async () => await onToggleWhereToMeet(true)}
94182
>
95183
Enable "Where to meet" voting
96184
</button>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {
2+
createAdminClient,
3+
createClient,
4+
} from '@tuturuuu/supabase/next/server';
5+
import { NextResponse } from 'next/server';
6+
7+
export async function PATCH(req: Request) {
8+
const sbAdmin = await createAdminClient();
9+
const supabase = await createClient();
10+
11+
const { planId, whereToMeet } = await req.json();
12+
13+
// 1. Authenticate user (optional but recommended)
14+
const {
15+
data: { user },
16+
} = await supabase.auth.getUser();
17+
if (!user) {
18+
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
19+
}
20+
21+
// 2. Update where_to_meet field
22+
const { data: updatedPlan, error: updateError } = await sbAdmin
23+
.from('meet_together_plans')
24+
.update({ where_to_meet: whereToMeet })
25+
.eq('id', planId)
26+
.select('id, where_to_meet')
27+
.single();
28+
29+
if (updateError || !updatedPlan) {
30+
return NextResponse.json(
31+
{ message: 'Error updating plan', error: updateError },
32+
{ status: 500 }
33+
);
34+
}
35+
36+
// 3. If enabling where_to_meet, ensure the "Where to Meet Poll" exists
37+
let pollId: string | null = null;
38+
if (whereToMeet) {
39+
// Try to find an existing poll for this plan with correct name
40+
const { data: poll, error: pollFetchError } = await sbAdmin
41+
.from('polls')
42+
.select('id')
43+
.eq('plan_id', planId)
44+
.eq('name', 'where')
45+
.maybeSingle();
46+
47+
if (pollFetchError) {
48+
return NextResponse.json(
49+
{ message: 'Error checking poll', error: pollFetchError },
50+
{ status: 500 }
51+
);
52+
}
53+
54+
if (poll?.id) {
55+
// Already exists, reuse
56+
pollId = poll.id;
57+
} else {
58+
// Not exist, create it
59+
const { data: newPoll, error: createPollError } = await sbAdmin
60+
.from('polls')
61+
.insert({
62+
plan_id: planId,
63+
creator_id: user.id,
64+
name: 'where',
65+
})
66+
.select('id')
67+
.single();
68+
69+
if (createPollError) {
70+
return NextResponse.json(
71+
{
72+
message: 'Plan updated, but failed to create poll',
73+
error: createPollError,
74+
},
75+
{ status: 200 }
76+
);
77+
}
78+
pollId = newPoll?.id;
79+
}
80+
}
81+
82+
return NextResponse.json({
83+
id: planId,
84+
where_to_meet: updatedPlan.where_to_meet,
85+
pollId,
86+
message: 'Plan updated successfully',
87+
});
88+
}

0 commit comments

Comments
 (0)