diff --git a/client/src/assets/HuskyGreeting.png b/client/src/assets/HuskyGreeting.png new file mode 100644 index 0000000..1f954c6 Binary files /dev/null and b/client/src/assets/HuskyGreeting.png differ diff --git a/client/src/assets/HuskyRating.png b/client/src/assets/HuskyRating.png new file mode 100644 index 0000000..2b57f1d Binary files /dev/null and b/client/src/assets/HuskyRating.png differ diff --git a/client/src/index.css b/client/src/index.css index 6f603db..02aca5f 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -300,3 +300,629 @@ input.RP-form-control:focus { /* ::-webkit-input-placeholder { font-weight: 800; } */ + + +/* RateTheProjectOwner Page */ +.RTPO-title { + margin-top: 1em; + font-weight: 900; + font-size: 45px; +} + + +.RTPO-subTitle { + margin-top: 1.2rem; + font-size: 28px; +} + + +/* stars */ +.starContainer { + /* margin-top: 15%; */ + margin-top: 8%; + margin-bottom: 5%; + + + position: relative; + top: 50%; + left: 50%; + + + width: 85%; + height: 30%; + + + transform: translate(-50%, -50%) rotateY(180deg); +} + + +.starContainer .container__items { + display: flex; + align-items: center; + justify-content: center; + + + gap: 0 0em; + + + width: 100%; + height: 100%; +} + + +.starContainer .container__items input { + display: none; +} + + +.starContainer .container__items label { + width: 20%; + aspect-ratio: 1; + cursor: pointer; +} + + +.starContainer .container__items label .star-stroke { + display: grid; + place-items: center; + width: 100%; + height: 100%; + background: #afabab; + clip-path: polygon( + 9.729% 43.07%, + 9.729% 43.07%, + 9.397% 42.682%, + 9.18% 42.252%, + 9.072% 41.8%, + 9.068% 41.342%, + 9.162% 40.897%, + 9.348% 40.481%, + 9.62% 40.113%, + 9.973% 39.81%, + 10.401% 39.591%, + 10.898% 39.472%, + 35.913% 36.504%, + 35.913% 36.504%, + 36.126% 36.468%, + 36.332% 36.411%, + 36.529% 36.333%, + 36.717% 36.236%, + 36.893% 36.122%, + 37.057% 35.99%, + 37.207% 35.842%, + 37.342% 35.678%, + 37.461% 35.5%, + 37.561% 35.309%, + 48.112% 12.435%, + 48.112% 12.435%, + 48.379% 11.999%, + 48.72% 11.66%, + 49.117% 11.418%, + 49.551% 11.273%, + 50.004% 11.224%, + 50.457% 11.273%, + 50.891% 11.418%, + 51.288% 11.66%, + 51.629% 11.999%, + 51.896% 12.435%, + 62.447% 35.309%, + 62.447% 35.309%, + 62.547% 35.5%, + 62.665% 35.678%, + 62.8% 35.841%, + 62.95% 35.99%, + 63.113% 36.122%, + 63.289% 36.237%, + 63.476% 36.334%, + 63.673% 36.411%, + 63.878% 36.469%, + 64.091% 36.505%, + 89.107% 39.472%, + 89.107% 39.472%, + 89.604% 39.591%, + 90.032% 39.81%, + 90.384% 40.113%, + 90.657% 40.481%, + 90.842% 40.897%, + 90.936% 41.342%, + 90.932% 41.8%, + 90.824% 42.253%, + 90.607% 42.682%, + 90.275% 43.07%, + 71.783% 60.175%, + 71.783% 60.175%, + 71.632% 60.329%, + 71.499% 60.497%, + 71.386% 60.675%, + 71.291% 60.864%, + 71.216% 61.06%, + 71.162% 61.263%, + 71.128% 61.471%, + 71.115% 61.683%, + 71.124% 61.896%, + 71.155% 62.11%, + 76.063% 86.816%, + 76.063% 86.816%, + 76.103% 87.326%, + 76.026% 87.801%, + 75.848% 88.23%, + 75.582% 88.603%, + 75.244% 88.909%, + 74.849% 89.136%, + 74.413% 89.274%, + 73.949% 89.312%, + 73.474% 89.239%, + 73.002% 89.043%, + 51.021% 76.736%, + 51.021% 76.736%, + 50.828% 76.64%, + 50.628% 76.566%, + 50.423% 76.513%, + 50.215% 76.482%, + 50.005% 76.471%, + 49.795% 76.482%, + 49.587% 76.514%, + 49.382% 76.568%, + 49.182% 76.642%, + 48.989% 76.738%, + 27.006% 89.04%, + 27.006% 89.04%, + 26.534% 89.235%, + 26.058% 89.309%, + 25.595% 89.272%, + 25.158% 89.134%, + 24.762% 88.907%, + 24.424% 88.602%, + 24.158% 88.23%, + 23.979% 87.8%, + 23.902% 87.326%, + 23.942% 86.816%, + 28.851% 62.111%, + 28.851% 62.111%, + 28.882% 61.897%, + 28.891% 61.684%, + 28.878% 61.472%, + 28.844% 61.264%, + 28.79% 61.061%, + 28.715% 60.864%, + 28.62% 60.675%, + 28.507% 60.497%, + 28.374% 60.329%, + 28.224% 60.175%, + 9.729% 43.07% + ); + transition: 0.3s; +} + + +.starContainer .container__items label .star-stroke .star-fill { + width: 80%; + aspect-ratio: 1; + background: white; + clip-path: polygon( + 9.729% 43.07%, + 9.729% 43.07%, + 9.397% 42.682%, + 9.18% 42.252%, + 9.072% 41.8%, + 9.068% 41.342%, + 9.162% 40.897%, + 9.348% 40.481%, + 9.62% 40.113%, + 9.973% 39.81%, + 10.401% 39.591%, + 10.898% 39.472%, + 35.913% 36.504%, + 35.913% 36.504%, + 36.126% 36.468%, + 36.332% 36.411%, + 36.529% 36.333%, + 36.717% 36.236%, + 36.893% 36.122%, + 37.057% 35.99%, + 37.207% 35.842%, + 37.342% 35.678%, + 37.461% 35.5%, + 37.561% 35.309%, + 48.112% 12.435%, + 48.112% 12.435%, + 48.379% 11.999%, + 48.72% 11.66%, + 49.117% 11.418%, + 49.551% 11.273%, + 50.004% 11.224%, + 50.457% 11.273%, + 50.891% 11.418%, + 51.288% 11.66%, + 51.629% 11.999%, + 51.896% 12.435%, + 62.447% 35.309%, + 62.447% 35.309%, + 62.547% 35.5%, + 62.665% 35.678%, + 62.8% 35.841%, + 62.95% 35.99%, + 63.113% 36.122%, + 63.289% 36.237%, + 63.476% 36.334%, + 63.673% 36.411%, + 63.878% 36.469%, + 64.091% 36.505%, + 89.107% 39.472%, + 89.107% 39.472%, + 89.604% 39.591%, + 90.032% 39.81%, + 90.384% 40.113%, + 90.657% 40.481%, + 90.842% 40.897%, + 90.936% 41.342%, + 90.932% 41.8%, + 90.824% 42.253%, + 90.607% 42.682%, + 90.275% 43.07%, + 71.783% 60.175%, + 71.783% 60.175%, + 71.632% 60.329%, + 71.499% 60.497%, + 71.386% 60.675%, + 71.291% 60.864%, + 71.216% 61.06%, + 71.162% 61.263%, + 71.128% 61.471%, + 71.115% 61.683%, + 71.124% 61.896%, + 71.155% 62.11%, + 76.063% 86.816%, + 76.063% 86.816%, + 76.103% 87.326%, + 76.026% 87.801%, + 75.848% 88.23%, + 75.582% 88.603%, + 75.244% 88.909%, + 74.849% 89.136%, + 74.413% 89.274%, + 73.949% 89.312%, + 73.474% 89.239%, + 73.002% 89.043%, + 51.021% 76.736%, + 51.021% 76.736%, + 50.828% 76.64%, + 50.628% 76.566%, + 50.423% 76.513%, + 50.215% 76.482%, + 50.005% 76.471%, + 49.795% 76.482%, + 49.587% 76.514%, + 49.382% 76.568%, + 49.182% 76.642%, + 48.989% 76.738%, + 27.006% 89.04%, + 27.006% 89.04%, + 26.534% 89.235%, + 26.058% 89.309%, + 25.595% 89.272%, + 25.158% 89.134%, + 24.762% 88.907%, + 24.424% 88.602%, + 24.158% 88.23%, + 23.979% 87.8%, + 23.902% 87.326%, + 23.942% 86.816%, + 28.851% 62.111%, + 28.851% 62.111%, + 28.882% 61.897%, + 28.891% 61.684%, + 28.878% 61.472%, + 28.844% 61.264%, + 28.79% 61.061%, + 28.715% 60.864%, + 28.62% 60.675%, + 28.507% 60.497%, + 28.374% 60.329%, + 28.224% 60.175%, + 9.729% 43.07% + ); + transition: 0.3s; +} + + +.starContainer .container__items input:hover ~ label .star-stroke, +.starContainer .container__items input:checked ~ label .star-stroke { + background: #fbbc05; + transition: 0.3s; +} + + +.starContainer .container__items input:checked ~ label .star-stroke .star-fill { + background: #fbbc05; + transition: 0.3s; +} + + +.starContainer .container__items label:hover .label-description::after { + content: attr(data-content); + position: fixed; + left: 0; + right: 0; + + + margin-inline: auto; + + + width: 100%; + height: 2em; + + + color: #5e5d5d; + + + text-align: center; + font-size: 1.3rem; + + + transform: rotateY(180deg); + transition: 0.3s; +} + + +.huskyRatingImg { + max-width: 100%; + min-width: 30%; + height: auto; + margin: 0 0; +} + + +.RTPO-form-control { + border: 1px solid #afabab; + border-left: none; + border-right: none; + border-top: none; + margin: none; + width: 70%; + font-weight: lighter; + text-align: center; + color: #3d3c3c; +} + + +input.RTPO-form-control:focus { + outline: none; + outline-width: 0; + border: none; + border-bottom: 1px solid #afabab; + box-shadow: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + text-align: center; + color: #3d3c3c; +} + + +.RTPO-input-div { + text-align: center; + margin-top: 3%; + margin-bottom: 3%; +} + + +.RTPO-button-orange { + background-color: #fbbc05; + width: 45%; + min-width: fit-content; + margin-right: 8%; + margin-left: 8%; + margin-top: 5%; + padding-top: 2.5%; + padding-bottom: 2.5%; +} + + +.RTPO-button-red { + background-color: #ea4335; + color: #ffffff; + min-width: fit-content; + width: 45%; + margin-top: 5%; + margin-left: 8%; + margin-right: 8%; + padding-top: 2.5%; + padding-bottom: 2.5%; +} + + +.RTPO-button-red:hover { + background-color: rgb(239, 122, 111); +} + + +.RTPO-wrapper { + display: flex; + flex-direction: row; + display: flex; + align-items: center; + justify-content: space-between; + /* margin-top: 5%; */ + margin-top: 4%; + margin-bottom: 0; +} + + +.speech-bubble { + position: relative; + width: 70%; + padding: 5%; + background-color: #4285f4; + color: #fff; + border-radius: 10px; + margin: 0px 0px 1% 20%; + animation: fadeIn 1s ease-out; +} + + +.speech-bubble::before { + content: ""; + position: absolute; + top: 100%; + left: 35%; + border-width: 35px 30px 0px 3px; + border-style: solid; + border-color: #4285f4 transparent transparent transparent; + transform: translateX(-50%); +} + + +.RTPO-speech-bubble-text { + font-size: 16px; +} + + +.RTPO-name { + font-weight: bold; + font-size: larger; +} + + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-25px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +.Husky-Img-Wrapper { + align-self: flex-end; +} + + +.RTPO-input-notes { + color: #918f8f; + padding: 0% 15%; + margin-top: 4%; + justify-self: center; +} + + +@media (max-width: 805px) { + .RTPO-wrapper { + display: flex; + flex-direction: column; + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 5%; + margin-bottom: 0; + font-size: smaller; + } + + + .huskyRatingImg { + max-width: 45%; + min-width: 30%; + height: auto; + position: absolute; + right: 0; + margin: 0 0; + } + + + .speech-bubble::before { + content: ""; + position: absolute; + top: 100%; + left: 35%; + border-width: 35px 3px 0px 30px; + border-style: solid; + border-color: #4285f4 transparent transparent transparent; + transform: translateX(-50%); + } + + + .RTPO-title { + margin-top: 1em; + font-weight: 900; + font-size: 35px; + } + + + .RTPO-subTitle { + margin-top: 1.2rem; + font-size: 18px; + } +} + + +/* Rating Confirmation Page */ +.image-center { + display: block; + margin-left: auto; + margin-right: auto; + max-width: 40%; + width: auto; +} + + +.RC-title { + font-size: 68px; + font-weight: 900; +} + + +.RC-text { + padding-left: 3em; + padding-right: 3em; + font-weight: 500; + font-size: 30px; +} + + +.RC-button-orange { + margin: 3% 7%; + padding-top: 1.1rem; + padding-bottom: 1.1rem; + font-size: 16px; + background-color: #fbbc05; + width: 190px; +} + + +.RC-button-orange:hover, +.RTPO-button-orange:hover { + background-color: #fae198; +} + + +.RC-button-wrapper, +.RTPO-button-wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-top: 5%; + margin-bottom: 8%; +} + + +@media (min-width: 950px) { + .RC-button-wrapper, + .RTPO-button-wrapper { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 0 20%; + margin-top: 5%; + margin-bottom: 8%; + } +} + + +.RC-button-confirmation { + padding-left: 3em; + padding-right: 3em; +} + + diff --git a/client/src/pages/CreateProj.tsx b/client/src/pages/CreateProj.tsx index cb00e18..7813499 100644 --- a/client/src/pages/CreateProj.tsx +++ b/client/src/pages/CreateProj.tsx @@ -14,7 +14,7 @@ export type FormState = { title: string; description: string; location: string; - maxMems: string; + maxMems: number; startDate: string; image: Blob | null | string; }; @@ -25,7 +25,7 @@ export default function CreateProj() { title: '', description: '', location: '', - maxMems: '', + maxMems: 0, image: null, startDate: '', }); // state form the inputs @@ -55,12 +55,16 @@ export default function CreateProj() { mutationFn: (image: Blob) => uploadProjectImage(image), onSuccess(url) { if (!user) return; + if (Number.isNaN(formState.maxMems)) { + return; + } projMutation.mutate({ name: formState.title, description: formState.description, meetLocation: formState.location, meetType: '', ownerId: user?.id, + maxMems: formState.maxMems, imageUrl: url, startDate: formState.startDate, tags: addedTags, @@ -267,7 +271,7 @@ export function InputsView({ max={100} placeholder="5" onChange={(e) => - setFormState({ ...formState, maxMems: e.target.value }) + setFormState({ ...formState, maxMems: Number(e.target.value)}) } value={formState.maxMems} required diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index 7f7b2fc..c9e6535 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -11,6 +11,8 @@ import { NavLink, Outlet, Form } from 'react-router-dom'; import Markdown from 'react-markdown'; import { RouterOutputs, trpc } from '../utils/trpc'; import { z } from 'zod'; +import Husky from "../assets/HuskyRating.png"; +import Husky2 from "../assets/HuskyGreeting.png"; dayjjs.extend(relativeTime); @@ -25,6 +27,16 @@ function useGetProjectData() { } export default function Project() { + + const ratingMutation = trpc.ratings.create.useMutation({ + onSuccess() { + utils.ratings.getAll.invalidate(); + // toast.success("Project Created!"); + // navigate("/"); + }, + }); + + // const { projectId } = useParams() const { data, isLoading, projectId } = useGetProjectData(); const { data: role, isLoading: roleIsLoading } = trpc.memberships.getRole.useQuery(projectId ?? ''); @@ -32,6 +44,7 @@ export default function Project() { const [showModal, setShowModal] = useState(false); const utils = trpc.useUtils(); + const sendJoinReqMutation = trpc.memberships.sendProjectJoinRequest.useMutation({ onSuccess() { @@ -48,6 +61,7 @@ export default function Project() { if (!data) return; if (!user) return; const formData = new FormData(e.currentTarget); + const description = formData.get('description') as string; @@ -91,6 +105,58 @@ export default function Project() { } } + ///////////////////////// + // rate projects + const [firstModalOpen, setFirstModalOpen] = useState(false); + const [secondModalOpen, setSecondModalOpen] = useState(false); + + const handleOpenFirstModal = () => { + setFirstModalOpen(true); + }; + + const handleCloseFirstModal = () => { + setFirstModalOpen(false); + }; + + const handleCloseSecondModal = () => { + setSecondModalOpen(false); + }; + + const handleOpenSecondModal = () => { + setFirstModalOpen(false); + setSecondModalOpen(true); + }; + + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const formData = new FormData(e.currentTarget); + + const feedbacks = formData.get("feedbacks") as string; + const rating = Object.fromEntries(formData).stars; + if (!user) { + return; + } + + ratingMutation.mutate({ + userId: user.id, + projectId: projectId ?? "", + feedback: feedbacks, + rating: parseInt(rating as string), + }) + + // console.log("Feedbacks:", feedbacks); + // console.log(typeof(parseInt(rating as string))); + // console.log(user?.firstName, user?.lastName); + // console.log(user); + // console.log(projectId); + + handleCloseFirstModal(); + handleOpenSecondModal(); + }; + + + // TO DO: Delete Project // const deleteProjectMutation = trpc.projects.delete.useMutation({ @@ -131,6 +197,8 @@ export default function Project() { if (!data) return
Project was not found
; + + //shows the user the view of the project and ability/options to join. //TO FIX: join button is not working. return ( @@ -230,11 +298,18 @@ export default function Project() {
@@ -270,6 +345,13 @@ type StatusChipProps = { ownerId: string; setShowModal: React.Dispatch>; projectId: string; + firstModalOpen: boolean; + secondModalOpen: boolean; + startDate: Date; + handleOpenFirstModal: () => void; + handleCloseFirstModal: () => void; + handleCloseSecondModal: () => void; + handleSubmit: (e: React.FormEvent) => void; handleLeaveReq: (e: React.FormEvent) => void; }; @@ -279,6 +361,13 @@ function StatusChip({ ownerId, setShowModal, handleLeaveReq, + firstModalOpen, + secondModalOpen, + handleSubmit, + handleOpenFirstModal, + handleCloseFirstModal, + handleCloseSecondModal, + startDate, projectId, }: StatusChipProps) { const user = useUser(); @@ -337,8 +426,211 @@ function StatusChip({ ); } else if (role.status === 'ACCEPTED') { + if (startDate > new Date()) { + return ( + <> +
+ + + +
+
+ Member +
+ + ) + } else { return ( <> +
+ + + {firstModalOpen ? ( +
handleSubmit(e)} + > +
+

+ Rate Your Experience: + {/* Project Owner: Flying Walrus{" "} */} +

+

+ 5000 Pieces Puzzle BuildingFlying Walrus +

+ +
+
+ {/* stars */} +
+
+ + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+

+ Note: Your feedback will be anonymouse and only visible to + the project owner!{" "} +

+
+
+ +
+ + +
+
+
+
+ + How many stars would you like to give to + {""}Flying Walrus? + +
+ Husky Greeting! +
+
+
+
+ ) : ( +
+ )} + + {secondModalOpen ? ( +
+
+ Husky Woof! +

+ Woof! +

+ + +
+

+ You have successfully submitted the rating for + + {" "} + Flying Walrus + + !! Thank you!! +

+
+ + +
+ {/* */} + + +{/* + + + */} +
+
+
+ ) : ( +
+ )} +
@@ -349,8 +641,10 @@ function StatusChip({
Member
+ ); + } } } @@ -478,6 +772,7 @@ export function ProjectInfo() { if (!data) return
something went wrong, Try again!
; + return (
diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 58868bc..3667b65 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -25,8 +25,9 @@ model Project { posts Post[] memberships Memberships[] imageUrl String + ratings Ratings[] startDate String - + maxMems Int } model Tag { @@ -88,3 +89,16 @@ enum MembershipStatus { PENDING REJECTED } + +model Ratings { + id String @id @default(uuid()) + createdAt DateTime @default(now()) + feedback String + rating Int + updatedAt DateTime @updatedAt + projectId String + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + userId String + @@unique([projectId, userId]) + @@index([projectId]) +} diff --git a/server/src/index.ts b/server/src/index.ts index 622fc50..3b33d2e 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -4,11 +4,13 @@ import cors from 'cors'; import { projectsRouter } from './routers/projectsRouter'; import { membershipsRouter } from './routers/membershipsRouter'; import { tagsRouter } from './routers/tagsRouter'; -import { postsRouter } from './routers/postsRouter'; +import { postsRouter } from "./routers/postsRouter"; +import { ratingRouter } from "./routers/ratingsRouter"; const appRouter = router({ projects: projectsRouter, memberships: membershipsRouter, + ratings: ratingRouter, posts: postsRouter, tags: tagsRouter, }); diff --git a/server/src/routers/projectsRouter.ts b/server/src/routers/projectsRouter.ts index 0276ba7..7d48876 100644 --- a/server/src/routers/projectsRouter.ts +++ b/server/src/routers/projectsRouter.ts @@ -135,6 +135,7 @@ export const projectsRouter = router({ imageUrl: z.string(), startDate: z.string(), tags: z.array(z.string()), + maxMems: z.number(), }), ) .mutation(async ({ ctx, input }) => { diff --git a/server/src/routers/ratingsRouter.ts b/server/src/routers/ratingsRouter.ts new file mode 100644 index 0000000..d8db443 --- /dev/null +++ b/server/src/routers/ratingsRouter.ts @@ -0,0 +1,81 @@ +import { z } from "zod"; +import { authedProcedure, publicProcedure, router } from "../trpc"; +import { TRPCError } from "@trpc/server"; + +export const ratingRouter = router({ + getAll: publicProcedure.query(async ({ ctx }) => { + const projects = await ctx.db.project.findMany({ + take: 5, + }); + return projects; + }), + + getById: publicProcedure.input(z.string()).query(async ({ ctx, input }) => { + const data = await ctx.db.project.findFirst({ + where: { + id: input, + }, + }); + + // const membershipts = await ctx.db + + if (!data) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "No project has corresponding id", + }); + } + + return data; + }), + + getByUserId: authedProcedure + .input(z.string()) + .query(async ({ ctx, input }) => { + return await ctx.db.project.findMany({ + where: { + ownerId: input, + }, + }); + }), + + // must be owner + kickUser: authedProcedure + .input( + z.object({ + userId: z.string(), + projectId: z.string(), + }), + ) + .mutation(async ({ ctx, input }) => { + await ctx.db.memberships.delete({ + where: { + projectId_userId: input, + }, + }); + }), + + getPosts: publicProcedure.input(z.string()).query(async ({ ctx, input }) => { + return await ctx.db.post.findMany({ + where: { + projectId: input, + }, + }); + }), + + // must be owner + create: authedProcedure + .input( + z.object({ + feedback: z.string(), + rating: z.number(), + projectId: z.string(), + userId: z.string(), + }), + ) + .mutation(async ({ ctx, input }) => { + await ctx.db.ratings.create({ data: input }); + }), + + +});