diff --git a/src/app/addProject/page.tsx b/src/app/addProject/page.tsx index 7d87cd4..d3f7545 100644 --- a/src/app/addProject/page.tsx +++ b/src/app/addProject/page.tsx @@ -11,7 +11,7 @@ const AddProjectPage = async () => { loggedInProtectedPage( session as { user: { email: string; id: string; randomKey: string }; - } | null + } | null, ); const interests = await prisma.interest.findMany(); const participants = await prisma.user.findMany(); diff --git a/src/app/auth/signin/page.tsx b/src/app/auth/signin/page.tsx index ac1c405..38d38e1 100644 --- a/src/app/auth/signin/page.tsx +++ b/src/app/auth/signin/page.tsx @@ -14,7 +14,7 @@ const SignIn = () => { const email = target.email.value; const password = target.password.value; const result = await signIn('credentials', { - callbackUrl: '/list', + callbackUrl: '/home', email, password, }); diff --git a/src/app/home/HomePage.tsx b/src/app/home/HomePage.tsx index caaa2ff..d7f1619 100644 --- a/src/app/home/HomePage.tsx +++ b/src/app/home/HomePage.tsx @@ -1,16 +1,37 @@ +/* eslint-disable react/jsx-one-expression-per-line */ + 'use client'; import React from 'react'; import { Container, Col } from 'react-bootstrap'; -import { PageIDs } from '../../utilities/ids'; -import pageStyle from '../../utilities/pageStyle'; +import { PageIDs } from '@/utilities/ids'; +import pageStyle from '@/utilities/pageStyle'; +import { Profile, Interest, Project } from '@prisma/client'; +import ProfileForm from '@/components/ProfileForm'; -const HomePage = () => ( - +const HomePage = ({ + profile, + interests, + projects, + profileInterests, + profileProjects, +}: { + profile: Profile; + interests: Interest[]; + projects: Project[]; + profileInterests: Interest[]; + profileProjects: Project[]; +}) => ( + - -

Your Profile

- +

Your Profile

+
); diff --git a/src/app/home/page.tsx b/src/app/home/page.tsx index f326a33..329d8be 100644 --- a/src/app/home/page.tsx +++ b/src/app/home/page.tsx @@ -1,6 +1,7 @@ /* eslint-disable import/extensions */ import React from 'react'; import { getServerSession } from 'next-auth'; +import { Profile, Interest, Project } from '@prisma/client'; import { prisma } from '@/lib/prisma'; import { loggedInProtectedPage } from '@/lib/page-protection'; import { authOptions } from '../api/auth/[...nextauth]/route'; @@ -25,27 +26,26 @@ const HomePageHelper = async () => { const profileInterests = await prisma.profileInterest.findMany({ where: { profileId: profile!.id }, }); - const profileInterestNames = profileInterests.map((profileInterest) => { + const proInterests: Interest[] = profileInterests.map((profileInterest) => { const i = interests.find((interest) => interest.id === profileInterest.interestId); - return i ? i.name : ''; + return i as Interest; }); - console.log(profileInterestNames); const profileProjects = await prisma.profileProject.findMany({ where: { profileId: profile!.id }, }); - const profileProjectNames = profileProjects.map((profileProject) => { + const proProjects: Project[] = profileProjects.map((profileProject) => { const p = projects.find((project) => project.id === profileProject.projectId); - return p ? p.name : ''; + return p as Project; }); - console.log( - // interests, - // projects, - // profileInterests, - // profileProjects, - profileInterestNames, - profileProjectNames, + return ( + ); - return ; }; export default HomePageHelper; diff --git a/src/components/ProfileForm.tsx b/src/components/ProfileForm.tsx new file mode 100644 index 0000000..a0c00ba --- /dev/null +++ b/src/components/ProfileForm.tsx @@ -0,0 +1,158 @@ +/* eslint-disable import/extensions */ + +'use client'; + +import React from 'react'; +import { Form, Button, Col, Container, Card, Row } from 'react-bootstrap'; +import { useForm, Controller } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import swal from 'sweetalert'; +import Multiselect from 'multiselect-react-dropdown'; +import { IProfile, ProfileSchema } from '@/lib/validationSchemas'; +import { Interest, Profile, Project } from '@prisma/client'; + +const ProfileForm = ({ + profile, + interests, + projects, + profileInterests, + profileProjects, +}: { + profile: Profile; + interests: Interest[]; + projects: Project[]; + profileInterests: Interest[]; + profileProjects: Project[]; +}) => { + const formPadding = 'py-1'; + const interestNames = interests.map((interest) => interest.name); + const profileInterestNames = profileInterests.map((interest) => interest.name); + const projectNames = projects.map((project) => project.name); + const profileProjectNames = profileProjects.map((project) => project.name); + const { + register, + handleSubmit, + control, + reset, + formState: { errors }, + } = useForm({ + resolver: yupResolver(ProfileSchema), + }); + + const onSubmit = async (data: IProfile) => { + console.log(data); + // const result = await upsertProject(data); + // if (result) { + // swal('Success!', 'Project data saved successfully!', 'success'); + // reset(); + // } else { + // swal('Error!', 'Failed to save project data!', 'error'); + // } + }; + return ( + + + +
+ + + + First Name + + {errors.firstName?.message} + + + + + Last Name + + {errors.lastName?.message} + + + + + Email + + {errors.email?.message} + + + + + + + Biographical statement + + (optional) + {errors.bio?.message} + + + + + + + Interests + ( + + )} + /> + + + + + Projects + ( + + )} + /> + + + + + + + + + + + +
+
+
+
+ ); +}; + +export default ProfileForm; diff --git a/src/lib/ProfileCardData.ts b/src/lib/ProfileCardData.ts new file mode 100644 index 0000000..e366b5c --- /dev/null +++ b/src/lib/ProfileCardData.ts @@ -0,0 +1,12 @@ +import { Project } from '@prisma/client'; + +export type ProfileCardData = { + email: string; + bio: string | null; + firstName: string | null; + lastName: string | null; + picture: string | null; + title: string | null; + projects: Project[]; + interests: string[]; +}; diff --git a/src/lib/ProjectCardData.ts b/src/lib/ProjectCardData.ts new file mode 100644 index 0000000..3481820 --- /dev/null +++ b/src/lib/ProjectCardData.ts @@ -0,0 +1,10 @@ +import { Profile } from '@prisma/client'; + +export type ProjectCardData = { + name: string; + homepage: string | null; + picture: string | null; + description: string | null; + interests: string[]; + participants: Profile[]; +}; diff --git a/src/lib/StickyState.ts b/src/lib/StickyState.ts new file mode 100644 index 0000000..4e2fa30 --- /dev/null +++ b/src/lib/StickyState.ts @@ -0,0 +1,16 @@ +import { Entity, entity } from 'simpler-state'; + +const entities: Record; savedSetter: (value: any) => void }> = {}; + +// eslint-disable-next-line import/prefer-default-export +export const useStickyState = (name: string, initialValue: any) => { + if (entities[name]) { + const { savedEntity, savedSetter } = entities[name]; + return [savedEntity.use(), savedSetter]; + } + // Otherwise create a new entity + const newEntity = entity(initialValue); + const newEntitySetter = (newValue: unknown) => newEntity.set(newValue); + entities[name] = { savedEntity: newEntity, savedSetter: newEntitySetter }; + return [newEntity.use(), newEntitySetter]; +}; diff --git a/src/lib/dbActions.ts b/src/lib/dbActions.ts index 416296f..aea078e 100644 --- a/src/lib/dbActions.ts +++ b/src/lib/dbActions.ts @@ -1,72 +1,37 @@ 'use server'; -import { Stuff, Condition } from '@prisma/client'; -import { hash } from 'bcrypt'; -import { redirect } from 'next/navigation'; +import { compare, hash } from 'bcrypt'; import { prisma } from './prisma'; -/** - * Adds a new stuff to the database. - * @param stuff, an object with the following properties: name, quantity, owner, condition. - */ -export async function addStuff(stuff: { name: string; quantity: number; owner: string; condition: string }) { - // console.log(`addStuff data: ${JSON.stringify(stuff, null, 2)}`); - let condition: Condition = 'good'; - if (stuff.condition === 'poor') { - condition = 'poor'; - } else if (stuff.condition === 'excellent') { - condition = 'excellent'; - } else { - condition = 'fair'; - } - await prisma.stuff.create({ - data: { - name: stuff.name, - quantity: stuff.quantity, - owner: stuff.owner, - condition, - }, +export async function getUser(email: string) { + // console.log(`getUser data: ${email}`); + // eslint-disable-next-line @typescript-eslint/return-await + return await prisma.user.findUnique({ + where: { email }, }); - // After adding, redirect to the list page - redirect('/list'); } -/** - * Edits an existing stuff in the database. - * @param stuff, an object with the following properties: id, name, quantity, owner, condition. - */ -export async function editStuff(stuff: Stuff) { - // console.log(`editStuff data: ${JSON.stringify(stuff, null, 2)}`); - await prisma.stuff.update({ - where: { id: stuff.id }, - data: { - name: stuff.name, - quantity: stuff.quantity, - owner: stuff.owner, - condition: stuff.condition, - }, - }); - // After updating, redirect to the list page - redirect('/list'); +export async function checkPassword(credentials: { email: string; password: string }) { + // console.log(`checkPassword data: ${JSON.stringify(credentials, null, 2)}`); + const user = await getUser(credentials.email); + if (!user) { + return false; + } + // eslint-disable-next-line @typescript-eslint/return-await + return await compare(credentials.password, user.password); } -/** - * Deletes an existing stuff from the database. - * @param id, the id of the stuff to delete. - */ -export async function deleteStuff(id: number) { - // console.log(`deleteStuff id: ${id}`); - await prisma.stuff.delete({ - where: { id }, +export async function changePassword(credentials: { email: string; password: string }) { + // console.log(`changePassword data: ${JSON.stringify(credentials, null, 2)}`); + const password = await hash(credentials.password, 10); + await prisma.user.update({ + where: { email: credentials.email }, + data: { + password, + }, }); - // After deleting, redirect to the list page - redirect('/list'); } -/** - * Creates a new user in the database. - * @param credentials, an object with the following properties: email, password. - */ export async function createUser(credentials: { email: string; password: string }) { // console.log(`createUser data: ${JSON.stringify(credentials, null, 2)}`); const password = await hash(credentials.password, 10); @@ -78,17 +43,92 @@ export async function createUser(credentials: { email: string; password: string }); } -/** - * Changes the password of an existing user in the database. - * @param credentials, an object with the following properties: email, password. - */ -export async function changePassword(credentials: { email: string; password: string }) { - // console.log(`changePassword data: ${JSON.stringify(credentials, null, 2)}`); - const password = await hash(credentials.password, 10); - await prisma.user.update({ - where: { email: credentials.email }, - data: { - password, +export async function createProject(project: any) { + // console.log(`createProject data: ${JSON.stringify(project, null, 2)}`); + const dbProject = await prisma.project.create({ + data: project, + }); + return dbProject; +} + +export async function upsertProject(project: any) { + // console.log(`upsertProject data: ${JSON.stringify(project, null, 2)}`); + const dbProject = await prisma.project.upsert({ + where: { name: project.name }, + update: {}, + create: { + name: project.name, + description: project.description, + homepage: project.homepage, + picture: project.picture, }, }); + project.interests.forEach(async (intere: string) => { + const dbInterest = await prisma.interest.findUnique({ + where: { name: intere }, + }); + // console.log(`${dbProject.name} ${dbInterest!.name}`); + const dbProjectInterest = await prisma.projectInterest.findMany({ + where: { projectId: dbProject.id, interestId: dbInterest!.id }, + }); + if (dbProjectInterest.length === 0) { + await prisma.projectInterest.create({ + data: { + projectId: dbProject.id, + interestId: dbInterest!.id, + }, + }); + } + }); + project.participants.forEach(async (email: string) => { + const dbProfile = await prisma.profile.findUnique({ + where: { email }, + }); + const dbProfileProject = await prisma.profileProject.findMany({ + where: { projectId: dbProject.id, profileId: dbProfile!.id }, + }); + if (dbProfileProject.length === 0) { + await prisma.profileProject.create({ + data: { + projectId: dbProject.id, + profileId: dbProfile!.id, + }, + }); + } + }); + return dbProject; +} + +export async function updateProfile(profile: any) { + console.log(`updateProfile data: ${JSON.stringify(profile, null, 2)}`); + const dbProfile = await prisma.profile.upsert({ + where: { email: profile.name }, + update: { + firstName: profile.firstName, + lastName: profile.lastName, + bio: profile.bio, + }, + create: { + firstName: profile.firstName, + lastName: profile.lastName, + bio: profile.bio, + email: profile.email, + }, + }); + profile.interests.forEach(async (intere: string) => { + const dbInterest = await prisma.interest.findUnique({ + where: { name: intere }, + }); + const dbProfileInterest = await prisma.profileInterest.findMany({ + where: { profileId: dbProfile.id, interestId: dbInterest!.id }, + }); + if (dbProfileInterest.length === 0) { + await prisma.profileInterest.create({ + data: { + profileId: dbProfile.id, + interestId: dbInterest!.id, + }, + }); + } + }); } diff --git a/src/lib/validationSchemas.ts b/src/lib/validationSchemas.ts index 6d48cfd..f600b4a 100644 --- a/src/lib/validationSchemas.ts +++ b/src/lib/validationSchemas.ts @@ -1,16 +1,37 @@ import * as Yup from 'yup'; -export const AddStuffSchema = Yup.object({ - name: Yup.string().required(), - quantity: Yup.number().positive().required(), - condition: Yup.string().oneOf(['excellent', 'good', 'fair', 'poor']).required(), - owner: Yup.string().required(), +export interface IProject { + name: string; + homepage?: string; + picture?: string; + description: string; + interests?: (string | undefined)[] | undefined; + participants?: (string | undefined)[] | undefined; +} + +export const AddProjectSchema = Yup.object().shape({ + name: Yup.string().required('Name is required'), + homepage: Yup.string().optional().url('Homepage must be a valid URL'), + picture: Yup.string().optional(), + description: Yup.string().required('Description is required'), + interests: Yup.array().of(Yup.string()), + participants: Yup.array().of(Yup.string()), }); -export const EditStuffSchema = Yup.object({ - id: Yup.number().required(), - name: Yup.string().required(), - quantity: Yup.number().positive().required(), - condition: Yup.string().oneOf(['excellent', 'good', 'fair', 'poor']).required(), - owner: Yup.string().required(), +export interface IProfile { + firstName: string; + lastName: string; + email: string; + bio?: string; + interests?: (string | undefined)[] | undefined; + projects?: (string | undefined)[] | undefined; +} + +export const ProfileSchema = Yup.object().shape({ + firstName: Yup.string().required('First name is required'), + lastName: Yup.string().required('Last name is required'), + email: Yup.string().required('Email is required').email('Email must be a valid email'), + bio: Yup.string().optional(), + interests: Yup.array().of(Yup.string()), + projects: Yup.array().of(Yup.string()), });