Skip to content

Commit f394b94

Browse files
Merge pull request #17 from bhavik-goplani/develop
Develop: Created generate link component for participant
2 parents 56718b0 + cf3b5ce commit f394b94

File tree

9 files changed

+268
-1
lines changed

9 files changed

+268
-1
lines changed

package-lock.json

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@
4040
"tailwindcss": "3.3.2",
4141
"tailwindcss-animate": "^1.0.7",
4242
"typescript": "5.0.4",
43+
"uuid": "^9.0.1",
4344
"zod": "^3.22.4"
4445
},
4546
"devDependencies": {
47+
"@types/uuid": "^9.0.7",
4648
"encoding": "^0.1.13",
4749
"supabase": "^1.88.0"
4850
}

src/app/[participant_id]/page.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
export const dynamic = 'force-dynamic'
3+
4+
export default async function Page({ params }: { params: { participant_id: string } }) {
5+
6+
return (
7+
<>
8+
<div className="container mx-auto py-10">
9+
<div className='flex justify-between'>
10+
<h1 className="text-2xl font-semibold tracking-tight">Welcome to the Survey {params.participant_id}</h1>
11+
12+
</div>
13+
</div>
14+
</>
15+
)
16+
}

src/app/api/participant/route.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
2+
import { cookies } from 'next/headers'
3+
import { NextRequest, NextResponse } from 'next/server'
4+
5+
export const dynamic = 'force-dynamic'
6+
7+
export async function POST(req: NextRequest) {
8+
const supabase = createRouteHandlerClient({ cookies })
9+
const body = await req.json();
10+
11+
const participant_email = body['participant_email'];
12+
const survey_id = body['survey_id'];
13+
const participant_id = body['participant_id'];
14+
15+
const { data, error } = await supabase.from('Participant').insert([
16+
{
17+
participant_email: participant_email,
18+
survey_id: survey_id,
19+
participant_id: participant_id,
20+
},
21+
]);
22+
23+
if (error) {
24+
console.error('Error inserting data:', error);
25+
// Handle error here
26+
} else {
27+
console.log('Data inserted successfully:', data);
28+
// Handle success here
29+
}
30+
31+
return NextResponse.redirect(new URL('/dashboard', req.url), 303)
32+
}

src/app/api/send/route.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
3+
export const dynamic = 'force-dynamic';
4+
5+
export async function POST(req: NextRequest) {
6+
const body = await req.json();
7+
const link = body['link'];
8+
const email = body['participant_email'];
9+
10+
try {
11+
const data = await fetch('https://api.resend.com/emails', {
12+
method: 'POST',
13+
headers: {
14+
'Content-Type': 'application/json',
15+
'Authorization': `Bearer ${process.env.RESEND_API_KEY}`,
16+
},
17+
body: JSON.stringify({
18+
19+
to: [email],
20+
subject: 'Link for the Survey',
21+
html: `<h1>Welcome to the Rock Paper Scissors Survey!</h1><p>Please click the link below to start the survey. You can save your progress and come back to the survey at any time.</p><br /><a href="${link}">Start Survey</a>`,
22+
}),
23+
});
24+
return NextResponse.json(data);
25+
} catch (error) {
26+
return NextResponse.json({ error });
27+
}
28+
}

src/components/icons.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,24 @@ export const Icons = {
139139
<path d="M21 12a9 9 0 1 1-6.219-8.56" />
140140
</svg>
141141
),
142+
copy_link: (props: IconProps) => (
143+
<svg
144+
width="20px"
145+
height="20px"
146+
viewBox="0 0 24 24"
147+
version="1.1"
148+
xmlns="http://www.w3.org/2000/svg"
149+
xmlnsXlink="http://www.w3.org/1999/xlink"
150+
{...props}
151+
>
152+
<title>ic_fluent_copy_link_24_regular</title>
153+
<desc>Created with Sketch.</desc>
154+
<g id="🔍-Product-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
155+
<g id="ic_fluent_copy_link_24_regular" fill="#ffffff" fill-rule="nonzero">
156+
<path d="M13.7533481,2 C14.9105985,2 15.863488,2.8749731 15.9865561,3.9994587 L17.75,4 C18.940864,4 19.9156449,4.92516159 19.9948092,6.09595119 L20,6.25 L20,12.25 C20,12.6642136 19.6642136,13 19.25,13 C18.8703042,13 18.556509,12.7178461 18.5068466,12.3517706 L18.5,12.25 L18.5,6.25 C18.5,5.87030423 18.2178461,5.55650904 17.8517706,5.50684662 L17.75,5.5 L15.6182905,5.49983563 C15.214809,6.09910034 14.5301141,6.49330383 13.7533481,6.49330383 L10.2466519,6.49330383 C9.46988587,6.49330383 8.78519098,6.09910034 8.38170952,5.49983563 L6.25,5.5 C5.87030423,5.5 5.55650904,5.78215388 5.50684662,6.14822944 L5.5,6.25 L5.5,19.754591 C5.5,20.1342868 5.78215388,20.448082 6.14822944,20.4977444 L6.35177056,20.5114376 C6.71784612,20.5611 7,20.8748952 7,21.254591 C7,21.6688046 6.66421356,22.004591 6.25,22.004591 C5.05913601,22.004591 4.08435508,21.0794294 4.00519081,19.9086398 L4,19.754591 L4,6.25 C4,5.05913601 4.92516159,4.08435508 6.09595119,4.00519081 L6.25,4 L8.01344395,3.9994587 C8.13651196,2.8749731 9.08940148,2 10.2466519,2 L13.7533481,2 Z M17.25,14.5 L18.25,14.5 C20.3210678,14.5 21.9999918,16.1789322 21.9999918,18.25 C21.9999918,20.2542592 20.4276389,21.8912737 18.4522792,21.994802 L18.2534432,22 L17.2534432,22.0045992 C16.839234,22.0064847 16.5019095,21.6722434 16.4999918,21.2580342 C16.4982641,20.8783424 16.778975,20.5632552 17.1448187,20.5119127 L17.2465568,20.5045989 L18.25,20.5 C19.4926407,20.5 20.4999918,19.4926407 20.4999918,18.25 C20.4999918,17.059136 19.5748384,16.0843551 18.4040488,16.0051908 L18.25,16 L17.25,16 C16.8357864,16 16.4999918,15.6642136 16.4999918,15.25 C16.4999918,14.8703042 16.7821539,14.556509 17.1482294,14.5068466 L17.25,14.5 L18.25,14.5 L17.25,14.5 Z M12.25,14.5 L13.25,14.5 C13.6642136,14.5 14,14.8357864 14,15.25 C14,15.6296958 13.7178461,15.943491 13.3517706,15.9931534 L13.25,16 L12.25,16 C11.0073593,16 10,17.0073593 10,18.25 C10,19.440864 10.9251616,20.4156449 12.0959512,20.4948092 L12.25,20.5 L13.25,20.5 C13.6642136,20.5 14,20.8357864 14,21.25 C14,21.6296958 13.7178461,21.943491 13.3517706,21.9931534 L13.25,22 L12.25,22 C10.1789322,22 8.5,20.3210678 8.5,18.25 C8.5,16.2457408 10.0723611,14.6087263 12.0508414,14.505198 L12.25,14.5 L13.25,14.5 L12.25,14.5 Z M12.25,17.5 L18.25,17.5 C18.6642136,17.5 19,17.8357864 19,18.25 C19,18.6296958 18.7178461,18.943491 18.3517706,18.9931534 L18.25,19 L12.25,19 C11.8357864,19 11.5,18.6642136 11.5,18.25 C11.5,17.8703042 11.7821539,17.556509 12.1482294,17.5068466 L12.25,17.5 L18.25,17.5 L12.25,17.5 Z M13.7533481,3.5 L10.2466519,3.5 C9.83428745,3.5 9.5,3.83428745 9.5,4.24665191 C9.5,4.65901638 9.83428745,4.99330383 10.2466519,4.99330383 L13.7533481,4.99330383 C14.1657126,4.99330383 14.5,4.65901638 14.5,4.24665191 C14.5,3.83428745 14.1657126,3.5 13.7533481,3.5 Z" id="🎨-Color">
157+
</path>
158+
</g>
159+
</g>
160+
</svg>
161+
),
142162
}

src/components/survey-dashboard/columns.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
} from "@/components/ui/alert-dialog"
2727

2828
import { EditSurvey } from "@/components/survey-dashboard/edit-survey"
29+
import { GenerateLink } from "@/components/survey-dashboard/generate-link"
2930

3031
// This type is used to define the shape of our data.
3132
// You can use a Zod schema here if you want.
@@ -65,6 +66,7 @@ export const columns: ColumnDef<Survey>[] = [
6566
const survey = row.original
6667
const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
6768
const [editDialogOpen, setEditDialogOpen] = React.useState(false);
69+
const [generateLinkDialogOpen, setGenerateLinkDialogOpen] = React.useState(false);
6870
const router = useRouter()
6971

7072
async function deleteSurvey(survey_id: string) {
@@ -132,7 +134,12 @@ export const columns: ColumnDef<Survey>[] = [
132134
View/Edit Sections
133135
</Link>
134136
</DropdownMenuItem>
135-
<DropdownMenuItem>Create Link for Participant</DropdownMenuItem>
137+
<DropdownMenuItem
138+
onSelect={() => {
139+
setGenerateLinkDialogOpen(true)
140+
document.body.style.pointerEvents = ""
141+
}}
142+
>Create Link for Participant</DropdownMenuItem>
136143
<DropdownMenuItem
137144
onSelect={() => {
138145
setDeleteDialogOpen(true)
@@ -166,6 +173,7 @@ export const columns: ColumnDef<Survey>[] = [
166173
</AlertDialogContent>
167174
</AlertDialog>
168175
<EditSurvey survey_id={survey.survey_id} editDialogOpen={editDialogOpen} setEditDialogOpen={setEditDialogOpen} />
176+
<GenerateLink survey_id={survey.survey_id} generateLinkDialogOpen={generateLinkDialogOpen} setGenerateLinkDialogOpen={setGenerateLinkDialogOpen} />
169177
</>
170178
)
171179
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
'use client'
2+
import {
3+
Dialog,
4+
DialogContent,
5+
DialogHeader,
6+
DialogTitle,
7+
} from "@/components/ui/dialog"
8+
9+
import {
10+
Form,
11+
FormControl,
12+
FormDescription,
13+
FormField,
14+
FormItem,
15+
FormLabel,
16+
FormMessage,
17+
} from "@/components/ui/form"
18+
19+
import * as z from "zod"
20+
import { Button } from "@/components/ui/button"
21+
import { Input } from "@/components/ui/input"
22+
import { zodResolver } from "@hookform/resolvers/zod"
23+
import { useForm } from "react-hook-form"
24+
import * as React from "react"
25+
import { useToast } from "@/components/ui/use-toast"
26+
import { v4 as uuidv4 } from 'uuid';
27+
28+
const formSchema = z.object({
29+
participant_email: z.string().email(),
30+
})
31+
32+
interface GenerateLinkProps {
33+
generateLinkDialogOpen: boolean;
34+
setGenerateLinkDialogOpen: (value: boolean) => void;
35+
survey_id: string;
36+
}
37+
38+
export function GenerateLink({generateLinkDialogOpen, setGenerateLinkDialogOpen, survey_id}: GenerateLinkProps) {
39+
40+
const form = useForm<z.infer<typeof formSchema>>({
41+
resolver: zodResolver(formSchema),
42+
defaultValues: {
43+
participant_email: "",
44+
},
45+
})
46+
47+
const { reset } = form
48+
const { toast } = useToast()
49+
50+
async function onSubmit(values: z.infer<typeof formSchema>) {
51+
// Do something with the form values.
52+
// ✅ This will be type-safe and validated.
53+
reset()
54+
setGenerateLinkDialogOpen(false)
55+
56+
const participant_id = uuidv4()
57+
const link = `${process.env.NEXT_PUBLIC_ROOT_URL}/${participant_id}`
58+
navigator.clipboard.writeText(link)
59+
60+
const { participant_email } = values
61+
const res = await fetch('/api/participant', {
62+
method: 'POST',
63+
headers: {
64+
'Content-Type': 'application/json'
65+
},
66+
body: JSON.stringify({participant_email, survey_id, participant_id})
67+
})
68+
69+
if (res.ok) {
70+
console.log("Participant created")
71+
} else {
72+
console.log("Participant not created")
73+
}
74+
const email_res = await fetch('/api/send', {
75+
method: 'POST',
76+
headers: {
77+
'Content-Type': 'application/json'
78+
},
79+
body: JSON.stringify({participant_email, link})
80+
})
81+
82+
if (email_res) {
83+
toast({
84+
title: "Email sent",
85+
description: "Participant invite sent successfully",
86+
})
87+
} else {
88+
toast({
89+
title: "Something went wrong.",
90+
description: "Your email could not be sent.",
91+
variant: "destructive",
92+
})
93+
}
94+
}
95+
96+
return(
97+
<Dialog open={generateLinkDialogOpen} onOpenChange={(open) => {
98+
setGenerateLinkDialogOpen(open)
99+
if (!open) {
100+
reset(); // Reset form values when dialog closes
101+
}
102+
}}>
103+
<DialogContent className="sm:max-w-[425px]">
104+
<DialogHeader>
105+
<DialogTitle>Create Link for Participant</DialogTitle>
106+
</DialogHeader>
107+
<Form {...form}>
108+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
109+
<FormField
110+
control={form.control}
111+
name="participant_email"
112+
render={({ field }) => (
113+
<FormItem>
114+
<FormLabel>Participant Email</FormLabel>
115+
<FormControl>
116+
<Input placeholder="" {...field} />
117+
</FormControl>
118+
<FormDescription>
119+
Link will be copied to clipboard
120+
</FormDescription>
121+
<FormMessage />
122+
</FormItem>
123+
)}
124+
/>
125+
<Button type="submit">Send Invite</Button>
126+
</form>
127+
</Form>
128+
</DialogContent>
129+
</Dialog>
130+
)
131+
}

yarn.lock

+10
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,11 @@
682682
resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz"
683683
integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==
684684

685+
"@types/uuid@^9.0.7":
686+
version "9.0.7"
687+
resolved "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz"
688+
integrity sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==
689+
685690
"@types/websocket@^1.0.3":
686691
version "1.0.6"
687692
resolved "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.6.tgz"
@@ -3175,6 +3180,11 @@ util-deprecate@^1.0.2:
31753180
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
31763181
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
31773182

3183+
uuid@^9.0.1:
3184+
version "9.0.1"
3185+
resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz"
3186+
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
3187+
31783188
web-streams-polyfill@^3.0.3:
31793189
version "3.2.1"
31803190
resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz"

0 commit comments

Comments
 (0)