Skip to content

Commit

Permalink
Feat/event invite participants (#203)
Browse files Browse the repository at this point in the history
* feat: create email for sending manual invitation

* fix: use proper data

* fix: react-email as external packages

* feat: invite email component

* feat: create endpoint to request sending invitation email

* Refactor EventInviteModal component to handle email sending and error handling
  • Loading branch information
ghaniswara authored Apr 5, 2024
1 parent 739f6b3 commit ffa18fa
Show file tree
Hide file tree
Showing 7 changed files with 363 additions and 33 deletions.
36 changes: 9 additions & 27 deletions app/(server)/_features/event/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { generateID } from '@/(server)/_shared/utils/generateid';
import { selectUser } from '../user/schema';
import {
SendEventCancelledEmail,
SendEventManualInvitationEmail,
SendEventRescheduledEmail,
} from '@/(server)/_shared/mailer/mailer';
import { PageMeta } from '@/_shared/types/types';
import { EventType } from '@/_shared/types/event';

/**
* Type used to represent all type of participant in an event
Expand All @@ -29,32 +31,8 @@ export type Participant = {

export interface iEventRepo {
addEvent(eventData: insertEvent): Promise<selectEvent>;
getEventById(id: number): Promise<
| (selectEvent & {
host:
| {
name: string;
id: number;
pictureUrl: string | null;
}
| null
| undefined;
})
| undefined
>;
getEventBySlug(slug: string): Promise<
| (selectEvent & {
host:
| {
name: string;
id: number;
pictureUrl: string | null;
}
| null
| undefined;
})
| undefined
>;
getEventById(id: number): Promise<EventType.Event | undefined>;
getEventBySlug(slug: string): Promise<EventType.Event | undefined>;
getEventParticipantsByEventId(
eventId: number
): Promise<selectParticipant[] | undefined>;
Expand Down Expand Up @@ -90,7 +68,7 @@ export class EventService implements iEventService {
}

async getEventBySlugOrID(slugOrId: string, userId?: number) {
let event: selectEvent | undefined;
let event: EventType.Event | undefined;

if (!isNaN(Number(slugOrId))) {
event = await this.repo.getEventById(Number(slugOrId));
Expand Down Expand Up @@ -170,4 +148,8 @@ export class EventService implements iEventService {

return false;
}

async sendManualEmailInvitation(event: EventType.Event, email: string) {
SendEventManualInvitationEmail(event, email);
}
}
14 changes: 9 additions & 5 deletions app/(server)/_shared/mailer/mailer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,22 +269,26 @@ export async function SendEventRescheduledEmail(
}

export async function SendEventManualInvitationEmail(
email: string,
event?: selectEvent,
host?: selectUser
event: selectEvent,
email: string
) {
const mg = new Mailgun(formData);
const mailer = mg.client({
key: MAILER_API_KEY,
username: 'api',
});

const html = render(EventManualInvitation({}), { pretty: true });
const html = render(
EventManualInvitation({
event,
}),
{ pretty: true }
);

mailer.messages.create(MAILER_DOMAIN, {
html: html,
from: 'inLive Room Events <[email protected]>',
to: email,
subject: `You've been invited to a Webinar`,
subject: `Webinar Invitation: ${event.name}`,
});
}
105 changes: 105 additions & 0 deletions app/(server)/api/events/[slugOrId]/invite/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { getCurrentAuthenticated } from '@/(server)/_shared/utils/get-current-authenticated';
import { eventService } from '@/(server)/api/_index';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
import { z } from 'zod';

const sendInviteEmailSchema = z.object({
emails: z.array(z.string().email()),
});

export async function POST(
request: Request,
{ params }: { params: { slugOrId: string } }
) {
const slugOrId = params.slugOrId;
const cookieStore = cookies();
const requestToken = cookieStore.get('token');

if (!requestToken) {
return NextResponse.json(
{
code: 401,
message: 'Please check if token is provided in the cookie',
},
{ status: 401 }
);
}

const response = await getCurrentAuthenticated(requestToken?.value || '');
const user = response.data ? response.data : null;

if (!user) {
return NextResponse.json(
{
code: 401,
ok: false,
message:
'User not found, please check if token is provided in the cookie is valid',
},
{ status: 401 }
);
}

try {
const event = await eventService.getEventBySlugOrID(slugOrId, user.id);
if (!event) {
return NextResponse.json(
{
code: 404,
ok: false,
message: 'Event not found',
},
{ status: 404 }
);
}

if (event.status !== 'published') {
return NextResponse.json(
{
code: 400,
ok: false,
message: 'Event is not published',
},
{ status: 400 }
);
}

try {
const reqJSON = await request.json();

const emails = sendInviteEmailSchema.parse(reqJSON);
emails.emails.forEach((email) => {
eventService.sendManualEmailInvitation(event, email);
});
} catch (e) {
return NextResponse.json(
{
code: 400,
ok: false,
message: e,
},
{ status: 400 }
);
}

return NextResponse.json(
{
code: 200,
ok: true,
message: 'Emails sent successfully',
},
{ status: 200 }
);
} catch (e) {
console.log(e);
return NextResponse.json(
{
code: 500,
ok: false,
message: 'Internal server error',
},
{ status: 500 }
);
}
}
13 changes: 13 additions & 0 deletions app/_features/event/components/event-detail-dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useToggle } from '@/_shared/hooks/use-toggle';
import CancelEventModal from './event-cancel-modal';
import type { SVGElementPropsType } from '@/_shared/types/types';
import { useFormattedDateTime } from '@/_shared/hooks/use-formatted-datetime';
import EventInviteModal from './event-invite-modal';

const APP_ORIGIN = process.env.NEXT_PUBLIC_APP_ORIGIN;

Expand Down Expand Up @@ -59,6 +60,7 @@ export default function EventDetailDashboard({
return (
<div className="bg-zinc-900">
<CancelEventModal slug={event.slug} />
<EventInviteModal slug={event.slug} />
<div className="min-viewport-height mx-auto flex max-w-7xl flex-col px-4">
<Header logoText="inLive Room" logoHref="/" />
<main className="flex-1">
Expand Down Expand Up @@ -162,6 +164,17 @@ export default function EventDetailDashboard({
</span>
</div>
</div>
<Button
onClick={() =>
document.dispatchEvent(
new CustomEvent('open:event-invite-modal')
)
}
target="_blank"
className="mt-2 h-9 w-full min-w-0 rounded-md bg-zinc-800 px-4 py-2 text-base font-medium text-white antialiased hover:bg-zinc-700 active:bg-zinc-600 lg:text-sm"
>
Invite Participants
</Button>
</div>
<div className="flex flex-col gap-1 py-3 sm:flex-row sm:gap-8 lg:py-5">
<div className="text-sm font-medium text-zinc-400">
Expand Down
Loading

0 comments on commit ffa18fa

Please sign in to comment.