Set up side panel + banner image for project pages
This commit is contained in:
205
package-lock.json
generated
205
package-lock.json
generated
@@ -14,7 +14,7 @@
|
||||
"@next-auth/prisma-adapter": "^1.0.5",
|
||||
"@next/font": "^13.1.6",
|
||||
"@prisma/client": "^4.9.0",
|
||||
"@radix-ui/react-avatar": "^1.0.2",
|
||||
"@radix-ui/react-avatar": "^1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.3",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
||||
"@radix-ui/react-hover-card": "^1.0.5",
|
||||
@@ -701,19 +701,138 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-avatar": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.0.2.tgz",
|
||||
"integrity": "sha512-XRL8z2l9V7hRLCPjHWg/34RBPZUGpmOjmsRSNvIh2DI28GyIWDChbcsDUVc63MzOItk6Q83Ob2KK8k2FUlXlGA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.0.3.tgz",
|
||||
"integrity": "sha512-9ToF7YNex3Ste45LrAeTlKtONI9yVRt/zOS158iilIkW5K/Apeyb/TUQlcEFTEFvWr8Kzdi2ZYrm1/suiXPajQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz",
|
||||
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
|
||||
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
|
||||
"integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
|
||||
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
@@ -2702,7 +2821,7 @@
|
||||
"version": "18.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
|
||||
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
@@ -9108,15 +9227,67 @@
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-avatar": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.0.2.tgz",
|
||||
"integrity": "sha512-XRL8z2l9V7hRLCPjHWg/34RBPZUGpmOjmsRSNvIh2DI28GyIWDChbcsDUVc63MzOItk6Q83Ob2KK8k2FUlXlGA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.0.3.tgz",
|
||||
"integrity": "sha512-9ToF7YNex3Ste45LrAeTlKtONI9yVRt/zOS158iilIkW5K/Apeyb/TUQlcEFTEFvWr8Kzdi2ZYrm1/suiXPajQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-context": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz",
|
||||
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
|
||||
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-slot": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
|
||||
"integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
|
||||
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-collection": {
|
||||
@@ -10696,7 +10867,7 @@
|
||||
"version": "18.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
|
||||
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"@next-auth/prisma-adapter": "^1.0.5",
|
||||
"@next/font": "^13.1.6",
|
||||
"@prisma/client": "^4.9.0",
|
||||
"@radix-ui/react-avatar": "^1.0.2",
|
||||
"@radix-ui/react-avatar": "^1.0.3",
|
||||
"@radix-ui/react-dialog": "^1.0.3",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
||||
"@radix-ui/react-hover-card": "^1.0.5",
|
||||
|
||||
@@ -145,7 +145,7 @@ const ProjectLifecycleIndicator: React.FC<{ state: ProjectLifecycle }> = ({
|
||||
<Tooltip.Content
|
||||
side="right"
|
||||
sideOffset={4}
|
||||
className="animate-slideRightAndFade rounded border-4 border-fg bg-quaternary-600 px-3 py-2 text-r-lg"
|
||||
className="animate-slideRightAndFade rounded-md bg-bg-700 p-3 px-3 py-2 text-r-lg"
|
||||
>
|
||||
{lifecycleTooltips[state]}
|
||||
{/* <Tooltip.Arrow className="fill-fg" /> */}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { MainLayout } from "@components/layouts";
|
||||
import { ProjectLifecycle } from "@prisma/client";
|
||||
import {
|
||||
Project,
|
||||
ProjectLifecycle,
|
||||
ProjectPreview,
|
||||
User,
|
||||
} from "@prisma/client";
|
||||
import { api } from "@utils/api";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
@@ -16,12 +21,35 @@ import { createProxySSGHelpers } from "@trpc/react-query/ssg";
|
||||
import { MDX } from "@components/MDX";
|
||||
import Image from "next/image";
|
||||
import { FiEdit3 } from "react-icons/fi";
|
||||
import { MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||
import React, { useState } from "react";
|
||||
import * as Avatar from "@radix-ui/react-avatar";
|
||||
|
||||
enum PageMode {
|
||||
visitor,
|
||||
member,
|
||||
editor,
|
||||
preview,
|
||||
}
|
||||
|
||||
type FetchedProjectAsProps = {
|
||||
project:
|
||||
| (Project & {
|
||||
author: User;
|
||||
members: User[];
|
||||
previews: ProjectPreview[];
|
||||
})
|
||||
| null
|
||||
| undefined;
|
||||
};
|
||||
|
||||
const StateOrder: ProjectLifecycle[] = Object.values(ProjectLifecycle);
|
||||
|
||||
const SpecificProject: NextPage<
|
||||
InferGetServerSidePropsType<typeof getServerSideProps>
|
||||
> = ({ projectId, description }) => {
|
||||
const [mode, setMode] = useState(PageMode.visitor);
|
||||
|
||||
const ctx = api.useContext();
|
||||
|
||||
const { data, isRefetching } = api.projects.getProjectById.useQuery(
|
||||
@@ -34,49 +62,83 @@ const SpecificProject: NextPage<
|
||||
}
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const { mutate, isLoading: isUpdatingState } =
|
||||
api.projects.updateState.useMutation({
|
||||
onSuccess: () => {
|
||||
void ctx.projects.getProjectById.invalidate();
|
||||
},
|
||||
});
|
||||
// const { mutate, isLoading: isUpdatingState } =
|
||||
// api.projects.updateState.useMutation({
|
||||
// onSuccess: () => {
|
||||
// void ctx.projects.getProjectById.invalidate();
|
||||
// },
|
||||
// });
|
||||
|
||||
const changeState = (diff: -1 | 1) => {
|
||||
if (!data) return;
|
||||
// const changeState = (diff: -1 | 1) => {
|
||||
// if (!data) return;
|
||||
|
||||
const newState =
|
||||
StateOrder[
|
||||
StateOrder.findIndex((state) => state === data.state) + diff
|
||||
] ?? data.state;
|
||||
// const newState =
|
||||
// StateOrder[
|
||||
// StateOrder.findIndex((state) => state === data.state) + diff
|
||||
// ] ?? data.state;
|
||||
|
||||
if (newState !== data.state) {
|
||||
mutate({
|
||||
projectId: data.id,
|
||||
state: newState,
|
||||
});
|
||||
}
|
||||
// if (newState !== data.state) {
|
||||
// mutate({
|
||||
// projectId: data.id,
|
||||
// state: newState,
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// TODO: Make edit mode do something
|
||||
const componentsByMode = {
|
||||
[PageMode.visitor]: (
|
||||
<DisplayProject project={data} description={description} />
|
||||
),
|
||||
[PageMode.member]: (
|
||||
<DisplayProject project={data} description={description} />
|
||||
),
|
||||
[PageMode.editor]: (
|
||||
<DisplayProject project={data} description={description} />
|
||||
),
|
||||
[PageMode.preview]: (
|
||||
<DisplayProject project={data} description={description} />
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<div className="max-w-4xl">
|
||||
<BannerImage
|
||||
bannerImageUrl={
|
||||
data?.bannerImageUrl ??
|
||||
`https://picsum.photos/seed/${data?.id ?? "A"}/800/200.webp`
|
||||
}
|
||||
/>
|
||||
<article className="mx-11 mt-8 flex-grow">
|
||||
<div className="flex flex-row items-start justify-between">
|
||||
<h1 className="font-bold text-primary text-r-5xl">{data?.title}</h1>
|
||||
<button className="rounded-full p-3 hover:bg-bg-300/30">
|
||||
<FiEdit3 size={24} className="font-bold" />
|
||||
</button>
|
||||
</div>
|
||||
<MDX {...description} />
|
||||
</article>
|
||||
<div className="flex flex-row justify-between">
|
||||
<DisplayProject project={data} description={description} />
|
||||
{/* Right side panel */}
|
||||
<div className="sticky top-0 flex h-screen w-96 flex-row">
|
||||
<div className="w-1.5 border-x-2 border-x-fg/10" />
|
||||
<aside className="flex w-full flex-col gap-y-4 overflow-x-hidden px-11 py-8">
|
||||
<div>
|
||||
<h2 className="mb-2 font-bold text-primary text-r-4xl">
|
||||
Contributors
|
||||
</h2>
|
||||
<div className="flex flex-row gap-x-4">
|
||||
{/* TODO: Handle overflow */}
|
||||
<ProfilePicture
|
||||
name={data?.author.name}
|
||||
image={data?.author.image}
|
||||
/>
|
||||
{data?.members.map((member) => (
|
||||
<ProfilePicture name={member.name} image={member.image} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="mb-2 font-bold text-primary text-r-4xl">
|
||||
Progress
|
||||
</h2>
|
||||
<ProjectProgress progress={data?.state} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="mb-2 font-bold text-primary text-r-4xl">
|
||||
Messages
|
||||
</h2>
|
||||
<p>[Coming soon]</p>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
{/* Right side panel */}
|
||||
</MainLayout>
|
||||
);
|
||||
};
|
||||
@@ -122,22 +184,88 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
return { props: { projectId, description: mdxSource } };
|
||||
};
|
||||
|
||||
const BannerImage: React.FC<{ bannerImageUrl: string }> = ({
|
||||
bannerImageUrl,
|
||||
}) => {
|
||||
// TODO: On hover, show edit button + popup for a new image picker
|
||||
interface ProfilePictureProps {
|
||||
name: string | undefined | null;
|
||||
image: string | undefined | null;
|
||||
}
|
||||
|
||||
const ProfilePicture: React.FC<ProfilePictureProps> = ({ name, image }) => {
|
||||
return (
|
||||
<button className="relative h-44 w-full shadow hover:opacity-80">
|
||||
<Image
|
||||
src={bannerImageUrl}
|
||||
alt="project banner"
|
||||
className="object-cover"
|
||||
fill
|
||||
<Avatar.Root className="flex min-w-max select-none items-center justify-center overflow-hidden rounded-full bg-quaternary p-2 align-middle shadow-solid-medium">
|
||||
<Avatar.Image
|
||||
className="h-10 w-10 rounded-[inherit] object-cover"
|
||||
src={image ?? undefined}
|
||||
alt={name ?? "User avatar"}
|
||||
/>
|
||||
</button>
|
||||
<Avatar.Fallback
|
||||
className="leading-1 flex h-10 w-10 items-center justify-center rounded-[inherit] bg-tertiary font-medium"
|
||||
delayMs={600}
|
||||
>
|
||||
{name?.charAt(0)}
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
);
|
||||
};
|
||||
|
||||
const ProjectDetails: React.FC<{ title: string }> = ({ title }) => {
|
||||
return <></>;
|
||||
interface ProjectProgressProps {
|
||||
progress: ProjectLifecycle | undefined | null;
|
||||
}
|
||||
|
||||
const ProjectProgress: React.FC<ProjectProgressProps> = ({ progress }) => {
|
||||
const stateIndex = StateOrder.findIndex((state) => state === progress) ?? -1;
|
||||
|
||||
return (
|
||||
<div className="relative flex w-full flex-row justify-between gap-x-4">
|
||||
{/* TODO: Fix shadows */}
|
||||
<div className="absolute top-1/2 -z-10 h-4 w-full -translate-y-1/2 bg-quaternary" />
|
||||
{["bg-disabled", "bg-warning", "bg-error", "bg-success"].map((bg, i) => {
|
||||
return (
|
||||
<div key={bg} className="h-12 w-12 rounded-full bg-quaternary p-2">
|
||||
{i <= stateIndex && (
|
||||
<div className={`h-full w-full rounded-full ${bg}`} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface DisplayProjectProps extends FetchedProjectAsProps {
|
||||
description: MDXRemoteSerializeResult<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
>;
|
||||
}
|
||||
|
||||
const DisplayProject: React.FC<DisplayProjectProps> = ({
|
||||
project,
|
||||
description,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex-1">
|
||||
<div className="relative h-44 w-full shadow">
|
||||
<Image
|
||||
src={
|
||||
project?.bannerImageUrl ??
|
||||
`https://picsum.photos/seed/${project?.id ?? "A"}/800/200.webp`
|
||||
}
|
||||
alt="project banner"
|
||||
className="object-cover"
|
||||
fill
|
||||
/>
|
||||
</div>
|
||||
<article className="mx-5 mt-8">
|
||||
<div className="flex flex-row items-start justify-between">
|
||||
<h1 className="font-bold text-primary text-r-5xl">
|
||||
{project?.title}
|
||||
</h1>
|
||||
<button className="-mr-3 mt-1 rounded-full p-3 hover:bg-bg-300/30">
|
||||
<FiEdit3 className="font-bold text-r-4xl" />
|
||||
</button>
|
||||
</div>
|
||||
<MDX {...description} />
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -77,6 +77,7 @@ export const projectsRouter = createTRPCRouter({
|
||||
.query(({ ctx, input }) => {
|
||||
return ctx.prisma.project.findUnique({
|
||||
where: { id: input.projectId },
|
||||
include: { author: true, members: true, previews: true },
|
||||
});
|
||||
}),
|
||||
createProposal: protectedProcedure
|
||||
|
||||
Reference in New Issue
Block a user