Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL=""

NEXT_PUBLIC_REDIRECT_AUTH_URL=""
AUTH_DOMAIN=""
DASH_DOMAIN=""

NEXT_PUBLIC_SITE_URL=""

NEXTAUTH_URL=""
NEXTAUTH_SECRET=""
NEXTAUTH_ROOT_DOMAIN=""
SENDGRID_API_KEY=""
1,039 changes: 903 additions & 136 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"@trpc/next": "^11.0.0-next-beta.289",
"@trpc/server": "^11.0.0-next-beta.289",
"bcrypt": "^5.1.1",
"eclipse-components": "^0.0.141",
"eclipse-components": "^0.0.163",
"next": "14.1.0",
"next-auth": "^4.24.5",
"react": "^18",
Expand Down
5 changes: 5 additions & 0 deletions prisma/migrations/20240628162247_add_referrals/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "referralId" TEXT;

-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_referralId_fkey" FOREIGN KEY ("referralId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
11 changes: 11 additions & 0 deletions prisma/migrations/20240628183850_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
Warnings:

- You are about to drop the column `referralId` on the `User` table. All the data in the column will be lost.

*/
-- DropForeignKey
ALTER TABLE "User" DROP CONSTRAINT "User_referralId_fkey";

-- AlterTable
ALTER TABLE "User" DROP COLUMN "referralId";
8 changes: 8 additions & 0 deletions prisma/migrations/20240628183910_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:

- Added the required column `nickname` to the `User` table without a default value. This is not possible if the table is not empty.

*/
-- AlterTable
ALTER TABLE "User" ADD COLUMN "nickname" TEXT NOT NULL;
12 changes: 12 additions & 0 deletions prisma/migrations/20240628190318_referral/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
Warnings:

- You are about to drop the column `nickname` on the `User` table. All the data in the column will be lost.

*/
-- AlterTable
ALTER TABLE "User" DROP COLUMN "nickname",
ADD COLUMN "referralId" TEXT;

-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_referralId_fkey" FOREIGN KEY ("referralId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
5 changes: 5 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ model User {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
LoginAttempt LoginAttempt[]

referredBy User? @relation("UserReferral", fields:[referralId], references:[id], onDelete: Cascade, onUpdate: NoAction)
referralId String?
referrals User[] @relation("UserReferral")

}

model Auth {
Expand Down
46 changes: 46 additions & 0 deletions src/app/api/register/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { NextApiRequest, NextApiResponse } from 'next';

import { getSessionUser } from "@/lib/user/getSessionUser";
import {sendEmail} from '@/lib/email';
import {prisma} from '@/server/db';


export async function POST(req: NextApiRequest) {

console.log(req.body);
const {email} = await req.json();

if (!email) {
return new Response("Email is required", { status: 400 });
}

// create a user with the given email

const currentUser = await getSessionUser();

await prisma.user.create({
data:{
email: email,
referralId: currentUser?.id,

}
})

const user = await prisma.user.findUnique({
where: {
email,
},
});

const link = `${process.env.NEXT_PUBLIC_SITE_URL}/register/${user?.id}`;

await sendEmail(
email,
"Welcome to Eclipse Expos",
`Welcome to Eclipse Expos! You have invited to register. Click here to register: ${link}`,
);

return new Response("User created", { status: 200 });


}
16 changes: 14 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@

import { getSessionUser } from "@/lib/user/getSessionUser";
import { isRegistered } from "@/lib/user/registration";
import Client from "@/components/refferal"
import Link from "next/link";

import {
StarBackground,

} from 'eclipse-components'

/**
* Home Page element
* @returns JSX.element
Expand All @@ -12,7 +18,9 @@ export default async function Home() {
const user = await getSessionUser();
const preRegistered = await isRegistered();
return (

<main className="p-4 text-left">
{/* <StarBackground/> */}
<h1 className="text-3xl">
{user ? `Welcome ${user.firstName}!` : "Not logged in."}
</h1>
Expand All @@ -27,15 +35,19 @@ export default async function Home() {
Settings
</Link>
<br />
{preRegistered ? ('You are preregistered!') : ("You are not yet preregistered. ")}
{preRegistered ? (
<div>
<p> You have preregistered, you can invite up to 10 of your friends! </p>
<Client users={user?.referrals}/>
</div> ) : ("You are not yet preregistered. ")}

</div>
) : (
<Link href={`${process.env.AUTH_DOMAIN}/login?callbackUrl=${process.env.DASH_DOMAIN}`} className="underline">
Login
</Link>
)}

<br />
</main>
);
Expand Down
152 changes: 152 additions & 0 deletions src/components/refferal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"use client"
import { User } from '@prisma/client'
import { prisma } from '@/server/db';
import {useState, useEffect, FormEvent} from 'react';
import { InputStatus } from '@/types/types';
import {
Button,
Input,
Spinner,
Popover
} from "eclipse-components";


export default function Client({users}:{users: User[]}) {

const refferalUsers = users;
const amountOfUsers = refferalUsers.length;


const [status, setStatus] = useState<InputStatus>(InputStatus.DEFAULT);
const [email, setEmail] = useState<string>("");

const onSubmit = async (e: FormEvent) => {
e.preventDefault();

if (email === "") {
setStatus(InputStatus.EMPTY_FIELDS);
return;
}

if (amountOfUsers >= 10) {

setStatus(InputStatus.ERROR);
return;
}

setStatus(InputStatus.LOADING)

const response = await fetch("/api/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email
}),
});

if (response.status === 200) {
setStatus(InputStatus.DEFAULT);
window.location.reload();
} else if (response.status === 409) {
setStatus(InputStatus.ALREADY_REGISTERED);
} else {
setStatus(InputStatus.ERROR);
}
}


return (
<>

<h1 className="text-4xl text-center">Referred Users</h1>
<h2 className="text-2xl text-center">You have {10 - amountOfUsers} invites left</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{refferalUsers.map((user) => (
<div key={user.id} className="bg-white p-4 rounded-lg shadow-md">
<h3 className="text-lg text-black font-semibold">{user.firstName && user.lastName ? "Accepted" : "Pending"}</h3>
<p className="text-black">{user.email}</p>
</div>
))}
</div>

{amountOfUsers < 10 && <form
className="relative flex flex-col items-center justify-center gap-4 p-4"
onSubmit={onSubmit}
>
{/**
* If the status is not success or loading, then show the input fields
*/}
{status !== InputStatus.SUCCESS && status !== InputStatus.LOADING && (
<>

<Input
type="email"
className="w-72 sm:w-[32rem]"
required={true}
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Button className="w-72 sm:w-[32rem]">Invite</Button>
</>
)}

{/**
* If the input is loading, then show the loading spinner
*/}
{status === InputStatus.LOADING && <Spinner className="mt-10" />}

{/**
* If an error occurs, then show the error message
*/}
{/* <Popover
open={status === InputStatus.ERROR}
message="An error has occurred. Please try again with a different email."
onClose={() => setStatus(InputStatus.DEFAULT)}
/> */}

{/**
* If the user is already registered, then show the error message
*/}
{/* <Notification
open={status === InputStatus.ALREADY_REGISTERED}
message="You are already registered! Check your email for more information."
onClose={() => setStatus(InputStatus.DEFAULT)}
/> */}

{/**
* If the user hasn't filled out all the fields, then show the error message
*/}
{/* <Notification
open={status === InputStatus.EMPTY_FIELDS}
message="Please fill out all fields"
onClose={() => setStatus(InputStatus.DEFAULT)}
/> */}


{/**
* If the user has successfully registered, then show the success message
*/}
{status === InputStatus.SUCCESS && (
<div className="flex flex-col items-center justify-center gap-2 tracking-wide">
<h1 className="text-4xl font-black tracking-wide text-primary">
Thanks for registering!
</h1>
<p className="mt-1 text-primary">Let&#39;s break some records.</p>
</div>
)}
</form> }

{amountOfUsers >= 10 && <div className="flex flex-col items-center justify-center gap-2 tracking-wide">
<h1 className="text-4xl font-black tracking-wide text-primary">
You have reached the limit!
</h1>
<p className="mt-1 text-primary">You have reached the limit of 10 invites</p>
</div>

}
</>);

}
39 changes: 39 additions & 0 deletions src/lib/email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import sgMail from "@sendgrid/mail";

/*
* Send an email to the given email address
* @param email The email to send to
* @param subject The subject of the email
* @param body The body of the email
*/
export async function sendEmail(
email: string,
subject: string,
body: string,
html?: string,
) {
// set the api key
sgMail.setApiKey(process.env.SENDGRID_API_KEY as string);

// message to send
const msg: sgMail.MailDataRequired = {
to: email,
from: {
email: process.env.SENDGRID_SENDER_ID as string,
name: "Eclipse Expos",
},
subject: subject,
text: body,
html: html ? html : body,
headers: {
"In-Reply-To": crypto.randomUUID(),
},
};

try {
await sgMail.send(msg);
} catch (error: any) {
console.error(error); // log any errors
console.error(error.response.body); // log the response body
}
}
3 changes: 3 additions & 0 deletions src/lib/user/getSessionUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export async function getSessionUser() {
where: {
email: email,
},
include: {
referrals: true
}
});

return user;
Expand Down
Loading