diff --git a/components.json b/components.json index e74c0a9..379cda9 100644 --- a/components.json +++ b/components.json @@ -13,4 +13,4 @@ "utils": "@/lib/utils", "components": "@/components" } -} \ No newline at end of file +} diff --git a/next.config.mjs b/next.config.mjs index 7b7c098..4678774 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = {}; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index d31bc9b..90e9525 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "jodit-react": "^4.1.2", "ldrs": "^1.0.2", "lucide-react": "^0.408.0", "next": "14.2.5", @@ -47,6 +48,7 @@ "@types/react-dom": "^18", "autoprefixer": "^10.4.19", "postcss": "^8", + "prettier": "^3.3.3", "supabase": "^1.187.3", "tailwindcss": "^3.4.1", "typescript": "^5" @@ -2393,6 +2395,16 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/autobind-decorator": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/autobind-decorator/-/autobind-decorator-2.4.0.tgz", + "integrity": "sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==", + "license": "MIT", + "engines": { + "node": ">=8.10", + "npm": ">=6.4.1" + } + }, "node_modules/autoprefixer": { "version": "10.4.19", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", @@ -3423,6 +3435,28 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jodit": { + "version": "4.2.27", + "resolved": "https://registry.npmjs.org/jodit/-/jodit-4.2.27.tgz", + "integrity": "sha512-cqqeunB3HMElnocVhs5Qq2bhgpMIT2vKQPBpKcOTWKvX6GJ0GYAIneMEf43lphJuo+119CvBE8YgljD5iTfsAQ==", + "license": "MIT", + "dependencies": { + "autobind-decorator": "^2.4.0" + } + }, + "node_modules/jodit-react": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/jodit-react/-/jodit-react-4.1.2.tgz", + "integrity": "sha512-Hs1evpM1IK5zvy/5m5Gk819L8aC+9EmEdQvCoLHVUr/R3vtH4nYFD6wsMRj3ur3J4ZHhaSBjt0N3R7ggwP405Q==", + "license": "MIT", + "dependencies": { + "jodit": "^4.2.10" + }, + "peerDependencies": { + "react": "~0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "~0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -4084,6 +4118,22 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", diff --git a/package.json b/package.json index 5bb01f8..a785146 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "pretty": "prettier --write \"./**/*.{js,jsx,mjs,cjs,ts,tsx,json}\"" }, "dependencies": { "@emotion/react": "^11.12.0", @@ -26,6 +27,7 @@ "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "jodit-react": "^4.1.2", "ldrs": "^1.0.2", "lucide-react": "^0.408.0", "next": "14.2.5", @@ -48,6 +50,7 @@ "@types/react-dom": "^18", "autoprefixer": "^10.4.19", "postcss": "^8", + "prettier": "^3.3.3", "supabase": "^1.187.3", "tailwindcss": "^3.4.1", "typescript": "^5" diff --git a/src/app/api/logout/route.ts b/src/app/api/logout/route.ts index e2bc148..66a2743 100644 --- a/src/app/api/logout/route.ts +++ b/src/app/api/logout/route.ts @@ -1,14 +1,14 @@ -import {NextRequest, NextResponse} from "next/server"; -import {createClient} from "@/utils/supabase/server"; +import { NextRequest, NextResponse } from "next/server"; +import { createClient } from "@/utils/supabase/server"; export async function GET(req: NextRequest) { - const supabase = createClient() + const supabase = createClient(); - const { error } = await supabase.auth.signOut() + const { error } = await supabase.auth.signOut(); if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } - return NextResponse.json({ data: 'Logout successful' }, { status: 200 }); -} \ No newline at end of file + return NextResponse.json({ data: "Logout successful" }, { status: 200 }); +} diff --git a/src/app/api/rest/v1/isUsername/route.ts b/src/app/api/rest/v1/isUsername/route.ts index 6e60734..48b03b7 100644 --- a/src/app/api/rest/v1/isUsername/route.ts +++ b/src/app/api/rest/v1/isUsername/route.ts @@ -1,4 +1,4 @@ -import {NextRequest, NextResponse} from "next/server"; +import { NextRequest, NextResponse } from "next/server"; import { createClient } from "@/utils/supabase/server"; export async function POST(req: NextRequest): Promise { @@ -6,13 +6,15 @@ export async function POST(req: NextRequest): Promise { const supabase = createClient(); const body = await req.json(); if (!body.username) { - return NextResponse.json({ error: 'username is required' }, { status: 400 }); + return NextResponse.json( + { error: "username is required" }, + { status: 400 }, + ); } - const { data, error } = await supabase - .rpc('is_username_exist', - { username: body.username } - ); + const { data, error } = await supabase.rpc("is_username_exist", { + username: body.username, + }); if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); @@ -22,4 +24,4 @@ export async function POST(req: NextRequest): Promise { } catch (error: any) { return NextResponse.json({ error: error.message }, { status: 500 }); } -} \ No newline at end of file +} diff --git a/src/app/api/rest/v1/users/route.ts b/src/app/api/rest/v1/users/route.ts index 024472b..bf0959d 100644 --- a/src/app/api/rest/v1/users/route.ts +++ b/src/app/api/rest/v1/users/route.ts @@ -1,4 +1,4 @@ -import {NextRequest, NextResponse} from "next/server"; +import { NextRequest, NextResponse } from "next/server"; import { createClient } from "@/utils/supabase/server"; import { User } from "@supabase/supabase-js"; @@ -7,28 +7,28 @@ export async function POST(req: NextRequest): Promise { request: { headers: req.headers, }, - }) + }); - const body = await req.json() + const body = await req.json(); const supabase = createClient(); const searchParams = req.nextUrl.searchParams; - const option = searchParams.get('option'); + const option = searchParams.get("option"); let data: User | User[] | null = null; - if ( option === 'insert' ) { + if (option === "insert") { const { data: user, error: error } = await supabase - .from('users') - .insert([{id: body.id, username: body.username, admin: body.admin}]) + .from("users") + .insert([{ id: body.id, username: body.username, admin: body.admin }]); if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } data = user; - } else if ( option === 'update' ) { + } else if (option === "update") { const { data: user, error: error } = await supabase - .from('users') - .update({username: body.username}) - .match({id: body.id}) + .from("users") + .update({ username: body.username }) + .match({ id: body.id }); if (error) { return NextResponse.json({ error: error.message }, { status: 500 }); } @@ -36,4 +36,4 @@ export async function POST(req: NextRequest): Promise { } return NextResponse.json({ data: data }); -} \ No newline at end of file +} diff --git a/src/app/api/update-password/route.ts b/src/app/api/update-password/route.ts index c6f45c0..6d20ecc 100644 --- a/src/app/api/update-password/route.ts +++ b/src/app/api/update-password/route.ts @@ -1,19 +1,26 @@ -import {NextRequest, NextResponse} from "next/server"; -import { cookies } from 'next/headers'; -import {createClient} from "@/utils/supabase/server"; +import { NextRequest, NextResponse } from "next/server"; +import { cookies } from "next/headers"; +import { createClient } from "@/utils/supabase/server"; export async function GET(req: NextRequest) { - const requestUrl = new URL(req.nextUrl.href) - const code = requestUrl.searchParams.get('code') + const requestUrl = new URL(req.nextUrl.href); + const code = requestUrl.searchParams.get("code"); - if( code ) { - const supabase = createClient() - const { error } = await supabase.auth.exchangeCodeForSession(code) + if (code) { + const supabase = createClient(); + const { error } = await supabase.auth.exchangeCodeForSession(code); - return NextResponse.redirect(new URL(`${req.nextUrl.origin}/auth/update_password`, req.nextUrl.href)) + return NextResponse.redirect( + new URL(`${req.nextUrl.origin}/auth/update_password`, req.nextUrl.href), + ); } - console.log({error : 'ERROR: Invalid auth code or no auth code found'}, { status: 500 }) + console.log( + { error: "ERROR: Invalid auth code or no auth code found" }, + { status: 500 }, + ); - return NextResponse.redirect(new URL(`${req.nextUrl.origin}/auth`, req.nextUrl.href)) -} \ No newline at end of file + return NextResponse.redirect( + new URL(`${req.nextUrl.origin}/auth`, req.nextUrl.href), + ); +} diff --git a/src/app/api/v1/create/event/route.ts b/src/app/api/v1/create/event/route.ts index 1c1e4ec..dd1454d 100644 --- a/src/app/api/v1/create/event/route.ts +++ b/src/app/api/v1/create/event/route.ts @@ -7,12 +7,10 @@ async function convertToAvif(inputFile: File): Promise { const arrayBuffer = await inputFile.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); - const avifBuffer = await sharp(buffer) - .avif() - .toBuffer(); + const avifBuffer = await sharp(buffer).avif().toBuffer(); - const blob = new Blob([avifBuffer], { type: 'image/avif' }); - const avifFile = new File([blob], inputFile.name, { type: 'image/avif' }); + const blob = new Blob([avifBuffer], { type: "image/avif" }); + const avifFile = new File([blob], inputFile.name, { type: "image/avif" }); return avifFile; } @@ -43,7 +41,7 @@ export async function POST(request: NextRequest): Promise { if (userError || !user) { return NextResponse.json( { success: false, message: "Unauthorized" }, - { status: 401 } + { status: 401 }, ); } @@ -51,10 +49,15 @@ export async function POST(request: NextRequest): Promise { .from("users") .select("admin") .eq("id", user.id); - if (isAdmin.error || !isAdmin.data || isAdmin.data.length === 0 || !isAdmin.data[0].admin) { + if ( + isAdmin.error || + !isAdmin.data || + isAdmin.data.length === 0 || + !isAdmin.data[0].admin + ) { return NextResponse.json( { success: false, message: "Unauthorized" }, - { status: 401 } + { status: 401 }, ); } @@ -75,14 +78,13 @@ export async function POST(request: NextRequest): Promise { poster = await convertToAvif(poster_file); } - delete event.poster; const validation = validateEvent(event); if (!validation.valid) { return NextResponse.json( { success: false, message: validation.message }, - { status: 400 } + { status: 400 }, ); } @@ -100,7 +102,6 @@ export async function POST(request: NextRequest): Promise { } if (poster_file && poster) { - const { error: posterUploadError } = await supabase.storage .from(process.env.BUCKET || "") .upload(`/events/${eventId}/poster`, poster, { @@ -120,7 +121,7 @@ export async function POST(request: NextRequest): Promise { } catch (error: any) { return NextResponse.json( { success: false, message: error.message }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/app/api/v1/delete/event/route.ts b/src/app/api/v1/delete/event/route.ts index 68bb873..2ddbb8c 100644 --- a/src/app/api/v1/delete/event/route.ts +++ b/src/app/api/v1/delete/event/route.ts @@ -12,14 +12,22 @@ export async function DELETE(request: NextRequest): Promise { if (userError || !user) { return NextResponse.json( { success: false, message: "Unauthorized" }, - { status: 401 } + { status: 401 }, ); } - const isAdmin = await supabase.from("users").select("admin").eq("id", user.id); - if (isAdmin.error || !isAdmin.data || isAdmin.data.length === 0 || !isAdmin.data[0].admin) { + const isAdmin = await supabase + .from("users") + .select("admin") + .eq("id", user.id); + if ( + isAdmin.error || + !isAdmin.data || + isAdmin.data.length === 0 || + !isAdmin.data[0].admin + ) { return NextResponse.json( { success: false, message: "Unauthorized" }, - { status: 401 } + { status: 401 }, ); } const url = new URL(request.url); @@ -28,7 +36,7 @@ export async function DELETE(request: NextRequest): Promise { if (!id) { return NextResponse.json( { success: false, message: "Invalid or missing event ID" }, - { status: 400 } + { status: 400 }, ); } @@ -41,10 +49,10 @@ export async function DELETE(request: NextRequest): Promise { if (eventError) { return NextResponse.json( { success: false, message: "Event not found" }, - { status: 404 } + { status: 404 }, ); } - + const { error } = await supabase.from("events").delete().eq("id", id); if (error) { @@ -58,7 +66,7 @@ export async function DELETE(request: NextRequest): Promise { } catch (error: any) { return NextResponse.json( { success: false, message: error.message }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/app/api/v1/get/blog/route.ts b/src/app/api/v1/get/blog/route.ts index f1637fb..1fb7f51 100644 --- a/src/app/api/v1/get/blog/route.ts +++ b/src/app/api/v1/get/blog/route.ts @@ -6,59 +6,59 @@ export async function GET(request: NextRequest): Promise { try { const supabase = createClient(); const url = new URL(request.url); - const id = url.searchParams.get('id'); + const id = url.searchParams.get("id"); if (!id) { return NextResponse.json( - { success: false, message: 'Missing blog ID' }, - { status: 400 } + { success: false, message: "Missing blog ID" }, + { status: 400 }, ); } const { data: blog, error: blogError } = await supabase - .from('blogs') - .select('*') - .eq('id', id) + .from("blogs") + .select("*") + .eq("id", id) .single(); if (blogError || !blog) { return NextResponse.json( - { success: false, message: 'Blog not found' }, - { status: 404 } + { success: false, message: "Blog not found" }, + { status: 404 }, ); } const posterUrl = `${getPublicUrl(`/images/${id}/poster`)}`; const blogFileUrl = `${getPublicUrl(`/blogs/${id}/blog`)}`; - const { data: images, error: imagesError } = await supabase - .storage + const { data: images, error: imagesError } = await supabase.storage .from(process.env.BUCKET || "") .list(`images/${id}/`); if (imagesError) { return NextResponse.json( - { success: false, message: 'Error fetching images' }, - { status: 500 } + { success: false, message: "Error fetching images" }, + { status: 500 }, ); } - const imageUrls = images.map(image => `${getPublicUrl(`/blogs/${id}/images/${image.name}`)}`); - + const imageUrls = images.map( + (image) => `${getPublicUrl(`/blogs/${id}/images/${image.name}`)}`, + ); return NextResponse.json({ success: true, - message: 'Blog retrieved successfully', + message: "Blog retrieved successfully", blog: { ...blog, posterUrl, blogFileUrl, - images: imageUrls - } + images: imageUrls, + }, }); } catch (error: any) { return NextResponse.json( { success: false, message: error.message }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/app/api/v1/get/blogs/route.ts b/src/app/api/v1/get/blogs/route.ts index 60ee324..ed3b0bf 100644 --- a/src/app/api/v1/get/blogs/route.ts +++ b/src/app/api/v1/get/blogs/route.ts @@ -5,39 +5,37 @@ import { getPublicUrl } from "@/lib/utils"; export async function GET(request: NextRequest): Promise { try { const supabase = createClient(); - + const url = new URL(request.url); - const limit = url.searchParams.get('limit'); - const page = url.searchParams.get('page'); + const limit = url.searchParams.get("limit"); + const page = url.searchParams.get("page"); const limitNumber = limit ? parseInt(limit) : 5; const pageNumber = page ? parseInt(page) : 1; if (pageNumber < 1 || limitNumber < 1) { return NextResponse.json( - { success: false, message: 'Page and limit must be greater than 0' }, - { status: 400 } + { success: false, message: "Page and limit must be greater than 0" }, + { status: 400 }, ); } const offset = (pageNumber - 1) * limitNumber; const { data: blogs, error: blogsError } = await supabase - .from('blogs') - .select('*') + .from("blogs") + .select("*") .range(offset, offset + limitNumber - 1); if (blogsError) { throw new Error(blogsError.message); } - const blogPromises = blogs.map(async (blog) => { const posterUrl = `${getPublicUrl(`/images/${blog.id}/poster`)}`; const blogFileUrl = `${getPublicUrl(`/blogs/${blog.id}/blog`)}`; - const { data: images, error: imagesError } = await supabase - .storage + const { data: images, error: imagesError } = await supabase.storage .from(process.env.BUCKET || "") .list(`images/${blog.id}`); @@ -45,14 +43,15 @@ export async function GET(request: NextRequest): Promise { throw new Error(imagesError.message); } - - const imageUrls = images.map(image => `${getPublicUrl(`/images/${blog.id}/${image.name}`)}`); + const imageUrls = images.map( + (image) => `${getPublicUrl(`/images/${blog.id}/${image.name}`)}`, + ); return { ...blog, posterUrl, blogFileUrl, - images: imageUrls + images: imageUrls, }; }); @@ -60,13 +59,13 @@ export async function GET(request: NextRequest): Promise { return NextResponse.json({ success: true, - message: 'Blogs retrieved successfully', + message: "Blogs retrieved successfully", blogs: blogsWithAssets, }); } catch (error: any) { return NextResponse.json( { success: false, message: error.message }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/app/api/v1/get/event/route.ts b/src/app/api/v1/get/event/route.ts index d610442..90a7b09 100644 --- a/src/app/api/v1/get/event/route.ts +++ b/src/app/api/v1/get/event/route.ts @@ -1,4 +1,3 @@ - import { NextRequest, NextResponse } from "next/server"; import { createClient } from "@/utils/supabase/server"; @@ -7,37 +6,37 @@ export async function GET(request: NextRequest): Promise { const supabase = createClient(); const url = new URL(request.url); - const id = url.searchParams.get('id'); + const id = url.searchParams.get("id"); if (!id) { return NextResponse.json( - { success: false, message: 'Event ID is required' }, - { status: 400 } + { success: false, message: "Event ID is required" }, + { status: 400 }, ); } const { data: event, error: eventError } = await supabase - .from('events') - .select('*') - .eq('id', id) + .from("events") + .select("*") + .eq("id", id) .single(); if (eventError) { return NextResponse.json( - { success: false, message: 'Event not found' }, - { status: 404 } + { success: false, message: "Event not found" }, + { status: 404 }, ); } return NextResponse.json({ success: true, - message: 'Event retrieved successfully', + message: "Event retrieved successfully", event: event, }); } catch (error: any) { return NextResponse.json( { success: false, message: error.message }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/app/api/v1/get/events/route.ts b/src/app/api/v1/get/events/route.ts index 897ce27..abcdcb9 100644 --- a/src/app/api/v1/get/events/route.ts +++ b/src/app/api/v1/get/events/route.ts @@ -19,9 +19,9 @@ export async function GET(request: NextRequest) { if (category.toLocaleLowerCase() === "upcoming") { query = query.gt("date", new Date().toISOString()); - }else if (category.toLocaleLowerCase() === "past") { + } else if (category.toLocaleLowerCase() === "past") { query = query.lt("date", new Date().toISOString()); - }else if (category.toLocaleLowerCase() === "ongoing") { + } else if (category.toLocaleLowerCase() === "ongoing") { query = query.eq("date", new Date().toISOString()); } diff --git a/src/app/api/v1/submit/blog/route.ts b/src/app/api/v1/submit/blog/route.ts index 9db4dde..2b896ae 100644 --- a/src/app/api/v1/submit/blog/route.ts +++ b/src/app/api/v1/submit/blog/route.ts @@ -3,17 +3,14 @@ import { createClient } from "@/utils/supabase/server"; import { Tables } from "@/types/supabase"; import sharp from "sharp"; - async function convertToAvif(inputFile: File): Promise { const arrayBuffer = await inputFile.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); - const avifBuffer = await sharp(buffer) - .avif() - .toBuffer(); + const avifBuffer = await sharp(buffer).avif().toBuffer(); - const blob = new Blob([avifBuffer], { type: 'image/avif' }); - const avifFile = new File([blob], inputFile.name, { type: 'image/avif' }); + const blob = new Blob([avifBuffer], { type: "image/avif" }); + const avifFile = new File([blob], inputFile.name, { type: "image/avif" }); return avifFile; } @@ -44,16 +41,16 @@ export async function POST(request: NextRequest): Promise { if (userError || !user) { return NextResponse.json( { success: false, message: "Unauthorized" }, - { status: 401 } + { status: 401 }, ); } // Use request.formData() to handle file uploads const formData = await request.formData(); - const blogData = JSON.parse(formData.get('blogData') as string); - - const posterFile = formData.get('poster') as File; - const blogFile = formData.get('blog') as File; + const blogData = JSON.parse(formData.get("blogData") as string); + + const posterFile = formData.get("poster") as File; + const blogFile = formData.get("blog") as File; let poster: File = posterFile; @@ -61,24 +58,27 @@ export async function POST(request: NextRequest): Promise { poster = await convertToAvif(posterFile); } - const images = formData.getAll('images') as File[]; + const images = formData.getAll("images") as File[]; const validation = validateRequestBody({ blogTable: blogData, poster, blog: blogFile, - images + images, }); if (!validation.valid) { return NextResponse.json( { success: false, message: validation.message }, - { status: 400 } + { status: 400 }, ); } blogData.writer = user.id; - const { data: userData, error: userDataError} = await supabase.from("users").select("username").eq("id", user.id); + const { data: userData, error: userDataError } = await supabase + .from("users") + .select("username") + .eq("id", user.id); if (userDataError || !userData || userData.length === 0) { throw new Error(userDataError?.message || "Failed to get user data"); @@ -115,21 +115,25 @@ export async function POST(request: NextRequest): Promise { console.log("Uploading image", image); uploadPromises.push( supabase.storage - .from(process.env.BUCKET || "") - .upload(`images/${blogId}/${image.name}`, await convertToAvif(image), { - upsert: true, - }) + .from(process.env.BUCKET || "") + .upload( + `images/${blogId}/${image.name}`, + await convertToAvif(image), + { + upsert: true, + }, + ), ); }); } - + const uploadResults = await Promise.all(uploadPromises); - + const [blogUploadResult, posterUploadResult, ...imageUploadResults] = uploadResults; if (blogUploadResult.error || posterUploadResult.error) { throw new Error( - blogUploadResult.error?.message || posterUploadResult.error?.message + blogUploadResult.error?.message || posterUploadResult.error?.message, ); } for (const result of imageUploadResults) { @@ -146,7 +150,7 @@ export async function POST(request: NextRequest): Promise { } catch (error: any) { return NextResponse.json( { success: false, message: error.message }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/app/auth/action.tsx b/src/app/auth/action.tsx index 34cba23..3e2d7e0 100644 --- a/src/app/auth/action.tsx +++ b/src/app/auth/action.tsx @@ -1,84 +1,82 @@ -'use server'; -import { cookies, headers } from 'next/headers'; +"use server"; +import { cookies, headers } from "next/headers"; import { RedirectType, redirect } from "next/navigation"; -import { createClient } from '@/utils/supabase/server'; +import { createClient } from "@/utils/supabase/server"; -export const Login = async ( - credentials : { - email: string , - password: string , - }) => { - const supabase = createClient(); - const { error } = await supabase.auth.signInWithPassword({ - email: credentials.email as string, - password: credentials.password as string, - }); - if (error) { - return { error: error.message }; - } - return { error: null }; -} +export const Login = async (credentials: { + email: string; + password: string; +}) => { + const supabase = createClient(); + const { error } = await supabase.auth.signInWithPassword({ + email: credentials.email as string, + password: credentials.password as string, + }); + if (error) { + return { error: error.message }; + } + return { error: null }; +}; -export const SignUp = async ( - credentials : { - username: string | null, - email: string , - password: string , - }) => { - const origin = headers().get("origin"); - const username = credentials.username; - const supabase = createClient(); +export const SignUp = async (credentials: { + username: string | null; + email: string; + password: string; +}) => { + const origin = headers().get("origin"); + const username = credentials.username; + const supabase = createClient(); - const { data: { user, session }, error, } = await supabase.auth.signUp({ - email: credentials.email as string, - password: credentials.password as string, - options: { - emailRedirectTo: `${origin}/auth/confirm`, - data: { username: username }, - }, - }); + const { + data: { user, session }, + error, + } = await supabase.auth.signUp({ + email: credentials.email as string, + password: credentials.password as string, + options: { + emailRedirectTo: `${origin}/auth/confirm`, + data: { username: username }, + }, + }); - if (error) { - console.log(error); - return { error: error.message }; - } - if (session || user?.role !== 'authenticated') { - return { error: 'Email already exists' }; - } - return { error: null }; + if (error) { + console.log(error); + return { error: error.message }; + } + if (session || user?.role !== "authenticated") { + return { error: "Email already exists" }; + } + return { error: null }; }; export const AuthSignIn = async () => { const origin = headers().get("origin"); - const gmail = cookies()?.get('email')?.value || ''; - + const gmail = cookies()?.get("email")?.value || ""; + const supabase = createClient(); const { data, error } = await supabase.auth.signInWithOAuth({ - provider: 'google', - options: { - redirectTo: `${origin}/auth/callback`, - queryParams: { - include_granted_scopes: 'true', - access_type: 'offline', - prompt: 'select_account', - login_hint: gmail, - }, + provider: "google", + options: { + redirectTo: `${origin}/auth/callback`, + queryParams: { + include_granted_scopes: "true", + access_type: "offline", + prompt: "select_account", + login_hint: gmail, }, + }, }); if (error) return { error: error.message, url: null }; if (data.url) return { error: null, url: data.url }; - return { error: 'Error signing in', url: null }; -} + return { error: "Error signing in", url: null }; +}; -export const checkEmailForOrganisation = ( - credentials : { - email: string , - }) => { - const cookiesStore = cookies(); - const organisation = new Set(['iiitv.ac.in', 'iiitvadodara.ac.in']); - cookiesStore.set('email', credentials.email as string); - if (organisation.has(credentials.email.split('@')[1])) { - redirect(`?auth=login&organisation=iiitv`); - } - redirect(`?auth=login`); -} \ No newline at end of file +export const checkEmailForOrganisation = (credentials: { email: string }) => { + const cookiesStore = cookies(); + const organisation = new Set(["iiitv.ac.in", "iiitvadodara.ac.in"]); + cookiesStore.set("email", credentials.email as string); + if (organisation.has(credentials.email.split("@")[1])) { + redirect(`?auth=login&organisation=iiitv`); + } + redirect(`?auth=login`); +}; diff --git a/src/app/auth/callback/route.ts b/src/app/auth/callback/route.ts index 4bb206f..68eb9b2 100644 --- a/src/app/auth/callback/route.ts +++ b/src/app/auth/callback/route.ts @@ -1,28 +1,27 @@ -import { createClient } from '@/utils/supabase/server'; -import { type NextRequest, NextResponse } from 'next/server'; +import { createClient } from "@/utils/supabase/server"; +import { type NextRequest, NextResponse } from "next/server"; export async function GET(req: NextRequest, res: NextResponse) { - const { searchParams } = new URL(req.url); - const code = searchParams.get('code'); + const { searchParams } = new URL(req.url); + const code = searchParams.get("code"); - const url = req.nextUrl.clone(); - url.pathname = '/form_create'; - url.searchParams.delete('code'); + const url = req.nextUrl.clone(); + url.pathname = "/form_create"; + url.searchParams.delete("code"); - - if( code ) { - try { - const supabase = createClient(); - const { error } = await supabase.auth.exchangeCodeForSession( code ); - if (error) { - url.searchParams.set('auth', 'login'); - url.searchParams.set('error', error.message); - return NextResponse.redirect(url); - } - } catch (error) { - console.log('x ',error); - } + if (code) { + try { + const supabase = createClient(); + const { error } = await supabase.auth.exchangeCodeForSession(code); + if (error) { + url.searchParams.set("auth", "login"); + url.searchParams.set("error", error.message); + return NextResponse.redirect(url); + } + } catch (error) { + console.log("x ", error); } - - return NextResponse.redirect(url); -} \ No newline at end of file + } + + return NextResponse.redirect(url); +} diff --git a/src/app/auth/component/component.tsx b/src/app/auth/component/component.tsx index 0f32c2d..5467056 100644 --- a/src/app/auth/component/component.tsx +++ b/src/app/auth/component/component.tsx @@ -2,208 +2,250 @@ import React, { FormEvent, use, useEffect, useState } from "react"; import Link from "next/link"; import axios from "axios"; -import { Label } from "@/components/ui/label" -import { Input } from "@/components/ui/input" -import { Button } from "@/components/ui/button" -import { Checkbox } from "@/components/ui/checkbox" -import Loader from '@/components/ui/loader' - -import { VscEye, VscEyeClosed } from "react-icons/vsc" -import styles from './styles.module.css' -import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import Loader from "@/components/ui/loader"; +import { VscEye, VscEyeClosed } from "react-icons/vsc"; +import styles from "./styles.module.css"; +import { cn } from "@/lib/utils"; interface Props { - auth: string | null - SignUp: (e: EventTarget & HTMLFormElement) => Promise - Login: (e: EventTarget & HTMLFormElement) => Promise - EmailSubmit: (e: React.FormEvent) => Promise - email: string | null - children?: React.ReactNode + auth: string | null; + SignUp: (e: EventTarget & HTMLFormElement) => Promise; + Login: (e: EventTarget & HTMLFormElement) => Promise; + EmailSubmit: (e: React.FormEvent) => Promise; + email: string | null; + children?: React.ReactNode; } -export function Component( props : Props) { - let { auth } = props - const [password, setPassword] = useState('') - const [revealPassword, setRevealPassword] = useState(false) +export function Component(props: Props) { + let { auth } = props; + const [password, setPassword] = useState(""); + const [revealPassword, setRevealPassword] = useState(false); const [loading, setLoading] = useState(false); let structure = { - inputfield : { - limit : 1, - values : [ - {name: 'email', label: 'Email Adress', type: 'email'} - ] + inputfield: { + limit: 1, + values: [{ name: "email", label: "Email Adress", type: "email" }], }, - button : { - text: 'continue', + button: { + text: "continue", onsubmit: async (e: FormEvent) => { setLoading(true); const bool = await props.EmailSubmit(e); if (!bool) setLoading(false); - } + }, }, - content : { - title: ['Welcome ', 'User'], - subtitle: 'Enter your email to continue ' - } - } - if (auth === 'login') { + content: { + title: ["Welcome ", "User"], + subtitle: "Enter your email to continue ", + }, + }; + if (auth === "login") { structure.inputfield = { - limit : 2, - values : [ - {name: 'email', label: 'Email address', type: 'email'}, - {name: 'password', label: 'Password', type: 'password'} - ] - } + limit: 2, + values: [ + { name: "email", label: "Email address", type: "email" }, + { name: "password", label: "Password", type: "password" }, + ], + }; structure.button = { - text: 'Login', + text: "Login", onsubmit: async (e) => { e.preventDefault(); const current = e.currentTarget; setLoading(true); const bool = await props.Login(current); if (!bool) setLoading(false); - } - } + }, + }; structure.content = { - title: ['Sign in to your ', 'account'], - subtitle: '' - } - } else if (auth === 'signup'){ + title: ["Sign in to your ", "account"], + subtitle: "", + }; + } else if (auth === "signup") { structure.inputfield = { - limit : 3, - values : [ - {name: 'email', label: 'Email address', type: 'email'}, - {name: 'username', label: 'Username', type: 'text'}, - {name: 'password', label: 'Password', type: 'password'} - ] - } + limit: 3, + values: [ + { name: "email", label: "Email address", type: "email" }, + { name: "username", label: "Username", type: "text" }, + { name: "password", label: "Password", type: "password" }, + ], + }; structure.button = { - text: 'Sign up', + text: "Sign up", onsubmit: async (e) => { e.preventDefault(); const current = e.currentTarget; const username = current.username.value; - if ( username.length < 3 ) { + if (username.length < 3) { const nextUserSibling = current.username.nextSibling as HTMLElement; - nextUserSibling.innerText = 'Username must be at least 3 characters long'; - return ; + nextUserSibling.innerText = + "Username must be at least 3 characters long"; + return; } setLoading(true); - const res = await axios.post('/api/rest/v1/isUsername', {username: username}); + const res = await axios.post("/api/rest/v1/isUsername", { + username: username, + }); if (res.data.state) { - const nextUserSibling = current.username.nextElementSibling as HTMLElement; - nextUserSibling.innerText = 'Username already exists'; + const nextUserSibling = current.username + .nextElementSibling as HTMLElement; + nextUserSibling.innerText = "Username already exists"; } else { - const nextUserSibling = current.username.nextElementSibling as HTMLElement; - nextUserSibling.innerText = ''; - if (validatePassword(password) && password.length>=8) { - const bool = await props.SignUp(current) + const nextUserSibling = current.username + .nextElementSibling as HTMLElement; + nextUserSibling.innerText = ""; + if (validatePassword(password) && password.length >= 8) { + const bool = await props.SignUp(current); if (!bool) setLoading(false); - const nextSibling = current.password.nextElementSibling as HTMLElement; - nextSibling.innerText = ''; + const nextSibling = current.password + .nextElementSibling as HTMLElement; + nextSibling.innerText = ""; } else { - const nextSibling = current.password.nextElementSibling as HTMLElement; + const nextSibling = current.password + .nextElementSibling as HTMLElement; if (!validatePassword(password, 1)) { - nextSibling.innerText = 'Password must be at least 8 characters long'; + nextSibling.innerText = + "Password must be at least 8 characters long"; } else if (!validatePassword(password, 2)) { - nextSibling.innerText = 'Password must contain at least one uppercase, one lowercase and one digit'; + nextSibling.innerText = + "Password must contain at least one uppercase, one lowercase and one digit"; } else if (!validatePassword(password, 3)) { - nextSibling.innerText = 'Password must contain at least one special character'; + nextSibling.innerText = + "Password must contain at least one special character"; } } } setLoading(false); - } - } + }, + }; structure.content = { - title: ['Create an ' ,'account'], - subtitle: '' - } + title: ["Create an ", "account"], + subtitle: "", + }; } - useEffect(() => { if (props.email) { - const emailElement = document.getElementById('email') as HTMLInputElement; - if (emailElement) emailElement.value = props.email || ''; + const emailElement = document.getElementById("email") as HTMLInputElement; + if (emailElement) emailElement.value = props.email || ""; } - }, [props.email]) + }, [props.email]); return (

- {structure.content.title[0]}{structure.content.title[1]} + {structure.content.title[0]} + {structure.content.title[1]}

- { (structure.content.subtitle !== '') &&

{structure.content.subtitle}

} + {structure.content.subtitle !== "" && ( +

+ {structure.content.subtitle} +

+ )}
- {structure.inputfield.values.map((input) => ( -
- - { - if (input.name === 'password') setPassword(e.target.value) + {structure.inputfield.values.map((input) => ( +
+ + { + if (input.name === "password") setPassword(e.target.value); }} - id={input.name} - name={input.name} - type={input.type === 'password' && revealPassword ? 'text' : input.type} - autoComplete={input.name === 'password' ? 'current-password' : 'email'} - required - placeholder={input.label} - disabled={(props.email && input.name === 'email' && auth)?true:false} - className={cn("rounded-[8px] border border-input bg-background px-4 py-6 text-foreground placeholder-muted-foreground focus:z-10 focus:border-primary focus:outline-none sm:text-sm", - input.name === 'password' && 'pr-12')} - /> - - setRevealPassword(!revealPassword)}>{revealPassword?:} -
- ))} - - {auth === 'login' && -
-
- - + + setRevealPassword(!revealPassword)} + > + {revealPassword ? ( + + ) : ( + + )} +
-
- - Forgot your password? - + ))} + + {auth === "login" && ( +
+
+ + +
+
+ + Forgot your password? + +
-
} + )}
- {props.children} - + {props.children}
- ) + ); } function validateEmail(email: string): boolean { @@ -212,7 +254,8 @@ function validateEmail(email: string): boolean { } function validatePassword(password: string, customCheck: number = 0): boolean { - const re = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$/; + const re = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$/; const special = /[!@#$%^&*()_+]/; const upper_lower_digit = /^(?=.*[a-z])(?=.*[A-Z])(?=.x\d)[A-Za-z\d]{8,}$/; if (!customCheck) return re.test(password); diff --git a/src/app/auth/component/oauth.tsx b/src/app/auth/component/oauth.tsx index faba5ee..8f187a0 100644 --- a/src/app/auth/component/oauth.tsx +++ b/src/app/auth/component/oauth.tsx @@ -1,16 +1,16 @@ import React, { useState } from "react"; -import { Button } from "@/components/ui/button" -import Loader from '@/components/ui/loader' +import { Button } from "@/components/ui/button"; +import Loader from "@/components/ui/loader"; // -- icons -- import { FcGoogle } from "react-icons/fc"; -import { JSX, SVGProps } from "react" +import { JSX, SVGProps } from "react"; -interface Props{ - onClick: () => Promise +interface Props { + onClick: () => Promise; } -export function OAuthComponent(props : Props) { +export function OAuthComponent(props: Props) { const [loading, setLoading] = useState(false); return ( @@ -20,24 +20,34 @@ export function OAuthComponent(props : Props) {
- Or continue with + + Or continue with +
- ) + ); } function ChromeIcon(props: JSX.IntrinsicAttributes & SVGProps) { @@ -60,5 +70,5 @@ function ChromeIcon(props: JSX.IntrinsicAttributes & SVGProps) { - ) -} \ No newline at end of file + ); +} diff --git a/src/app/auth/confirm/route.ts b/src/app/auth/confirm/route.ts index b654759..413a47c 100644 --- a/src/app/auth/confirm/route.ts +++ b/src/app/auth/confirm/route.ts @@ -1,33 +1,33 @@ -import { type EmailOtpType } from '@supabase/supabase-js' -import { type NextRequest, NextResponse } from 'next/server' +import { type EmailOtpType } from "@supabase/supabase-js"; +import { type NextRequest, NextResponse } from "next/server"; -import { createClient } from '@/utils/supabase/server' +import { createClient } from "@/utils/supabase/server"; export async function GET(req: NextRequest) { - const { searchParams } = new URL(req.url) - const token_hash = searchParams.get('token_hash') - const type = searchParams.get('type') as EmailOtpType | null - const next = searchParams.get('next') ?? '/form_create' - - const redirectTo = req.nextUrl.clone() - redirectTo.pathname = next - redirectTo.searchParams.delete('token_hash') - redirectTo.searchParams.delete('type') + const { searchParams } = new URL(req.url); + const token_hash = searchParams.get("token_hash"); + const type = searchParams.get("type") as EmailOtpType | null; + const next = searchParams.get("next") ?? "/form_create"; - if (token_hash ) { - const supabase = createClient() - const { error, data } = await supabase.auth.verifyOtp({ - type: type || 'email', - token_hash: token_hash , - }) - if (!error) { - redirectTo.searchParams.delete('next') - return NextResponse.redirect(redirectTo) - } + const redirectTo = req.nextUrl.clone(); + redirectTo.pathname = next; + redirectTo.searchParams.delete("token_hash"); + redirectTo.searchParams.delete("type"); + + if (token_hash) { + const supabase = createClient(); + const { error, data } = await supabase.auth.verifyOtp({ + type: type || "email", + token_hash: token_hash, + }); + if (!error) { + redirectTo.searchParams.delete("next"); + return NextResponse.redirect(redirectTo); } + } - redirectTo.pathname = '/auth' - redirectTo.searchParams.set('auth', 'signup'); - redirectTo.searchParams.set('error', 'Invalid token hash'); - return NextResponse.redirect(redirectTo) -} \ No newline at end of file + redirectTo.pathname = "/auth"; + redirectTo.searchParams.set("auth", "signup"); + redirectTo.searchParams.set("error", "Invalid token hash"); + return NextResponse.redirect(redirectTo); +} diff --git a/src/app/auth/confirm_email/page.tsx b/src/app/auth/confirm_email/page.tsx index 446091e..2cca8b7 100644 --- a/src/app/auth/confirm_email/page.tsx +++ b/src/app/auth/confirm_email/page.tsx @@ -1,13 +1,13 @@ -'use client'; -import React, { useState, useEffect, useCallback } from 'react'; -import Link from 'next/link'; +"use client"; +import React, { useState, useEffect, useCallback } from "react"; +import Link from "next/link"; -import { useCookies } from 'next-client-cookies'; -import { supabase } from '@/utils/supabase/client'; -import { Button } from '@/components/ui/button'; -import ErrorDialog from '@/components/error_dialog'; +import { useCookies } from "next-client-cookies"; +import { supabase } from "@/utils/supabase/client"; +import { Button } from "@/components/ui/button"; +import ErrorDialog from "@/components/error_dialog"; -import Image from 'next/image'; +import Image from "next/image"; export default function Confirm() { const [loading, setLoading] = useState(false); @@ -15,7 +15,7 @@ export default function Confirm() { const [countdown, setCountdown] = useState(10); useEffect(() => { - supabase.auth.getUser().then(({ data , error }) => { + supabase.auth.getUser().then(({ data, error }) => { if (error) { console.log(error.message); return; @@ -25,33 +25,41 @@ export default function Confirm() { }, []); const cookies = useCookies(); - const user_email = cookies.get('email') || null; + const user_email = cookies.get("email") || null; - if (!user_email) return ( - - ) + if (!user_email) + return ( + + ); - if(!validateEmail(user_email)) { + if (!validateEmail(user_email)) { return (

- Invalid email address: + Invalid email address:

- {user_email} + + {user_email} +

); } useEffect(() => { - const timer = setTimeout(() => { - setError('The email confirmation link has expired. Please sign up again to receive a new confirmation email.'); - }, 1000 * 60 * 60 ); + const timer = setTimeout( + () => { + setError( + "The email confirmation link has expired. Please sign up again to receive a new confirmation email.", + ); + }, + 1000 * 60 * 60, + ); return () => clearTimeout(timer); }, []); @@ -69,71 +77,85 @@ export default function Confirm() { const resent = async () => { const origin = window.location.origin; - setLoading(true) + setLoading(true); const { error } = await supabase.auth.resend({ - type: 'signup', + type: "signup", email: user_email, options: { emailRedirectTo: `${origin}/auth/confirm`, - } - }) + }, + }); if (error) { - setError(error.message) + setError(error.message); } else { - setError(null) + setError(null); } - } + }; return (
-

Email Sent!

+

+ Email Sent! +

-

- We've sent a confirmation email to {user_email}. Click the link in - the email to verify your account. -

-
- - - Return to Login - -
-
- {loading &&

Resending email in {countdown} seconds...

} -
-
- {error &&

{error}

} +

+ We've sent a confirmation email to{" "} + {user_email}. Click the link + in the email to verify your account. +

+
+ + + Return to Login + +
+
+ {loading && ( +

+ Resending email in {countdown} seconds... +

+ )} +
+
+ {error &&

{error}

} +
-
- Confirm Email + Confirm Email
); } - const validateEmail = (email: string) => { const re = /\S+@\S+\.\S+/; return re.test(email); -} +}; -function CircleCheckIcon(props: React.JSX.IntrinsicAttributes & React.SVGProps) { +function CircleCheckIcon( + props: React.JSX.IntrinsicAttributes & React.SVGProps, +) { return ( - ) + ); } - -function XIcon(props: React.JSX.IntrinsicAttributes & React.SVGProps) { +function XIcon( + props: React.JSX.IntrinsicAttributes & React.SVGProps, +) { return ( - ) -} \ No newline at end of file + ); +} diff --git a/src/app/auth/layout.tsx b/src/app/auth/layout.tsx index d53be27..9be563e 100644 --- a/src/app/auth/layout.tsx +++ b/src/app/auth/layout.tsx @@ -1,9 +1,9 @@ -import { CookiesProvider } from 'next-client-cookies/server'; +import { CookiesProvider } from "next-client-cookies/server"; interface RootLayoutProps { children: React.ReactNode; } -export default function RootLayout({ children } : RootLayoutProps) { +export default function RootLayout({ children }: RootLayoutProps) { return {children}; -} \ No newline at end of file +} diff --git a/src/app/auth/page.tsx b/src/app/auth/page.tsx index c4b24df..dfcf17e 100644 --- a/src/app/auth/page.tsx +++ b/src/app/auth/page.tsx @@ -1,115 +1,137 @@ -'use client'; -import React , { useState, Suspense, useEffect } from 'react'; -import Link from 'next/link'; -import { useRouter, useSearchParams} from 'next/navigation'; -import { useCookies } from 'next-client-cookies'; +"use client"; +import React, { useState, Suspense, useEffect } from "react"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useCookies } from "next-client-cookies"; -import { Login, SignUp, AuthSignIn, checkEmailForOrganisation } from './action'; +import { Login, SignUp, AuthSignIn, checkEmailForOrganisation } from "./action"; -import { Component } from './component/component'; -import { OAuthComponent } from './component/oauth'; +import { Component } from "./component/component"; +import { OAuthComponent } from "./component/oauth"; -import Alert from '@/components/ui/alert'; -import Loading from '@/components/loading'; +import Alert from "@/components/ui/alert"; +import Loading from "@/components/loading"; const Path = (path: string, org: string | null) => { - if (org) return `/auth?auth=${path}&organisation=${org}`; - return `/auth?auth=${path}`; -} - -export default function Auth(){ - const router = useRouter(); - - const [error, setError] = useState(null); - - const searchParams = useSearchParams(); - const auth = searchParams.get('auth'); - const organisation = searchParams.get('organisation'); - const oauthhidden = organisation==='iiitv'?true:false; - const err = searchParams.get('error'); - - useEffect(() => { - if (err) setError(err); - }, [err]); - - const cookies = useCookies(); - const email = cookies.get('email') || null; - if(!email && auth) router.push('/auth'); - - const emailSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - const email = e.currentTarget.email.value; - checkEmailForOrganisation({email}); - } - - async function login(current: EventTarget & HTMLFormElement) { - const email = current.email.value; - const password = current.password.value; - setError(null); - const { error } = await Login({email, password}) - if (error) { - setError(error); - return false; - } else { - router.push('/form_create'); - } + if (org) return `/auth?auth=${path}&organisation=${org}`; + return `/auth?auth=${path}`; +}; + +export default function Auth() { + const router = useRouter(); + + const [error, setError] = useState(null); + + const searchParams = useSearchParams(); + const auth = searchParams.get("auth"); + const organisation = searchParams.get("organisation"); + const oauthhidden = organisation === "iiitv" ? true : false; + const err = searchParams.get("error"); + + useEffect(() => { + if (err) setError(err); + }, [err]); + + const cookies = useCookies(); + const email = cookies.get("email") || null; + if (!email && auth) router.push("/auth"); + + const emailSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const email = e.currentTarget.email.value; + checkEmailForOrganisation({ email }); + }; + + async function login(current: EventTarget & HTMLFormElement) { + const email = current.email.value; + const password = current.password.value; + setError(null); + const { error } = await Login({ email, password }); + if (error) { + setError(error); + return false; + } else { + router.push("/form_create"); } - - const signup = async (cur: EventTarget & HTMLFormElement) => { - const email = cur.email.value; - const username = cur.username.value; - const password = cur.password.value; - - setError(null); - const { error } = await SignUp({username, email, password}) - console.log(error); - if (error) { - setError(error); - return false; - } else { - router.push(`/auth/confirm_email?type=signup`); - } + } + + const signup = async (cur: EventTarget & HTMLFormElement) => { + const email = cur.email.value; + const username = cur.username.value; + const password = cur.password.value; + + setError(null); + const { error } = await SignUp({ username, email, password }); + console.log(error); + if (error) { + setError(error); + return false; + } else { + router.push(`/auth/confirm_email?type=signup`); } - - const Authsignin = async () => { - setError(null); - const { error, url } = await AuthSignIn(); - if (error) { - setError(error); - } else if (url) { - router.push(url); - } + }; + + const Authsignin = async () => { + setError(null); + const { error, url } = await AuthSignIn(); + if (error) { + setError(error); + } else if (url) { + router.push(url); } - - return ( - }> - {error && - } - - - { auth && (
-
- {auth === 'signup' ? -

Have an account? Login

: -

Don't have an account? Create here

} -
-
)} - - {oauthhidden && } -
- - Back to Home - + }; + + return ( + }> + {error && } + + {auth && ( +
+
+ {auth === "signup" ? ( +

+ Have an account?{" "} + + Login + +

+ ) : ( +

+ Don't have an account? Create{" "} + + here + +

+ )} +
-
-
- ) + )} + + {oauthhidden && } +
+ + Back to Home + +
+ + + ); } diff --git a/src/app/auth/reset_password/page.tsx b/src/app/auth/reset_password/page.tsx index 9ad9da1..f6f1e6f 100644 --- a/src/app/auth/reset_password/page.tsx +++ b/src/app/auth/reset_password/page.tsx @@ -1,85 +1,105 @@ -'use client'; +"use client"; -import React, { useState } from 'react'; -import { supabase } from '@/utils/supabase/client'; -import { useCookies } from 'next-client-cookies'; -import Link from 'next/link'; - -import { Card, CardContent, CardFooter } from "@/components/ui/card" -import { Label } from "@/components/ui/label" -import { Input } from "@/components/ui/input" -import { Button } from "@/components/ui/button" -import ErrorDialog from '@/components/error_dialog' -import Loader from '@/components/ui/loader' +import React, { useState } from "react"; +import { supabase } from "@/utils/supabase/client"; +import { useCookies } from "next-client-cookies"; +import Link from "next/link"; +import { Card, CardContent, CardFooter } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import ErrorDialog from "@/components/error_dialog"; +import Loader from "@/components/ui/loader"; const ResetPassword = () => { const cookies = useCookies(); - const loginpath = cookies.get('email') != ''?'/auth?auth=login':'/auth'; + const loginpath = cookies.get("email") != "" ? "/auth?auth=login" : "/auth"; - const [email, setEmail] = useState(cookies.get('email') || ''); + const [email, setEmail] = useState(cookies.get("email") || ""); const [errorMsg, setErrorMsg] = useState(null); const [successMsg, setSuccessMsg] = useState(null); const [loading, setLoading] = useState(false); - - async function resetPassword(formData: { email: string; }) { + async function resetPassword(formData: { email: string }) { setLoading(true); - const { error } = await supabase.auth.resetPasswordForEmail(formData.email, { - redirectTo: `${window.location.origin}/api/update-password`, - }); + const { error } = await supabase.auth.resetPasswordForEmail( + formData.email, + { + redirectTo: `${window.location.origin}/api/update-password`, + }, + ); setLoading(false); if (error) { setErrorMsg(error.message); } else { - setSuccessMsg('Password reset instructions sent.'); + setSuccessMsg("Password reset instructions sent."); } - } return ( -
-
-
-

Forgot Password

-

Enter your email below to reset your password.

+
+
+
+

+ Forgot Password +

+

+ Enter your email below to reset your password. +

+
+ +
{ + e.preventDefault(); + resetPassword({ email }); + }} + > + +
+ + { + setEmail(e.target.value); + }} + id="email" + type="email" + name="email" + value={email} + placeholder="m@example.com" + required + /> +
+
+ +
+
+ + + Return to Login + + +
+
+

{successMsg}

- -
{ - e.preventDefault(); - resetPassword({ email }); - }}> - -
- - { - setEmail(e.target.value); - }} id="email" type="email" name="email" value={email} placeholder="m@example.com" required /> -
-
- -
-
- - - Return to Login - - -
-
-

{successMsg}

+ + {errorMsg && }
- - {errorMsg && } -
); }; -export default ResetPassword; \ No newline at end of file +export default ResetPassword; diff --git a/src/app/auth/update_password/page.tsx b/src/app/auth/update_password/page.tsx index 6c1073e..e443363 100644 --- a/src/app/auth/update_password/page.tsx +++ b/src/app/auth/update_password/page.tsx @@ -1,57 +1,70 @@ -'use client'; +"use client"; -import React, { useState } from "react" -import { supabase } from "@/utils/supabase/client" -import { useRouter } from "next/navigation" +import React, { useState } from "react"; +import { supabase } from "@/utils/supabase/client"; +import { useRouter } from "next/navigation"; -import { Label } from "@/components/ui/label" -import { Input } from "@/components/ui/input" -import { Button } from "@/components/ui/button" -import Loader from "@/components/ui/loader" -import ErrorDialog from "@/components/error_dialog" -import Link from "next/link" +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import Loader from "@/components/ui/loader"; +import ErrorDialog from "@/components/error_dialog"; +import Link from "next/link"; export default function Update_password() { - const router = useRouter() - const [errorMsg, setErrorMsg] = useState(null) - const [loading, setLoading] = useState(false) + const router = useRouter(); + const [errorMsg, setErrorMsg] = useState(null); + const [loading, setLoading] = useState(false); async function updatePassword(e: React.FormEvent) { - e.preventDefault() - setLoading(true) - if( e.currentTarget.password.value !== e.currentTarget.confirmPassword.value ) { - setErrorMsg("Passwords do not match") - setLoading(false) - return + e.preventDefault(); + setLoading(true); + if ( + e.currentTarget.password.value !== e.currentTarget.confirmPassword.value + ) { + setErrorMsg("Passwords do not match"); + setLoading(false); + return; } const { error } = await supabase.auth.updateUser({ password: e.currentTarget.password.value, }); - setLoading(false) + setLoading(false); if (error) { - setErrorMsg(error.message) + setErrorMsg(error.message); } else { - router.replace("/form_create") + router.replace("/form_create"); } } - return (

Update Password

-

Enter a new password to update your account.

+

+ Enter a new password to update your account. +

- +
- +
- + Back to Reset Password
{errorMsg && }
- ) -} \ No newline at end of file + ); +} diff --git a/src/app/blogs/components/blogCard.jsx b/src/app/blogs/components/blogCard.jsx index ee81bb0..b6e90be 100644 --- a/src/app/blogs/components/blogCard.jsx +++ b/src/app/blogs/components/blogCard.jsx @@ -2,166 +2,191 @@ import React, { useState } from "react"; import Image from "next/image"; import Link from "next/link"; -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; import { Montserrat } from "next/font/google"; -const montserratFont = Montserrat({ weight: ["100", "200", "400", "500", "600"], subsets: ["latin"] }); +const montserratFont = Montserrat({ + weight: ["100", "200", "400", "500", "600"], + subsets: ["latin"], +}); export default function BlogCard(props) { - const [blog, setBlog] = useState(props.blog); - const [blogPosterUrl, setBlogPosterUrl] = useState(blog.posterUrl); - const [blogTitle, setBlogTitle] = useState(blog.title); - const [blogIntro, setBlogIntro] = useState(blog.intro); - const [creatorName, setCreatorName] = useState("Devesh Sawant") - const [createdAt, setCreatedAt] = useState(blog.created_at); - const [blogPoster, setblogPoster] = useState("/event_poster.avif"); - const [blogLikes, setBlogLikes] = useState("10"); - const [blogComments, setBlogComments] = useState("10"); - - console.log(trimString(blogTitle, 45)); - - return ( -
- -
- Blog Poster -
- - -
-
-

{formatDate(new Date(createdAt))}

- -
+ const [blog, setBlog] = useState(props.blog); + const [blogPosterUrl, setBlogPosterUrl] = useState(blog.posterUrl); + const [blogTitle, setBlogTitle] = useState(blog.title); + const [blogIntro, setBlogIntro] = useState(blog.intro); + const [creatorName, setCreatorName] = useState("Devesh Sawant"); + const [createdAt, setCreatedAt] = useState(blog.created_at); + const [blogPoster, setblogPoster] = useState("/event_poster.avif"); + const [blogLikes, setBlogLikes] = useState("10"); + const [blogComments, setBlogComments] = useState("10"); + + console.log(trimString(blogTitle, 45)); + + return ( +
+ +
+ Blog Poster +
+ + +
+
+

+ {formatDate(new Date(createdAt))} +

+ +
-
- -
-
-

{trimString(blogTitle, 45)}

-
-

{trimString(blogIntro, 200)}

- -
-
-
-
- - - -

{creatorName}

-
-
-
- -

{blogLikes}

-
-
- -

{blogComments}

-
-
-
-
+
+ +
+
+

+ {trimString(blogTitle, 45)} +

+
+

+ {trimString(blogIntro, 200)} +

+ +
+
+
+
+ + + +

{creatorName}

+
+
+
+ +

{blogLikes}

- +
+ +

{blogComments}

+
+
+
- ) +
+
+ ); } - function trimString(str, length) { - // Use Intl.Segmenter to handle grapheme clusters (e.g., emojis) - const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" }); - const segments = segmenter.segment(str); + // Use Intl.Segmenter to handle grapheme clusters (e.g., emojis) + const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" }); + const segments = segmenter.segment(str); - // Convert segments iterator to an array of strings - const graphemes = Array.from(segments, segment => segment.segment); + // Convert segments iterator to an array of strings + const graphemes = Array.from(segments, (segment) => segment.segment); - // Check if truncation is needed - if (graphemes.length > length) { - return graphemes.slice(0, length).join('') + "..."; - } + // Check if truncation is needed + if (graphemes.length > length) { + return graphemes.slice(0, length).join("") + "..."; + } - // Return the original string if no truncation is needed - return str; + // Return the original string if no truncation is needed + return str; } - function formatDate(date) { - // Array of month names - const monthNames = [ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sept", "Oct", "Nov", "Dec" - ]; - - // Extract day, month, and year from the date - const day = date.getDate(); - const month = monthNames[date.getMonth()]; - const year = date.getFullYear(); - - // Format the date - return `${day} ${month}, ${year}`; + // Array of month names + const monthNames = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sept", + "Oct", + "Nov", + "Dec", + ]; + + // Extract day, month, and year from the date + const day = date.getDate(); + const month = monthNames[date.getMonth()]; + const year = date.getFullYear(); + + // Format the date + return `${day} ${month}, ${year}`; } - - const ShareButton = (props) => { - const handleShare = async () => { - if (navigator.share) { - try { - await navigator.share({ - title: 'Check this out!', - text: 'This is an awesome React app.', - url: window.location.origin + props.href - }); - - } catch (error) { - console.error('Error sharing:', error); - } - } else { - console.log('Web Share API not supported'); - // Optionally handle fallback here - } - }; - - return ( - - ); + const handleShare = async () => { + if (navigator.share) { + try { + await navigator.share({ + title: "Check this out!", + text: "This is an awesome React app.", + url: window.location.origin + props.href, + }); + } catch (error) { + console.error("Error sharing:", error); + } + } else { + console.log("Web Share API not supported"); + // Optionally handle fallback here + } + }; + + return ( + + ); }; const ShareIcon = () => ( - - - + + + ); - const CommentIcon = () => ( - - - + + + ); - const LikeIcon = () => ( - - - + + + ); diff --git a/src/app/blogs/components/blogCoverPoster.jsx b/src/app/blogs/components/blogCoverPoster.jsx index 67c9bb3..ad2c5d2 100644 --- a/src/app/blogs/components/blogCoverPoster.jsx +++ b/src/app/blogs/components/blogCoverPoster.jsx @@ -1,14 +1,16 @@ - -import Image from 'next/image'; +import Image from "next/image"; export default function Blogs() { - const blogCoverImage = "/blog_cover_poster.png"; - return( -
- Blog cover poster -
- ) -} \ No newline at end of file + const blogCoverImage = "/blog_cover_poster.png"; + return ( +
+ Blog cover poster +
+ ); +} diff --git a/src/app/blogs/components/generateBlogCards.jsx b/src/app/blogs/components/generateBlogCards.jsx index d9203e3..0a88eda 100644 --- a/src/app/blogs/components/generateBlogCards.jsx +++ b/src/app/blogs/components/generateBlogCards.jsx @@ -1,23 +1,28 @@ import React, { useEffect } from "react"; import BlogCard from "./blogCard"; -import "@/styles/blogs.css" +import "@/styles/blogs.css"; -import { Montserrat,Alata} from "next/font/google"; -const alataFont = Alata({weight: ["400"], subsets: ["latin"]}); -const montserratFont = Montserrat({weight: ["100","200","400","600"], subsets: ["latin"]}); +import { Montserrat, Alata } from "next/font/google"; +const alataFont = Alata({ weight: ["400"], subsets: ["latin"] }); +const montserratFont = Montserrat({ + weight: ["100", "200", "400", "600"], + subsets: ["latin"], +}); -export default function GenerateBlogCards(props){ - const blogs = props.blogs; +export default function GenerateBlogCards(props) { + const blogs = props.blogs; - return( -
-
-

Blogs

-
- {blogs.map((blog)=>)} -
-
+ return ( +
+
+

Blogs

+
+ {blogs.map((blog) => ( + + ))}
- ) -} \ No newline at end of file +
+
+ ); +} diff --git a/src/app/blogs/fetchBlogs.jsx b/src/app/blogs/fetchBlogs.jsx index c501145..c286ab7 100644 --- a/src/app/blogs/fetchBlogs.jsx +++ b/src/app/blogs/fetchBlogs.jsx @@ -1,29 +1,31 @@ -import { useState, useEffect } from 'react'; -import axios from 'axios'; +import { useState, useEffect } from "react"; +import axios from "axios"; const useFetchBlog = (currentPage) => { - const [blogs, setBlogs] = useState({}); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const [blogs, setBlogs] = useState({}); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - useEffect(() => { - setLoading(true); - const fetchBlog = async () => { - try { - const response = await axios.get(`/api/v1/get/blogs?page=${currentPage}`); - const result = response.data; - setBlogs(result.blogs); - setLoading(false); - } catch (error) { - setError(error); - setLoading(false); - } - }; + useEffect(() => { + setLoading(true); + const fetchBlog = async () => { + try { + const response = await axios.get( + `/api/v1/get/blogs?page=${currentPage}`, + ); + const result = response.data; + setBlogs(result.blogs); + setLoading(false); + } catch (error) { + setError(error); + setLoading(false); + } + }; - fetchBlog(); - },[currentPage]); - - return { blogs, loading, error }; + fetchBlog(); + }, [currentPage]); + + return { blogs, loading, error }; }; -export default useFetchBlog; \ No newline at end of file +export default useFetchBlog; diff --git a/src/app/blogs/layout.tsx b/src/app/blogs/layout.tsx index 1bcbaa6..34d3e5f 100644 --- a/src/app/blogs/layout.tsx +++ b/src/app/blogs/layout.tsx @@ -1,18 +1,17 @@ -import React from 'react'; -import Navbar from "@/components/navbar" -import Footer from "@/components/footer" - +import React from "react"; +import Navbar from "@/components/navbar"; +import Footer from "@/components/footer"; interface RootLayoutProps { children: React.ReactNode; } -export default function RootLayout({ children } : RootLayoutProps) { +export default function RootLayout({ children }: RootLayoutProps) { return ( <> {children}