Skip to content

Commit 2335ed4

Browse files
authored
Merge pull request #35 from Evently-Event-Management/subscribe
Subscribe
2 parents 9bb0a12 + 052038a commit 2335ed4

20 files changed

Lines changed: 1171 additions & 196 deletions

File tree

src/app/(home-app)/_components/EventCard.tsx

Lines changed: 106 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card"
22
import { Badge } from "@/components/ui/badge"
33
import { Button } from "@/components/ui/button"
44
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
5-
import { CalendarDays, MapPin, Tag, Share2, Bookmark, Clock, Users } from "lucide-react"
5+
import { CalendarDays, MapPin, Tag, Share2, Clock, Users, Bell, Loader2 } from "lucide-react"
66
import { cn, formatCurrency } from "@/lib/utils"
77
import { DiscountType } from "@/types/enums/discountType";
88
import { DiscountThumbnailDTO, EventThumbnailDTO } from "@/types/event";
99
import Image from "next/image";
1010
import { AspectRatio } from "@/components/ui/aspect-ratio";
1111
import Link from "next/link";
12-
import { MouseEvent } from "react";
12+
import { MouseEvent, useState, useEffect } from "react";
13+
import { toast } from "sonner";
14+
import { subscribeToEntity, unsubscribeFromEntity, checkSubscriptionStatus } from "@/lib/subscriptionUtils";
15+
import { EventShareDialog } from "./EventShareDialog";
1316

1417
interface EventCardProps {
1518
event: EventThumbnailDTO
@@ -95,15 +98,70 @@ export function EventCard({ event, className }: EventCardProps) {
9598

9699
const bestDiscount = getBestDiscount();
97100

98-
// Handler to stop propagation for nested interactive elements
99-
const handleButtonClick = (e: MouseEvent<HTMLButtonElement>) => {
101+
// State for subscription status and share dialog
102+
const [isSubscribed, setIsSubscribed] = useState(false);
103+
const [isLoading, setIsLoading] = useState(false);
104+
const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);
105+
106+
// Check subscription status on mount
107+
useEffect(() => {
108+
const checkStatus = async () => {
109+
setIsLoading(true);
110+
try {
111+
const status = await checkSubscriptionStatus(event.id, 'event');
112+
setIsSubscribed(status);
113+
} catch (error) {
114+
console.error("Error checking subscription status:", error);
115+
} finally {
116+
setIsLoading(false);
117+
}
118+
};
119+
120+
checkStatus();
121+
}, [event.id]);
122+
123+
// Handler for subscribe/unsubscribe action
124+
const handleSubscribeClick = async (e: MouseEvent<HTMLButtonElement>) => {
100125
e.stopPropagation();
101126
e.preventDefault();
102-
// Add button-specific logic here (e.g., open share modal, call bookmark API)
103-
console.log("Button clicked, navigation prevented.");
127+
128+
if (isLoading) return;
129+
130+
setIsLoading(true);
131+
132+
try {
133+
if (isSubscribed) {
134+
// Unsubscribe
135+
const success = await unsubscribeFromEntity({
136+
id: event.id,
137+
type: 'event',
138+
name: event.title
139+
});
140+
141+
if (success) {
142+
setIsSubscribed(false);
143+
toast.success(`Unsubscribed from ${event.title}`);
144+
}
145+
} else {
146+
// Subscribe
147+
const success = await subscribeToEntity({
148+
id: event.id,
149+
type: 'event',
150+
name: event.title
151+
});
152+
153+
if (success) {
154+
setIsSubscribed(true);
155+
toast.success(`Subscribed to ${event.title}`);
156+
}
157+
}
158+
} finally {
159+
setIsLoading(false);
160+
}
104161
};
105162

106163
return (
164+
<>
107165
<Link href={`/events/${event.id}`} className="block">
108166
<Card
109167
className={cn(
@@ -127,18 +185,43 @@ export function EventCard({ event, className }: EventCardProps) {
127185
size="icon"
128186
variant="secondary"
129187
className="bg-background/80 text-foreground backdrop-blur-sm hover:bg-background/95 p-2 h-9 w-9 rounded-full"
130-
onClick={handleButtonClick}
188+
onClick={(e) => {
189+
e.stopPropagation();
190+
e.preventDefault();
191+
setIsShareDialogOpen(true);
192+
}}
131193
>
132194
<Share2 className="w-4 h-4" />
133195
</Button>
134-
<Button
135-
size="icon"
136-
variant="secondary"
137-
className="bg-background/80 text-foreground backdrop-blur-sm hover:bg-background/95 p-2 h-9 w-9 rounded-full"
138-
onClick={handleButtonClick}
139-
>
140-
<Bookmark className="w-4 h-4" />
141-
</Button>
196+
<TooltipProvider delayDuration={200}>
197+
<Tooltip>
198+
<TooltipTrigger asChild>
199+
<Button
200+
size="icon"
201+
variant="secondary"
202+
disabled={isLoading}
203+
className={cn(
204+
"bg-background/80 text-foreground backdrop-blur-sm hover:bg-background/95 p-2 h-9 w-9 rounded-full",
205+
isSubscribed && "bg-primary/20 text-primary hover:bg-primary/30"
206+
)}
207+
onClick={handleSubscribeClick}
208+
>
209+
{isLoading ? (
210+
<Loader2 className="w-4 h-4 animate-spin" />
211+
) : (
212+
<Bell className="w-4 h-4" />
213+
)}
214+
</Button>
215+
</TooltipTrigger>
216+
<TooltipContent>
217+
{isLoading
218+
? "Processing..."
219+
: isSubscribed
220+
? "Unsubscribe from event updates"
221+
: "Subscribe to event updates"}
222+
</TooltipContent>
223+
</Tooltip>
224+
</TooltipProvider>
142225
</div>
143226
<div className="absolute top-3 left-3">
144227
<Badge variant="secondary" className="bg-background/80 text-foreground backdrop-blur-sm">
@@ -212,5 +295,13 @@ export function EventCard({ event, className }: EventCardProps) {
212295
</CardContent>
213296
</Card>
214297
</Link>
298+
299+
{/* Share Dialog */}
300+
<EventShareDialog
301+
open={isShareDialogOpen}
302+
onOpenChange={setIsShareDialogOpen}
303+
event={event}
304+
/>
305+
</>
215306
)
216307
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use client';
2+
3+
import * as React from "react";
4+
import {
5+
AlertDialog,
6+
AlertDialogCancel,
7+
AlertDialogContent,
8+
AlertDialogDescription,
9+
AlertDialogFooter,
10+
AlertDialogHeader,
11+
AlertDialogTitle
12+
} from "@/components/ui/alert-dialog";
13+
import { ShareComponent } from "@/components/ui/share/share-component";
14+
import { EventThumbnailDTO } from "@/types/event";
15+
16+
interface EventShareDialogProps {
17+
open: boolean;
18+
onOpenChange: (open: boolean) => void;
19+
event: EventThumbnailDTO;
20+
}
21+
22+
export function EventShareDialog({
23+
open,
24+
onOpenChange,
25+
event
26+
}: EventShareDialogProps) {
27+
const eventUrl = `${typeof window !== 'undefined' ? window.location.origin : 'http://localhost:8090'}/events/${event.id}`;
28+
29+
const handleCopy = () => {
30+
// Optional: handle any additional logic after copying
31+
onOpenChange(false); // Close dialog after copying
32+
};
33+
34+
return (
35+
<AlertDialog open={open} onOpenChange={onOpenChange}>
36+
<AlertDialogContent>
37+
<AlertDialogHeader>
38+
<AlertDialogTitle>Share Event</AlertDialogTitle>
39+
<AlertDialogDescription>
40+
Share {event.title} with others.
41+
</AlertDialogDescription>
42+
</AlertDialogHeader>
43+
44+
<div className="py-4">
45+
<ShareComponent
46+
url={eventUrl}
47+
title={event.title}
48+
text={`Check out this event: ${event.title}`}
49+
onCopy={handleCopy}
50+
/>
51+
</div>
52+
53+
<AlertDialogFooter>
54+
<AlertDialogCancel>Close</AlertDialogCancel>
55+
</AlertDialogFooter>
56+
</AlertDialogContent>
57+
</AlertDialog>
58+
);
59+
}

src/app/(home-app)/events/[event_id]/_components/SessionItem.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {SessionStatus} from "@/types/enums/sessionStatus";
2121
import {DiscountParameters} from "@/lib/validators/event";
2222
import {DiscountType} from "@/types/enums/discountType";
2323
import {Badge} from "@/components/ui/badge";
24+
import {SessionSubscribeButton} from "@/components/SessionSubscribeButton";
2425

2526
// --- Helper Functions & Constants for Discounts ---
2627

@@ -103,7 +104,12 @@ export const SessionItem = ({session}: { session: SessionInfoBasicDTO }) => {
103104
<span className="font-semibold text-foreground">{formatDate(session.startTime)}</span>
104105
<SessionStatusBadge status={session.status}/>
105106
</div>
106-
<div>
107+
<div className="flex items-center gap-2">
108+
<SessionSubscribeButton
109+
sessionId={session.id}
110+
sessionTitle={`Session on ${formatDate(session.startTime)}`}
111+
variant="outline"
112+
/>
107113
{session.status === SessionStatus.ON_SALE &&
108114
<Button onClick={() => router.push(`${window.location.pathname}/${session.id}`)}>
109115
Buy Tickets

src/app/manage/organization/[organization_id]/edit/page.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {Skeleton} from "@/components/ui/skeleton";
99
import {LogoManagementCard} from "@/app/manage/organization/[organization_id]/edit/_components/LogoManagementCard";
1010
import {ProfileDetailsCard} from "@/app/manage/organization/[organization_id]/edit/_components/InfoCard";
1111
import {DangerZoneCard} from "@/app/manage/organization/[organization_id]/edit/_components/DangerZoneCard";
12+
import {OrganizationSubscribeButton} from "@/components/OrganizationSubscribeButton";
1213

1314
export default function OrganizationSettingsPage() {
1415
const router = useRouter();
@@ -51,9 +52,15 @@ export default function OrganizationSettingsPage() {
5152

5253
return (
5354
<div className="p-4 md:p-8 space-y-8">
54-
<h1 className="text-3xl font-bold tracking-tight">
55-
{organization.name} Settings
56-
</h1>
55+
<div className="flex justify-between items-center">
56+
<h1 className="text-3xl font-bold tracking-tight">
57+
{organization.name} Settings
58+
</h1>
59+
<OrganizationSubscribeButton
60+
organizationId={organization.id}
61+
organizationName={organization.name}
62+
/>
63+
</div>
5764

5865
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
5966
<div className="lg:col-span-1">

src/app/manage/organization/[organization_id]/event/[eventId]/analytics/_components/SessionPerformanceCard.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { formatCurrency } from "@/lib/utils";
88
import { ArrowUpRightIcon, ClockIcon, MapPinIcon, UsersIcon } from "lucide-react";
99
import { SessionSummary } from "@/types/eventAnalytics";
1010
import { SessionSummary as OrderSessionSummary } from "@/lib/actions/analyticsActions";
11-
import { useRouter } from "next/navigation";
1211
import { SessionDetailDTO } from "@/lib/validators/event";
1312
import { SessionStatusBadge } from "@/components/SessionStatusBadge";
1413
import Link from "next/link";
@@ -28,8 +27,6 @@ export const SessionPerformanceCard = ({
2827
organizationId,
2928
eventId
3029
}: SessionPerformanceCardProps) => {
31-
const router = useRouter();
32-
3330
// Basic session info
3431
const sessionId = session.sessionId;
3532
const startTime = parseISO(session.startTime);
@@ -52,10 +49,6 @@ export const SessionPerformanceCard = ({
5249
const location = isOnline
5350
? "Online Event"
5451
: sessionMetadata?.venueDetails?.name || "Venue not specified";
55-
56-
const handleViewDetails = () => {
57-
router.push(`/manage/organization/${organizationId}/event/${eventId}/sessions/${sessionId}/analytics`);
58-
};
5952

6053
return (
6154
<Card className="overflow-hidden hover:shadow-md transition-all">

src/app/manage/organization/[organization_id]/event/[eventId]/analytics/_components/SessionPerformanceGrid.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export const SessionPerformanceGrid = ({
1818
isLoading
1919
}: SessionPerformanceGridProps) => {
2020
const [sessionRevenueData, setSessionRevenueData] = useState<OrderSessionSummary[]>([]);
21-
const [isRevenueLoading, setIsRevenueLoading] = useState(true);
2221
const params = useParams();
2322
const { event } = useEventContext();
2423

@@ -30,13 +29,10 @@ export const SessionPerformanceGrid = ({
3029
const fetchRevenueData = async () => {
3130
if (eventId) {
3231
try {
33-
setIsRevenueLoading(true);
3432
const data = await getSessionsRevenueAnalytics(eventId);
3533
setSessionRevenueData(data.sessions);
3634
} catch (error) {
3735
console.error("Error fetching session revenue data:", error);
38-
} finally {
39-
setIsRevenueLoading(false);
4036
}
4137
}
4238
};

src/app/manage/organization/[organization_id]/event/[eventId]/discounts/_components/DiscountUsageChart.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ export const DiscountUsageChart: React.FC<DiscountUsageChartProps> = ({ data })
199199
layout="horizontal"
200200
verticalAlign="bottom"
201201
align="center"
202-
formatter={(value, entry) => (
202+
formatter={(value) => (
203203
<span className="text-xs text-foreground">
204204
{value}
205205
</span>

src/app/manage/organization/[organization_id]/event/[eventId]/orders/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default function OrdersPage() {
3636
const [statusFilter, setStatusFilter] = useState("all");
3737
const [currentSession, setCurrentSession] = useState<string | null>(null);
3838
const [currentPage, setCurrentPage] = useState(1);
39-
const [pageSize, setPageSize] = useState(10);
39+
const pageSize = 10;
4040
const [totalOrders, setTotalOrders] = useState(0);
4141

4242
// Get session ID from URL if present

src/app/manage/organization/[organization_id]/event/[eventId]/sessions/[sessionId]/_components/SessionHeader.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { format } from 'date-fns';
55
import { LinkIcon, MapPin, Share2, Trash2 } from 'lucide-react';
66
import { Button } from "@/components/ui/button";
77
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
8-
import { useParams } from 'next/navigation';
98

109
interface SessionHeaderProps {
1110
title: string;
@@ -26,8 +25,6 @@ export const SessionHeader: React.FC<SessionHeaderProps> = ({
2625
onShare,
2726
onDelete
2827
}) => {
29-
const params = useParams();
30-
const backUrl = `/manage/organization/${params.organization_id}/event/${params.eventId}/sessions`;
3128
return (
3229
<div className="space-y-4">
3330
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">

src/app/manage/organization/my-organizations/page.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {DeleteOrganizationDialog, CreateOrganizationDialog} from "@/components/O
2121
import {useRouter} from "next/navigation";
2222
import {Avatar, AvatarFallback, AvatarImage} from "@/components/ui/avatar"
2323
import Link from "next/link";
24+
import {OrganizationSubscribeButton} from "@/components/OrganizationSubscribeButton";
2425

2526

2627
export default function OrganizationsPage() {
@@ -111,6 +112,18 @@ export default function OrganizationsPage() {
111112
>
112113
Copy organization ID
113114
</DropdownMenuItem>
115+
<DropdownMenuItem
116+
onClick={(e) => {
117+
e.stopPropagation();
118+
}}
119+
>
120+
<OrganizationSubscribeButton
121+
organizationId={organization.id}
122+
organizationName={organization.name}
123+
variant="default"
124+
className="w-full justify-start px-0"
125+
/>
126+
</DropdownMenuItem>
114127
<DropdownMenuItem onClick={() => {
115128
router.push(`/manage/organization/${organization.id}/edit`);
116129
}}>

0 commit comments

Comments
 (0)