diff --git a/.env.sample b/.env.sample index 59397999..34aa4b73 100644 --- a/.env.sample +++ b/.env.sample @@ -10,3 +10,5 @@ DATABASE_URL= ACCOUNT_PK=0x0574ba4998dd9aedf1c4d6e56b747b29256a795bc3846437d121cd64b972bdd8 NEXT_PUBLIC_OG_NFT_CONTRACT=0x3cb654f2f557a7f71a0c16d97c05a2dec62a0b744979d11afc95b804e1d7307 + +CRON_SECRET= diff --git a/next.config.mjs b/next.config.mjs index 83948dfc..9b833cdc 100755 --- a/next.config.mjs +++ b/next.config.mjs @@ -2,9 +2,9 @@ const nextConfig = { // output: 'export', compiler: { - removeConsole: { - exclude: ['error', 'debug'], - }, + // removeConsole: { + // exclude: ['error', 'debug'], + // }, }, async rewrites() { return [ @@ -62,7 +62,8 @@ const nextConfig = { }, { source: '/tnc/v1', - destination: 'https://github.com/strkfarm/static-assets/blob/177389cad715d69245c1b125df87f90318ac2d7b/tnc.pdf', + destination: + 'https://github.com/strkfarm/static-assets/blob/177389cad715d69245c1b125df87f90318ac2d7b/tnc.pdf', permanent: true, }, ]; diff --git a/prisma/migrations/20240818115638_init/migration.sql b/prisma/migrations/20240818115638_init/migration.sql deleted file mode 100644 index 08f2584e..00000000 --- a/prisma/migrations/20240818115638_init/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ --- CreateTable -CREATE TABLE "User" ( - "id" SERIAL NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "email" TEXT NOT NULL, - "name" TEXT, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/prisma/migrations/20240818143249_updated_user_model/migration.sql b/prisma/migrations/20240818143249_updated_user_model/migration.sql deleted file mode 100644 index 978dd956..00000000 --- a/prisma/migrations/20240818143249_updated_user_model/migration.sql +++ /dev/null @@ -1,22 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `email` on the `User` table. All the data in the column will be lost. - - You are about to drop the column `name` on the `User` table. All the data in the column will be lost. - - A unique constraint covering the columns `[address]` on the table `User` will be added. If there are existing duplicate values, this will fail. - - Added the required column `address` to the `User` table without a default value. This is not possible if the table is not empty. - - Added the required column `message` to the `User` table without a default value. This is not possible if the table is not empty. - -*/ --- DropIndex -DROP INDEX "User_email_key"; - --- AlterTable -ALTER TABLE "User" DROP COLUMN "email", -DROP COLUMN "name", -ADD COLUMN "address" TEXT NOT NULL, -ADD COLUMN "message" TEXT NOT NULL, -ADD COLUMN "tncDocVersion" TEXT NOT NULL DEFAULT '1.0'; - --- CreateIndex -CREATE UNIQUE INDEX "User_address_key" ON "User"("address"); diff --git a/prisma/migrations/20240824114901_updated_user_model/migration.sql b/prisma/migrations/20240824114901_updated_user_model/migration.sql deleted file mode 100644 index 8a0bc979..00000000 --- a/prisma/migrations/20240824114901_updated_user_model/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[referralCode]` on the table `User` will be added. If there are existing duplicate values, this will fail. - - Added the required column `referralCode` to the `User` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "User" ADD COLUMN "referralCode" TEXT NOT NULL, -ALTER COLUMN "message" DROP NOT NULL, -ALTER COLUMN "tncDocVersion" DROP NOT NULL; - --- CreateIndex -CREATE UNIQUE INDEX "User_referralCode_key" ON "User"("referralCode"); diff --git a/prisma/migrations/20240824115758_updated_user_model/migration.sql b/prisma/migrations/20240824115758_updated_user_model/migration.sql deleted file mode 100644 index ea5ea2cf..00000000 --- a/prisma/migrations/20240824115758_updated_user_model/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "User" ADD COLUMN "isTncSigned" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/migrations/20240824121027_updated_user_model/migration.sql b/prisma/migrations/20240824121027_updated_user_model/migration.sql deleted file mode 100644 index 5ec0b272..00000000 --- a/prisma/migrations/20240824121027_updated_user_model/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "User" ALTER COLUMN "isTncSigned" DROP NOT NULL; diff --git a/prisma/migrations/20240824132519_added_refree_model/migration.sql b/prisma/migrations/20240824132519_added_refree_model/migration.sql deleted file mode 100644 index 0f289923..00000000 --- a/prisma/migrations/20240824132519_added_refree_model/migration.sql +++ /dev/null @@ -1,18 +0,0 @@ --- AlterTable -ALTER TABLE "User" ADD COLUMN "refreeCount" INTEGER DEFAULT 0; - --- CreateTable -CREATE TABLE "Refree" ( - "id" SERIAL NOT NULL, - "address" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "userId" INTEGER, - - CONSTRAINT "Refree_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "Refree_address_key" ON "Refree"("address"); - --- AddForeignKey -ALTER TABLE "Refree" ADD CONSTRAINT "Refree_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20240825061257_updated_user_model/migration.sql b/prisma/migrations/20240825061257_updated_user_model/migration.sql deleted file mode 100644 index 9920d717..00000000 --- a/prisma/migrations/20240825061257_updated_user_model/migration.sql +++ /dev/null @@ -1,32 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `refreeCount` on the `User` table. All the data in the column will be lost. - - You are about to drop the `Refree` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropForeignKey -ALTER TABLE "Refree" DROP CONSTRAINT "Refree_userId_fkey"; - --- AlterTable -ALTER TABLE "User" DROP COLUMN "refreeCount", -ADD COLUMN "referralCount" INTEGER DEFAULT 0; - --- DropTable -DROP TABLE "Refree"; - --- CreateTable -CREATE TABLE "Referral" ( - "id" SERIAL NOT NULL, - "refreeAddress" TEXT NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "referralId" INTEGER, - - CONSTRAINT "Referral_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "Referral_refreeAddress_key" ON "Referral"("refreeAddress"); - --- AddForeignKey -ALTER TABLE "Referral" ADD CONSTRAINT "Referral_referralId_fkey" FOREIGN KEY ("referralId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20240825064700_updated_user_model/migration.sql b/prisma/migrations/20240825064700_updated_user_model/migration.sql deleted file mode 100644 index 4cfcc1ce..00000000 --- a/prisma/migrations/20240825064700_updated_user_model/migration.sql +++ /dev/null @@ -1,23 +0,0 @@ -/* - Warnings: - - - The primary key for the `Referral` table will be changed. If it partially fails, the table could be left without primary key constraint. - - You are about to drop the column `id` on the `Referral` table. All the data in the column will be lost. - - Made the column `referralId` on table `Referral` required. This step will fail if there are existing NULL values in that column. - -*/ --- DropForeignKey -ALTER TABLE "Referral" DROP CONSTRAINT "Referral_referralId_fkey"; - --- AlterTable -CREATE SEQUENCE referral_referralid_seq; -ALTER TABLE "Referral" DROP CONSTRAINT "Referral_pkey", -DROP COLUMN "id", -ADD COLUMN "userId" INTEGER, -ALTER COLUMN "referralId" SET NOT NULL, -ALTER COLUMN "referralId" SET DEFAULT nextval('referral_referralid_seq'), -ADD CONSTRAINT "Referral_pkey" PRIMARY KEY ("referralId"); -ALTER SEQUENCE referral_referralid_seq OWNED BY "Referral"."referralId"; - --- AddForeignKey -ALTER TABLE "Referral" ADD CONSTRAINT "Referral_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20240825141032_updated_referral_model/migration.sql b/prisma/migrations/20240825141032_updated_referral_model/migration.sql deleted file mode 100644 index 13d9a7ae..00000000 --- a/prisma/migrations/20240825141032_updated_referral_model/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ -/* - Warnings: - - - Made the column `userId` on table `Referral` required. This step will fail if there are existing NULL values in that column. - -*/ --- DropForeignKey -ALTER TABLE "Referral" DROP CONSTRAINT "Referral_userId_fkey"; - --- AlterTable -ALTER TABLE "Referral" ALTER COLUMN "userId" SET NOT NULL; - --- AddForeignKey -ALTER TABLE "Referral" ADD CONSTRAINT "Referral_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20241028130637_changes/migration.sql b/prisma/migrations/20241028130637_changes/migration.sql new file mode 100644 index 00000000..1e851fd7 --- /dev/null +++ b/prisma/migrations/20241028130637_changes/migration.sql @@ -0,0 +1,111 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "address" TEXT NOT NULL, + "isTncSigned" BOOLEAN DEFAULT false, + "message" TEXT, + "tncDocVersion" TEXT DEFAULT '1.0', + "referralCode" TEXT NOT NULL, + "referralCount" INTEGER DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Raffle" ( + "raffleId" SERIAL NOT NULL, + "isRaffleParticipant" BOOLEAN DEFAULT false, + "sharedOnX" BOOLEAN DEFAULT false, + "activeDeposits" BOOLEAN DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "userId" INTEGER NOT NULL, + + CONSTRAINT "Raffle_pkey" PRIMARY KEY ("raffleId") +); + +-- CreateTable +CREATE TABLE "LuckyWinner" ( + "winnerId" SERIAL NOT NULL, + "updatedAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "raffleId" INTEGER NOT NULL, + "userId" INTEGER NOT NULL, + + CONSTRAINT "LuckyWinner_pkey" PRIMARY KEY ("winnerId") +); + +-- CreateTable +CREATE TABLE "Signatures" ( + "id" SERIAL NOT NULL, + "signature" TEXT NOT NULL, + "tncDocVersion" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "userId" INTEGER NOT NULL, + + CONSTRAINT "Signatures_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Referral" ( + "referralId" SERIAL NOT NULL, + "refreeAddress" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "userId" INTEGER NOT NULL, + + CONSTRAINT "Referral_pkey" PRIMARY KEY ("referralId") +); + +-- CreateTable +CREATE TABLE "og_nft_users" ( + "id" SERIAL NOT NULL, + "userId" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "og_nft_users_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_address_key" ON "User"("address"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_referralCode_key" ON "User"("referralCode"); + +-- CreateIndex +CREATE UNIQUE INDEX "Raffle_userId_raffleId_key" ON "Raffle"("userId", "raffleId"); + +-- CreateIndex +CREATE UNIQUE INDEX "LuckyWinner_userId_winnerId_key" ON "LuckyWinner"("userId", "winnerId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Signatures_userId_tncDocVersion_key" ON "Signatures"("userId", "tncDocVersion"); + +-- CreateIndex +CREATE UNIQUE INDEX "Referral_refreeAddress_key" ON "Referral"("refreeAddress"); + +-- CreateIndex +CREATE UNIQUE INDEX "Referral_userId_refreeAddress_key" ON "Referral"("userId", "refreeAddress"); + +-- CreateIndex +CREATE UNIQUE INDEX "og_nft_users_userId_key" ON "og_nft_users"("userId"); + +-- AddForeignKey +ALTER TABLE "Raffle" ADD CONSTRAINT "Raffle_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LuckyWinner" ADD CONSTRAINT "LuckyWinner_raffleId_fkey" FOREIGN KEY ("raffleId") REFERENCES "Raffle"("raffleId") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LuckyWinner" ADD CONSTRAINT "LuckyWinner_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Signatures" ADD CONSTRAINT "Signatures_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Referral" ADD CONSTRAINT "Referral_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "og_nft_users" ADD CONSTRAINT "og_nft_users_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20241029102731_added_round_id/migration.sql b/prisma/migrations/20241029102731_added_round_id/migration.sql new file mode 100644 index 00000000..cd1ffbac --- /dev/null +++ b/prisma/migrations/20241029102731_added_round_id/migration.sql @@ -0,0 +1,22 @@ +/* + Warnings: + + - The primary key for the `LuckyWinner` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `winnerId` on the `LuckyWinner` table. All the data in the column will be lost. + - A unique constraint covering the columns `[roundId]` on the table `LuckyWinner` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[userId,roundId]` on the table `LuckyWinner` will be added. If there are existing duplicate values, this will fail. + +*/ +-- DropIndex +DROP INDEX "LuckyWinner_userId_winnerId_key"; + +-- AlterTable +ALTER TABLE "LuckyWinner" DROP CONSTRAINT "LuckyWinner_pkey", +DROP COLUMN "winnerId", +ADD COLUMN "roundId" INTEGER NOT NULL DEFAULT 0; + +-- CreateIndex +CREATE UNIQUE INDEX "LuckyWinner_roundId_key" ON "LuckyWinner"("roundId"); + +-- CreateIndex +CREATE UNIQUE INDEX "LuckyWinner_userId_roundId_key" ON "LuckyWinner"("userId", "roundId"); diff --git a/prisma/migrations/20241029103119_updated_round_id_constraints/migration.sql b/prisma/migrations/20241029103119_updated_round_id_constraints/migration.sql new file mode 100644 index 00000000..087978e7 --- /dev/null +++ b/prisma/migrations/20241029103119_updated_round_id_constraints/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "LuckyWinner_roundId_key"; diff --git a/prisma/migrations/20241029103502_updated_round_id_constraints/migration.sql b/prisma/migrations/20241029103502_updated_round_id_constraints/migration.sql new file mode 100644 index 00000000..b4fbb617 --- /dev/null +++ b/prisma/migrations/20241029103502_updated_round_id_constraints/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "LuckyWinner" ALTER COLUMN "roundId" SET DEFAULT 1, +ADD CONSTRAINT "LuckyWinner_pkey" PRIMARY KEY ("roundId"); diff --git a/prisma/migrations/20241029103711_updated_round_id_constraints/migration.sql b/prisma/migrations/20241029103711_updated_round_id_constraints/migration.sql new file mode 100644 index 00000000..d186da9c --- /dev/null +++ b/prisma/migrations/20241029103711_updated_round_id_constraints/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - The primary key for the `LuckyWinner` table will be changed. If it partially fails, the table could be left without primary key constraint. + +*/ +-- AlterTable +ALTER TABLE "LuckyWinner" DROP CONSTRAINT "LuckyWinner_pkey", +ADD COLUMN "winnerId" SERIAL NOT NULL, +ADD CONSTRAINT "LuckyWinner_pkey" PRIMARY KEY ("winnerId"); diff --git a/prisma/migrations/20241029105811_updated_raffle_model/migration.sql b/prisma/migrations/20241029105811_updated_raffle_model/migration.sql new file mode 100644 index 00000000..ed8dfcf5 --- /dev/null +++ b/prisma/migrations/20241029105811_updated_raffle_model/migration.sql @@ -0,0 +1,18 @@ +/* + Warnings: + + - You are about to drop the column `roundId` on the `LuckyWinner` table. All the data in the column will be lost. + - A unique constraint covering the columns `[userId,winnerId]` on the table `LuckyWinner` will be added. If there are existing duplicate values, this will fail. + +*/ +-- DropIndex +DROP INDEX "LuckyWinner_userId_roundId_key"; + +-- AlterTable +ALTER TABLE "LuckyWinner" DROP COLUMN "roundId"; + +-- AlterTable +ALTER TABLE "Raffle" ADD COLUMN "roundId" INTEGER NOT NULL DEFAULT 1; + +-- CreateIndex +CREATE UNIQUE INDEX "LuckyWinner_userId_winnerId_key" ON "LuckyWinner"("userId", "winnerId"); diff --git a/prisma/migrations/20241029113615_updated_lucky_winner_model/migration.sql b/prisma/migrations/20241029113615_updated_lucky_winner_model/migration.sql new file mode 100644 index 00000000..d9c69c81 --- /dev/null +++ b/prisma/migrations/20241029113615_updated_lucky_winner_model/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `roundId` on the `Raffle` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "LuckyWinner" ADD COLUMN "roundId" SERIAL NOT NULL; + +-- AlterTable +ALTER TABLE "Raffle" DROP COLUMN "roundId"; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index 6bf90154..fbffa92c 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a8a99152..0e628ee9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,19 +8,51 @@ generator client { } model User { - id Int @id @default(autoincrement()) - address String @unique - isTncSigned Boolean? @default(false) + id Int @id @default(autoincrement()) + address String @unique + isTncSigned Boolean? @default(false) message String? - tncDocVersion String? @default("1.0") - referralCode String @unique + tncDocVersion String? @default("1.0") + referralCode String @unique referrals Referral[] - referralCount Int? @default(0) + referralCount Int? @default(0) + Raffles Raffle[] + og_nft_users og_nft_users? + Signatures Signatures[] + LuckyWinner LuckyWinner[] - createdAt DateTime @default(now()) - og_nft_users og_nft_users? - updatedAt DateTime @updatedAt @default(now()) - Signatures Signatures[] + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt +} + +model Raffle { + raffleId Int @id @default(autoincrement()) + + isRaffleParticipant Boolean? @default(false) + sharedOnX Boolean? @default(false) + activeDeposits Boolean? @default(false) + + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + + User User @relation(fields: [userId], references: [id]) + userId Int @unique + + LuckyWinner LuckyWinner[] +} + +model LuckyWinner { + winnerId Int @id @default(autoincrement()) + roundId Int @default(autoincrement()) + + updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + + Raffle Raffle @relation(fields: [raffleId], references: [raffleId]) + raffleId Int + + User User @relation(fields: [userId], references: [id]) + userId Int @unique } model Signatures { diff --git a/public/raffle-deposits.svg b/public/raffle-deposits.svg new file mode 100644 index 00000000..95342c85 --- /dev/null +++ b/public/raffle-deposits.svg @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/public/raffle-hero.svg b/public/raffle-hero.svg new file mode 100644 index 00000000..c534feaa --- /dev/null +++ b/public/raffle-hero.svg @@ -0,0 +1,1188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/raffle-register.svg b/public/raffle-register.svg new file mode 100644 index 00000000..a755bd0f --- /dev/null +++ b/public/raffle-register.svg @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/public/raffle-share.svg b/public/raffle-share.svg new file mode 100644 index 00000000..bd8de8c9 --- /dev/null +++ b/public/raffle-share.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/src/app/api/raffle/luckyWinner/route.ts b/src/app/api/raffle/luckyWinner/route.ts new file mode 100644 index 00000000..e908b194 --- /dev/null +++ b/src/app/api/raffle/luckyWinner/route.ts @@ -0,0 +1,147 @@ +import { db } from '@/db'; +import { Raffle } from '@prisma/client'; +import { NextResponse } from 'next/server'; + +export const dynamic = 'force-dynamic'; // static by default, unless reading the request + +export async function GET(request: Request) { + const authHeader = request.headers.get('authorization'); + if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) { + return new Response('Unauthorized', { + status: 401, + }); + } + + const { searchParams } = new URL(request.url); + let noOfWinners = parseInt(searchParams.get('winnersCount') || '0', 10); + console.log('No of winners requested:', noOfWinners); + + if (noOfWinners <= 0) { + return NextResponse.json({ + success: false, + message: 'Invalid number of winners requested', + }); + } + + try { + const raffleParticipants = await db.raffle.findMany({ + where: { + OR: [ + { isRaffleParticipant: true }, + { sharedOnX: true }, + { activeDeposits: true }, + ], + }, + }); + + if (raffleParticipants.length === 0) { + return NextResponse.json({ + success: false, + message: 'No raffle participants found', + }); + } + + console.log('Total raffle participants:', raffleParticipants.length); + + const totalExistingWinners = await db.luckyWinner.findMany({ + select: { + userId: true, + }, + }); + if (totalExistingWinners.length == raffleParticipants.length) { + return NextResponse.json({ + success: false, + message: 'No new participants found', + }); + } + + // index to ticket owner map + const ticketOwners: Raffle[] = []; + + let uniqueUsersCount = 0; + raffleParticipants.forEach((participant) => { + const exists = totalExistingWinners.find( + (winner) => winner.userId === participant.userId, + ); + if (exists) return; + + uniqueUsersCount++; + if (participant.isRaffleParticipant) { + ticketOwners.push(participant); + } + if (participant.sharedOnX) { + ticketOwners.push(participant); + } + if (participant.activeDeposits) { + ticketOwners.push(participant); + } + }); + + if (uniqueUsersCount < noOfWinners) { + noOfWinners = uniqueUsersCount; + } + + if (noOfWinners === 0) { + return NextResponse.json({ + success: false, + message: 'No winners to select', + }); + } + + const luckyWinners: Raffle[] = []; + + // Continue selecting winners until we reach the desired count + while (luckyWinners.length < noOfWinners) { + // Try to find a participant from each group in order + if (ticketOwners.length === 0) continue; + + // Keep searching within the current group until a valid participant is found + const randomIndex = Math.floor(Math.random() * ticketOwners.length); + const selectedParticipant = ticketOwners[randomIndex]; + + // assert selectedParticipant is not already a winner + const exists = luckyWinners.find( + (winner) => winner.userId === selectedParticipant.userId, + ); + if (exists) continue; + + luckyWinners.push(selectedParticipant); + } + + // Check if we were able to select enough winners + if (luckyWinners.length == 0) { + return NextResponse.json({ + success: false, + message: 'No winner found', + }); + } + + // Add selected users to the LuckyWinner table + const maxRoundIdInfo = await db.luckyWinner.findFirst({ + select: { roundId: true }, + orderBy: { roundId: 'desc' }, + }); + const maxRoundId = maxRoundIdInfo ? maxRoundIdInfo.roundId : 0; + const newLuckyWinners = await db.luckyWinner.createMany({ + data: luckyWinners.map((userId) => ({ + roundId: maxRoundId + 1, + raffleId: userId.raffleId, + userId: userId.userId, + })), + }); + + console.log('Lucky winners selected successfully:', newLuckyWinners); + + return NextResponse.json({ + success: true, + message: 'Lucky winners selected successfully', + luckyWinners: newLuckyWinners, + }); + } catch (error) { + console.error('Error selecting lucky winners:', error); + return NextResponse.json({ + success: false, + message: 'An error occurred while selecting lucky winners', + }); + } +} diff --git a/src/app/api/raffle/route.ts b/src/app/api/raffle/route.ts new file mode 100644 index 00000000..d4182be3 --- /dev/null +++ b/src/app/api/raffle/route.ts @@ -0,0 +1,157 @@ +import { NextResponse } from 'next/server'; + +import { db } from '@/db'; +import { getStrategies } from '@/store/strategies.atoms'; +import { standariseAddress } from '@/utils'; + +export async function POST(req: Request) { + const { address, type } = await req.json(); + + if (!address || !type) { + return NextResponse.json({ + success: false, + message: 'address not found', + user: null, + }); + } + + if (!type || !['SHARED_ON_X', 'ACTIVE_DEPOSITS', 'REGISTER'].includes(type)) { + return NextResponse.json({ + success: false, + message: 'Invalid type', + }); + } + + // standardised address + let parsedAddress = address; + try { + parsedAddress = standariseAddress(address); + } catch (e) { + throw new Error('Invalid address'); + } + + const user = await db.user.findFirst({ + where: { + address: parsedAddress, + }, + }); + + if (!user) { + return NextResponse.json({ + success: false, + message: 'User not found', + user: null, + }); + } + + if (type === 'REGISTER') { + const raffleUser = await db.raffle.findFirst({ + where: { + userId: user.id, + }, + }); + + if (raffleUser) { + return NextResponse.json({ + success: false, + message: 'User already registered', + user: raffleUser, + }); + } + + const createdUser = await db.raffle.create({ + data: { + isRaffleParticipant: true, + User: { + connect: { + address: parsedAddress, + }, + }, + }, + }); + + return NextResponse.json({ + success: true, + message: 'User registered for raffle successfully', + user: createdUser, + }); + } + + const raffleUser = await db.raffle.findFirst({ + where: { + userId: user.id, + isRaffleParticipant: true, + }, + }); + + if (!raffleUser) { + return NextResponse.json({ + success: false, + message: 'Registered user not found', + user: null, + }); + } + + if (type === 'SHARED_ON_X') { + const updatedUser = await db.raffle.update({ + where: { + raffleId: raffleUser.raffleId, + }, + data: { + sharedOnX: true, + }, + }); + + return NextResponse.json({ + success: true, + message: 'Shared on X successfully', + user: updatedUser, + }); + } + + if (type === 'ACTIVE_DEPOSITS') { + const strategies = getStrategies(); + + const values = strategies.map(async (strategy) => { + const balanceInfo = await strategy.getUserTVL(parsedAddress); + return { + id: strategy.id, + usdValue: balanceInfo.usdValue, + tokenInfo: { + name: balanceInfo.tokenInfo.name, + symbol: balanceInfo.tokenInfo.name, + logo: balanceInfo.tokenInfo.logo, + decimals: balanceInfo.tokenInfo.decimals, + displayDecimals: balanceInfo.tokenInfo.displayDecimals, + }, + amount: balanceInfo.amount.toEtherStr(), + }; + }); + + const result = await Promise.all(values); + const sum = result.reduce((acc, item) => acc + item.usdValue, 0); + + if (sum > 10) { + const createdUser = await db.raffle.update({ + where: { + raffleId: raffleUser.raffleId, + }, + data: { + activeDeposits: true, + }, + }); + + return NextResponse.json({ + success: true, + message: 'Active deposits found', + user: createdUser, + }); + } + + return NextResponse.json({ + success: false, + message: 'No active deposits found', + user: null, + }); + } +} diff --git a/src/app/api/tnc/getUser/[address]/route.ts b/src/app/api/tnc/getUser/[address]/route.ts index 8947b65d..6647c810 100644 --- a/src/app/api/tnc/getUser/[address]/route.ts +++ b/src/app/api/tnc/getUser/[address]/route.ts @@ -3,10 +3,13 @@ import { NextResponse } from 'next/server'; import { db } from '@/db'; import { standariseAddress } from '@/utils'; -export async function GET(_req: Request, context: any) { +export async function GET(req: Request, context: any) { const { params } = context; const address = params.address; + const { searchParams } = new URL(req.url); + const type = searchParams.get('type'); + if (!address) { return NextResponse.json({ success: false, @@ -33,6 +36,27 @@ export async function GET(_req: Request, context: any) { }); } + if (type && type === 'RAFFLE') { + const raffleUser = await db.raffle.findFirst({ + where: { + userId: user.id, + }, + }); + + if (!raffleUser) { + return NextResponse.json({ + success: false, + message: 'Raffle user not found', + user: null, + }); + } + + return NextResponse.json({ + success: true, + user: raffleUser, + }); + } + return NextResponse.json({ success: true, user, diff --git a/src/app/raffle/_components/active-deposits.tsx b/src/app/raffle/_components/active-deposits.tsx new file mode 100644 index 00000000..ac28e2c2 --- /dev/null +++ b/src/app/raffle/_components/active-deposits.tsx @@ -0,0 +1,114 @@ +'use client'; + +import { Spinner } from '@chakra-ui/react'; +import { useAccount } from '@starknet-react/core'; +import axios from 'axios'; +import Image from 'next/image'; +import React from 'react'; +import toast from 'react-hot-toast'; + +const ActiveDeposits = () => { + const { address } = useAccount(); + + const [loading, setLoading] = React.useState(false); + const [initialLoading, setInitialLoading] = React.useState(false); + const [isActiveDeposits, setIsActiveDeposits] = React.useState(false); + + const handleActiveDeposits = async () => { + setLoading(true); + + try { + const res = await axios.post('/api/raffle', { + address, + type: 'ACTIVE_DEPOSITS', + }); + + if (res?.data?.success) { + setIsActiveDeposits(true); + toast.success('Successfully completed!', { + position: 'bottom-right', + }); + } else if ( + !res?.data?.success && + res?.data?.message === 'No active deposits found' + ) { + toast.error('You should have atleast $10 deposited in STRKFarm', { + position: 'bottom-right', + }); + } + } catch (error) { + console.error(error); + toast.error('Something went wrong', { + position: 'bottom-right', + }); + } finally { + setLoading(false); + } + }; + + React.useEffect(() => { + if (!address) return; + + setInitialLoading(true); + setIsActiveDeposits(false); + + (async () => { + try { + const res = await axios.get(`/api/tnc/getUser/${address}`, { + params: { type: 'RAFFLE' }, + }); + + if (res?.data?.success && res?.data?.user?.activeDeposits) { + setIsActiveDeposits(true); + } else setIsActiveDeposits(false); + } catch (error) { + console.error(error); + toast.error('Something went wrong', { + position: 'bottom-right', + }); + } finally { + setInitialLoading(false); + } + })(); + }, [address]); + + return ( +
+
+
+ STRKFarm +

+ Deposit atleast $10 in STRKFarm +

+
+ + +
+
+ ); +}; + +export default ActiveDeposits; diff --git a/src/app/raffle/_components/raffle-timer.tsx b/src/app/raffle/_components/raffle-timer.tsx new file mode 100644 index 00000000..aeccdeb4 --- /dev/null +++ b/src/app/raffle/_components/raffle-timer.tsx @@ -0,0 +1,63 @@ +'use client'; + +import React, { useEffect, useState } from 'react'; + +const RaffleTimer: React.FC = () => { + const [timeLeft, setTimeLeft] = useState({ + days: 0, + hours: 0, + minutes: 0, + seconds: 0, + }); + + useEffect(() => { + const endDate = new Date('2024-11-11T23:59:59'); + + const updateTimer = () => { + const now = new Date(); + const difference = endDate.getTime() - now.getTime(); + + if (difference > 0) { + setTimeLeft({ + days: Math.floor(difference / (1000 * 60 * 60 * 24)), + hours: Math.floor((difference / (1000 * 60 * 60)) % 24), + minutes: Math.floor((difference / (1000 * 60)) % 60), + seconds: Math.floor((difference / 1000) % 60), + }); + } else { + clearInterval(timer); + } + }; + + const timer = setInterval(updateTimer, 1000); + + return () => clearInterval(timer); + }, []); + + return ( +
+
+ {timeLeft.days} + + Days + +
+
+ {timeLeft.hours} + Hrs +
+
+ {timeLeft.minutes} + + Mins + +
+
+ {timeLeft.seconds} + Sec +
+
+ ); +}; + +export default RaffleTimer; diff --git a/src/app/raffle/_components/register-raffle.tsx b/src/app/raffle/_components/register-raffle.tsx new file mode 100644 index 00000000..f1b79d4d --- /dev/null +++ b/src/app/raffle/_components/register-raffle.tsx @@ -0,0 +1,108 @@ +'use client'; + +import { Spinner } from '@chakra-ui/react'; +import { useAccount } from '@starknet-react/core'; +import axios from 'axios'; +import Image from 'next/image'; +import React from 'react'; +import toast from 'react-hot-toast'; + +const RegisterRaffle: React.FC = () => { + const { address } = useAccount(); + + const [isUserRegistered, setIsUserRegistered] = React.useState(false); + const [loading, setLoading] = React.useState(false); + const [initialLoading, setInitialLoading] = React.useState(false); + + const handleRegister = async () => { + setLoading(true); + + if (isUserRegistered) return; + + try { + const res = await axios.post('/api/raffle', { + address, + type: 'REGISTER', + }); + if (res?.data?.success) { + setIsUserRegistered(true); + toast.success('Successfully completed!', { + position: 'bottom-right', + }); + } + } catch (error) { + console.error(error); + toast.error('Something went wrong', { + position: 'bottom-right', + }); + } finally { + setLoading(false); + } + + setLoading(false); + }; + + React.useEffect(() => { + if (!address) return; + + setInitialLoading(true); + setIsUserRegistered(false); + + (async () => { + try { + const res = await axios.get(`/api/tnc/getUser/${address}`, { + params: { type: 'RAFFLE' }, + }); + + if (res?.data?.success && res?.data?.user?.isRaffleParticipant) { + setIsUserRegistered(true); + } else setIsUserRegistered(false); + } catch (error) { + console.error(error); + toast.error('Something went wrong', { + position: 'bottom-right', + }); + } finally { + setInitialLoading(false); + } + })(); + }, [address]); + + return ( +
+
+
+ STRKFarm +

+ Register if you are coming to Devcon and get one ticket. +

+
+ + +
+
+ ); +}; + +export default RegisterRaffle; diff --git a/src/app/raffle/_components/share-on-x.tsx b/src/app/raffle/_components/share-on-x.tsx new file mode 100644 index 00000000..ce18fd45 --- /dev/null +++ b/src/app/raffle/_components/share-on-x.tsx @@ -0,0 +1,105 @@ +'use client'; + +import { Spinner } from '@chakra-ui/react'; +import { useAccount } from '@starknet-react/core'; +import axios from 'axios'; +import Image from 'next/image'; +import Link from 'next/link'; +import React from 'react'; +import toast from 'react-hot-toast'; + +const ShareOnX = () => { + const { address } = useAccount(); + + const [loading, setLoading] = React.useState(false); + const [initialLoading, setInitialLoading] = React.useState(false); + const [isSharedOnX, setIsSharedOnX] = React.useState(false); + + const handleShare = async () => { + setLoading(true); + + try { + const res = await axios.post('/api/raffle', { + address, + type: 'SHARED_ON_X', + }); + + if (res?.data?.success) { + await new Promise((resolve) => setTimeout(resolve, 8000)); + setIsSharedOnX(true); + toast.success('Successfully completed!', { + position: 'bottom-right', + }); + } + } catch (error) { + console.error(error); + toast.error('Something went wrong', { + position: 'bottom-right', + }); + } finally { + setLoading(false); + } + }; + + React.useEffect(() => { + if (!address) return; + + setInitialLoading(true); + setIsSharedOnX(false); + + (async () => { + try { + const res = await axios.get(`/api/tnc/getUser/${address}`, { + params: { type: 'RAFFLE' }, + }); + + if (res?.data?.success && res?.data?.user?.sharedOnX) { + setIsSharedOnX(true); + } else setIsSharedOnX(false); + } catch (error) { + console.error(error); + toast.error('Something went wrong'); + } finally { + setInitialLoading(false); + } + })(); + }, [address]); + + return ( +
+
+
+ STRKFarm +

+ RT our tweet +

+
+ + {}} + > + {loading && ( + + )} + {initialLoading && 'loading...'} + {isSharedOnX && 'completed'} + {!isSharedOnX && !initialLoading && '1 ticket'} + +
+
+ ); +}; + +export default ShareOnX; diff --git a/src/app/raffle/_components/total-tickets.tsx b/src/app/raffle/_components/total-tickets.tsx new file mode 100644 index 00000000..10a20455 --- /dev/null +++ b/src/app/raffle/_components/total-tickets.tsx @@ -0,0 +1,60 @@ +'use client'; + +import { Spinner } from '@chakra-ui/react'; +import { useAccount } from '@starknet-react/core'; +import axios from 'axios'; +import React from 'react'; +import toast from 'react-hot-toast'; + +const TotalTickets: React.FC = () => { + const { address } = useAccount(); + + const [totalTickets, setTotalTickets] = React.useState(0); + const [loading, setLoading] = React.useState(false); + + React.useEffect(() => { + if (!address) return; + + setLoading(true); + setTotalTickets(0); + + (async () => { + try { + const res = await axios.get(`/api/tnc/getUser/${address}`, { + params: { type: 'RAFFLE' }, + }); + + if (res?.data?.success && res?.data?.user?.isRaffleParticipant) { + setTotalTickets((prev) => prev + 1); + console.log('+1'); + } + if (res?.data?.success && res?.data?.user?.sharedOnX) { + setTotalTickets((prev) => prev + 1); + console.log('+1 +1'); + } + if (res?.data?.success && res?.data?.user?.activeDeposits) { + setTotalTickets((prev) => prev + 1); + console.log('+1 +1 +1'); + } + } catch (error) { + console.error(error); + toast.error('Something went wrong', { + position: 'bottom-right', + }); + } finally { + setLoading(false); + } + })(); + }, [address]); + + return ( +

+ Your tickets:{' '} + + {!loading ? totalTickets : } + +

+ ); +}; + +export default TotalTickets; diff --git a/src/app/raffle/page.tsx b/src/app/raffle/page.tsx new file mode 100644 index 00000000..a98e41e6 --- /dev/null +++ b/src/app/raffle/page.tsx @@ -0,0 +1,108 @@ +import { NextPage } from 'next'; +import Image from 'next/image'; +import React from 'react'; + +import ActiveDeposits from './_components/active-deposits'; +import RaffleTimer from './_components/raffle-timer'; +import RegisterRaffle from './_components/register-raffle'; +import ShareOnX from './_components/share-on-x'; +import TotalTickets from './_components/total-tickets'; + +const Raffle: NextPage = () => { + return ( +
+
+
+

+ Devcon raffle +

+

+ Each day, we shall select 3 winners who will receive exclusive merch + during Starkspace (Devcon, Bangkok) +

+ + +
+ + Raffle Hero Image +
+ +
+
+

+ Earn Raffle tickets for every task and increase chances to win +

+ +
+

+ Raffle end's in +

+ +
+
+
+ +
+
+

Tasks

+
+

+ Participate and + + {' '} + get Raffle tickets + +

+ + +
+ +
+ + + + + +
+
+
+ +
+
+ Rules: +
+

+ 1. 3 unique winners will be selected each day +

+

+ 2. You just have to register once and you will be part of each round + automatically +

+

+ 3. You have to register if you want to participate. This mean you or + anyone on your behalf will be available to collect the merch.{' '} +

+

+ 4. The rewards will be in the form of exclusive merch reserved for you +

+

+ 5. Selected winners can collect their merch on 13th Nov, from The Fig + Lobby, Bangkok +

+

+ 6. Winners will be announced on our socials (i.e. X, TG, etc.) + everyday +

+
+
+ ); +}; + +export default Raffle; diff --git a/src/app/strategy/[strategyId]/_components/Strategy.tsx b/src/app/strategy/[strategyId]/_components/Strategy.tsx index 25de2ada..be369268 100755 --- a/src/app/strategy/[strategyId]/_components/Strategy.tsx +++ b/src/app/strategy/[strategyId]/_components/Strategy.tsx @@ -46,8 +46,6 @@ import { import { StrategyParams } from '../page'; import MyNumber from '@/utils/MyNumber'; import { ExternalLinkIcon } from '@chakra-ui/icons'; -import { isMobile } from 'react-device-detect'; -import CONSTANTS from '@/constants'; const Strategy = ({ params }: StrategyParams) => { const { address } = useAccount(); @@ -247,9 +245,7 @@ const Strategy = ({ params }: StrategyParams) => { ) == 0 ? '-' : `${balData.data.amount.toEtherToFixedDecimals(balData.data.tokenInfo?.displayDecimals || 2)} ${balData.data.tokenInfo?.name}` - : isMobile - ? CONSTANTS.MOBILE_MSG - : 'Connect wallet'} + : 'Connect wallet'} @@ -276,8 +272,6 @@ const Strategy = ({ params }: StrategyParams) => { Your Holdings: {address ? ( - ) : isMobile ? ( - CONSTANTS.MOBILE_MSG ) : ( 'Connect wallet' )} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 30909446..00714251 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -49,12 +49,12 @@ import { import mixpanel from 'mixpanel-browser'; import { useEffect } from 'react'; import { isMobile } from 'react-device-detect'; -import TncModal from './TncModal'; import { ArgentMobileConnector, isInArgentMobileAppBrowser, } from 'starknetkit/argentMobile'; import { WebWalletConnector } from 'starknetkit/webwallet'; +import TncModal from './TncModal'; export const MYCONNECTORS: any[] = isInArgentMobileAppBrowser() ? [ @@ -273,17 +273,17 @@ export default function Navbar(props: NavbarProps) { */} - + + + +