Cleaned up home page + added editing to projects page
This commit is contained in:
@@ -18,6 +18,6 @@
|
||||
"extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"],
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-imports": "warn",
|
||||
"react/no-unescaped-entities": "no"
|
||||
"react/no-unescaped-entities": "off"
|
||||
}
|
||||
}
|
||||
|
||||
73
package-lock.json
generated
73
package-lock.json
generated
@@ -28,6 +28,7 @@
|
||||
"@trpc/react-query": "^10.9.0",
|
||||
"@trpc/server": "^10.9.0",
|
||||
"class-variance-authority": "^0.4.0",
|
||||
"clsx": "^2.0.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"highlight.js": "^11.8.0",
|
||||
"next": "13.1.6",
|
||||
@@ -41,6 +42,7 @@
|
||||
"react-icons": "^4.8.0",
|
||||
"rehype-highlight": "^6.0.0",
|
||||
"superjson": "1.9.1",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -2187,6 +2189,14 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/focus/node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/grid": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.6.0.tgz",
|
||||
@@ -2685,6 +2695,14 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/utils/node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/visually-hidden": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.7.0.tgz",
|
||||
@@ -2700,6 +2718,14 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/visually-hidden/node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-stately/calendar": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.1.0.tgz",
|
||||
@@ -4255,9 +4281,9 @@
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
|
||||
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -8824,6 +8850,15 @@
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
||||
"integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
|
||||
@@ -10870,6 +10905,13 @@
|
||||
"@react-types/shared": "^3.17.0",
|
||||
"@swc/helpers": "^0.4.14",
|
||||
"clsx": "^1.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@react-aria/grid": {
|
||||
@@ -11280,6 +11322,13 @@
|
||||
"@react-types/shared": "^3.17.0",
|
||||
"@swc/helpers": "^0.4.14",
|
||||
"clsx": "^1.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@react-aria/visually-hidden": {
|
||||
@@ -11292,6 +11341,13 @@
|
||||
"@react-types/shared": "^3.17.0",
|
||||
"@swc/helpers": "^0.4.14",
|
||||
"clsx": "^1.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@react-stately/calendar": {
|
||||
@@ -12401,9 +12457,9 @@
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
|
||||
"integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q=="
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
@@ -15490,6 +15546,11 @@
|
||||
"tslib": "^2.5.0"
|
||||
}
|
||||
},
|
||||
"tailwind-merge": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
|
||||
"integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ=="
|
||||
},
|
||||
"tailwindcss": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"@trpc/react-query": "^10.9.0",
|
||||
"@trpc/server": "^10.9.0",
|
||||
"class-variance-authority": "^0.4.0",
|
||||
"clsx": "^2.0.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"highlight.js": "^11.8.0",
|
||||
"next": "13.1.6",
|
||||
@@ -42,6 +43,7 @@
|
||||
"react-icons": "^4.8.0",
|
||||
"rehype-highlight": "^6.0.0",
|
||||
"superjson": "1.9.1",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -11,12 +11,12 @@ type HTMLTag = Tag<HTMLElement>;
|
||||
type HTag = Tag<HTMLHeadingElement>;
|
||||
|
||||
export const components = {
|
||||
h1: (props: HTag) => <h1 className="font-bold text-r-5xl" {...props} />,
|
||||
h2: (props: HTag) => <h2 className="font-bold text-r-4xl" {...props} />,
|
||||
h3: (props: HTag) => <h3 className="font-bold text-r-3xl" {...props} />,
|
||||
h4: (props: HTag) => <h4 className="font-bold text-r-2xl" {...props} />,
|
||||
h5: (props: HTag) => <h5 className="font-bold text-r-xl" {...props} />,
|
||||
h6: (props: HTag) => <h1 className="font-bold text-r-lg" {...props} />,
|
||||
h1: (props: HTag) => <h1 className="font-bold text-r-4xl" {...props} />,
|
||||
h2: (props: HTag) => <h2 className="font-bold text-r-3xl" {...props} />,
|
||||
h3: (props: HTag) => <h3 className="font-bold text-r-2xl" {...props} />,
|
||||
h4: (props: HTag) => <h4 className="font-bold text-r-xl" {...props} />,
|
||||
h5: (props: HTag) => <h5 className="font-bold text-r-lg" {...props} />,
|
||||
h6: (props: HTag) => <h1 className="font-bold text-r-base" {...props} />,
|
||||
p: (props: Tag<HTMLParagraphElement>) => (
|
||||
<p className="text-r-lg" {...props} />
|
||||
),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { cn } from "@utils/cn";
|
||||
import type { TextInputProps, MultilineInputProps } from "@utils/types/props";
|
||||
import { cva } from "class-variance-authority";
|
||||
import React, { useEffect, useImperativeHandle, useRef } from "react";
|
||||
@@ -66,6 +67,7 @@ export const MultilineTextInput = React.forwardRef<
|
||||
optional = false,
|
||||
error,
|
||||
hasAdaptiveHeight = false,
|
||||
className,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
@@ -74,23 +76,23 @@ export const MultilineTextInput = React.forwardRef<
|
||||
useImperativeHandle(ref, () => innerRef.current as HTMLTextAreaElement);
|
||||
|
||||
// Handle adaptive height
|
||||
useEffect(() => {
|
||||
const r = innerRef.current;
|
||||
// useEffect(() => {
|
||||
// const r = innerRef.current;
|
||||
|
||||
function updateHeight() {
|
||||
if (!r) return;
|
||||
const scrollHeight = r.scrollHeight;
|
||||
r.style.height = `${scrollHeight}px`;
|
||||
}
|
||||
// function updateHeight() {
|
||||
// if (!r) return;
|
||||
// const scrollHeight = r.scrollHeight;
|
||||
// r.style.height = `${scrollHeight}px`;
|
||||
// }
|
||||
|
||||
if (hasAdaptiveHeight) {
|
||||
r?.addEventListener("input", updateHeight);
|
||||
}
|
||||
// if (hasAdaptiveHeight) {
|
||||
// r?.addEventListener("input", updateHeight);
|
||||
// }
|
||||
|
||||
return () => {
|
||||
r?.removeEventListener("input", updateHeight);
|
||||
};
|
||||
}, [innerRef.current?.scrollHeight, hasAdaptiveHeight]);
|
||||
// return () => {
|
||||
// r?.removeEventListener("input", updateHeight);
|
||||
// };
|
||||
// }, [innerRef.current?.scrollHeight, hasAdaptiveHeight]);
|
||||
|
||||
return (
|
||||
<fieldset className="w-full">
|
||||
@@ -104,7 +106,10 @@ export const MultilineTextInput = React.forwardRef<
|
||||
>
|
||||
<textarea
|
||||
placeholder={placeholder}
|
||||
className="max-h-64 min-h-[150px] w-full appearance-none overflow-y-hidden border-none bg-transparent text-fg placeholder-fg/50 outline-none transition-all"
|
||||
className={cn(
|
||||
"min-h-[150px] w-full appearance-none overflow-y-hidden border-none bg-transparent text-fg placeholder-fg/50 outline-none transition-all",
|
||||
className
|
||||
)}
|
||||
ref={innerRef}
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
@@ -56,18 +56,18 @@ const Header: React.FC = () => {
|
||||
>
|
||||
<Background />
|
||||
<nav className="container flex flex-row items-center justify-between gap-x-4">
|
||||
<a href="#" className="text-r-2xl min-w-max font-bold text-primary">
|
||||
<a href="#" className="min-w-max font-bold text-primary text-r-2xl">
|
||||
<span className="text-fg">||</span> Parallel
|
||||
</a>
|
||||
<div className="flex flex-row items-center gap-x-4 sm:gap-x-8">
|
||||
<a href="#" className="text-r-lg font-medium">
|
||||
About Us
|
||||
{/*<div className="flex flex-row items-center gap-x-4 sm:gap-x-8">
|
||||
<a href="#" className="font-medium text-r-lg">
|
||||
About
|
||||
</a>
|
||||
<FlexDivider />
|
||||
<a href="#" className="text-r-lg font-medium">
|
||||
<FlexDivider />
|
||||
<a href="#" className="font-medium text-r-lg">
|
||||
Premium
|
||||
</a>
|
||||
</div>
|
||||
</div>*/}
|
||||
<CTAButton />
|
||||
</nav>
|
||||
</header>
|
||||
@@ -75,7 +75,7 @@ const Header: React.FC = () => {
|
||||
};
|
||||
|
||||
const FlexDivider: React.FC = () => (
|
||||
<span className="text-r-2xl text-fg/25">||</span>
|
||||
<span className="text-fg/25 text-r-2xl">||</span>
|
||||
);
|
||||
|
||||
const Background: React.FC = () => (
|
||||
@@ -125,11 +125,55 @@ const CTAButton: React.FC = () => {
|
||||
// === Footer =================================================================
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="container flex flex-row justify-center py-32">
|
||||
{/* <nav> */}
|
||||
<h2 className="text-r-4xl font-bold">Footer goes here!</h2>
|
||||
{/* </nav> */}
|
||||
<footer className="mx-auto max-w-xl px-6 pt-4">
|
||||
<div className="grid items-center gap-y-8 md:grid-cols-2 ">
|
||||
<a
|
||||
href="#"
|
||||
className="-ml-[0.8em] min-w-max font-bold text-primary text-r-4xl"
|
||||
>
|
||||
<span className="text-fg">||</span> Parallel
|
||||
</a>
|
||||
|
||||
<div className="hidden md:block" />
|
||||
|
||||
<div>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://zyrrus.dev"
|
||||
className="hover:opacity- block"
|
||||
>
|
||||
Created by Zyrrus
|
||||
</a>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/zyrrus/parallel"
|
||||
className="hover:opacity- block"
|
||||
>
|
||||
Check it out on GitHub
|
||||
</a>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() =>
|
||||
void signIn(undefined, {
|
||||
callbackUrl: "/projects",
|
||||
redirect: false,
|
||||
})
|
||||
}
|
||||
variant={{ size: "small" }}
|
||||
className="md:ml-auto"
|
||||
>
|
||||
Sign Up Now
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="col-span-2 mb-8 mt-28 text-center text-r-base">
|
||||
Copyright © {year} Parallel
|
||||
</p>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { User, Project, ProjectPreview } from "@prisma/client";
|
||||
import type { MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||
import { PropsWithChildren } from "react";
|
||||
import type { PropsWithChildren } from "react";
|
||||
import * as Toolbar from "@radix-ui/react-toolbar";
|
||||
import Image from "next/image";
|
||||
import { MDX } from "@components/MDX";
|
||||
@@ -8,6 +8,13 @@ import { FiEdit3, FiSettings, FiEye } from "react-icons/fi";
|
||||
import { ProjectPageMode } from "@utils/types/projects";
|
||||
import { useRouter } from "next/router";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { MultilineTextInput, TextInput } from "@components/TextInput";
|
||||
import { api } from "@utils/api";
|
||||
import type { SubmitHandler } from "react-hook-form";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { proposalSchema } from "@utils/constants/schema/project";
|
||||
import type { z } from "zod";
|
||||
|
||||
// TODO: all icon buttons should have tooltips
|
||||
|
||||
@@ -30,10 +37,12 @@ type FetchedProjectAsProps = {
|
||||
};
|
||||
|
||||
interface DisplayProps extends FetchedProjectAsProps, PropsWithChildren {
|
||||
toolbar: React.ReactNode | undefined;
|
||||
description: MDXRemoteSerializeResult<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
>;
|
||||
descriptionAsString: string;
|
||||
}
|
||||
|
||||
type CanSetMode = {
|
||||
@@ -41,14 +50,16 @@ type CanSetMode = {
|
||||
};
|
||||
|
||||
type GenericDisplayProps = DisplayProps & PropsWithChildren;
|
||||
type MemberDisplayProps = DisplayProps & CanSetMode;
|
||||
type EditorDisplayProps = DisplayProps & CanSetMode;
|
||||
type PreviewDisplayProps = DisplayProps & CanSetMode;
|
||||
type VisitorDisplayProps = Omit<DisplayProps, "toolbar"> & CanSetMode;
|
||||
type MemberDisplayProps = Omit<DisplayProps, "toolbar"> & CanSetMode;
|
||||
type EditorDisplayProps = Omit<DisplayProps, "toolbar"> & CanSetMode;
|
||||
type PreviewDisplayProps = Omit<DisplayProps, "toolbar"> & CanSetMode;
|
||||
|
||||
const GenericDisplay: React.FC<GenericDisplayProps> = ({
|
||||
project,
|
||||
description,
|
||||
children: toolbarItems,
|
||||
toolbar,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex-1">
|
||||
@@ -67,17 +78,25 @@ const GenericDisplay: React.FC<GenericDisplayProps> = ({
|
||||
className="flex w-full flex-row items-center border-y-2 border-y-fg/10 bg-bg-600 px-5 py-3"
|
||||
aria-label="Project options"
|
||||
>
|
||||
{toolbarItems}
|
||||
{toolbar}
|
||||
</Toolbar.Root>
|
||||
<article className="mx-5 my-3">
|
||||
<h1 className="font-bold text-primary text-r-5xl">{project?.title}</h1>
|
||||
<MDX {...description} />
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<>
|
||||
<h1 className="font-bold text-primary text-r-5xl">
|
||||
{project?.title}
|
||||
</h1>
|
||||
<MDX {...description} />
|
||||
</>
|
||||
)}
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const VisitorDisplay: React.FC<DisplayProps> = (props) => {
|
||||
export const VisitorDisplay: React.FC<VisitorDisplayProps> = (props) => {
|
||||
const router = useRouter();
|
||||
|
||||
const handleGoBack = () => {
|
||||
@@ -89,27 +108,34 @@ export const VisitorDisplay: React.FC<DisplayProps> = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<GenericDisplay {...props}>
|
||||
<span className="text-r-base">You are not a member of this project.</span>
|
||||
<Toolbar.Separator className="mx-3 h-5 w-[2px] rounded-full bg-fg/30" />
|
||||
<div className="flex-grow" />
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="mr-5 text-fg/80 text-r-base hover:text-fg-600/80"
|
||||
onClick={handleGoBack}
|
||||
>
|
||||
Go back
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="font-semibold text-fg text-r-base hover:text-fg-600"
|
||||
onClick={handleJoinRequest}
|
||||
>
|
||||
Request to join
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
</GenericDisplay>
|
||||
<GenericDisplay
|
||||
{...props}
|
||||
toolbar={
|
||||
<>
|
||||
<span className="text-r-base">
|
||||
You are not a member of this project.
|
||||
</span>
|
||||
<Toolbar.Separator className="mx-3 h-5 w-[2px] rounded-full bg-fg/30" />
|
||||
<div className="flex-grow" />
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="mr-5 text-fg/80 text-r-base hover:text-fg-600/80"
|
||||
onClick={handleGoBack}
|
||||
>
|
||||
Go back
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="font-semibold text-fg text-r-base hover:text-fg-600"
|
||||
onClick={handleJoinRequest}
|
||||
>
|
||||
Request to join
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -122,18 +148,23 @@ export const PreviewDisplay: React.FC<PreviewDisplayProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<GenericDisplay {...rest}>
|
||||
<span className="text-r-base">This is a preview.</span>
|
||||
<div className="flex-grow" />
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="font-semibold text-fg text-r-base hover:text-fg-600"
|
||||
onClick={handleExitPreview}
|
||||
>
|
||||
Exit preview
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
</GenericDisplay>
|
||||
<GenericDisplay
|
||||
{...rest}
|
||||
toolbar={
|
||||
<>
|
||||
<span className="text-r-base">This is a preview.</span>
|
||||
<div className="flex-grow" />
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="font-semibold text-fg text-r-base hover:text-fg-600"
|
||||
onClick={handleExitPreview}
|
||||
>
|
||||
Exit preview
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -146,75 +177,139 @@ export const MemberDisplay: React.FC<MemberDisplayProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<GenericDisplay {...rest}>
|
||||
<span className="text-r-base">You are a member of this project.</span>
|
||||
<Toolbar.Separator className="mx-3 h-5 w-[2px] rounded-full bg-fg/30" />
|
||||
<span className="text-r-base">Last updated X minutes ago.</span>
|
||||
<div className="flex-grow" />
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="-my-2 mr-1 rounded-full p-2 hover:bg-bg-300/30"
|
||||
onClick={handleEditMode}
|
||||
>
|
||||
<FiEdit3 className="font-bold text-r-2xl" />
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
{/* TODO: Make dropdown menu popup */}
|
||||
<Toolbar.Button asChild>
|
||||
<button className="-my-2 -mr-2 rounded-full p-2 hover:bg-bg-300/30">
|
||||
<FiSettings className="font-bold text-r-2xl" />
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
</GenericDisplay>
|
||||
<GenericDisplay
|
||||
{...rest}
|
||||
toolbar={
|
||||
<>
|
||||
<span className="text-r-base">You are a member of this project.</span>
|
||||
<Toolbar.Separator className="mx-3 h-5 w-[2px] rounded-full bg-fg/30" />
|
||||
<span className="text-r-base">Last updated X minutes ago.</span>
|
||||
<div className="flex-grow" />
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="-my-2 mr-1 rounded-full p-2 hover:bg-bg-300/30"
|
||||
onClick={handleEditMode}
|
||||
>
|
||||
<FiEdit3 className="font-bold text-r-2xl" />
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
{/* TODO: Make dropdown menu popup */}
|
||||
<Toolbar.Button asChild>
|
||||
<button className="-my-2 -mr-2 rounded-full p-2 hover:bg-bg-300/30">
|
||||
<FiSettings className="font-bold text-r-2xl" />
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type EditProjectForm = z.infer<typeof proposalSchema>;
|
||||
|
||||
export const EditorDisplay: React.FC<EditorDisplayProps> = ({
|
||||
setMode,
|
||||
...rest
|
||||
}) => {
|
||||
const { project, descriptionAsString } = rest;
|
||||
|
||||
const ctx = api.useContext();
|
||||
|
||||
const { handleSubmit, register, formState, reset } = useForm<EditProjectForm>(
|
||||
{
|
||||
resolver: zodResolver(proposalSchema),
|
||||
defaultValues: {
|
||||
title: project?.title,
|
||||
description: descriptionAsString,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { mutate, isLoading: isUpdating } =
|
||||
api.projects.editProject.useMutation({
|
||||
onSuccess: async () => {
|
||||
toast.success("Successfully updated the project");
|
||||
|
||||
await ctx.projects.getProjectById.invalidate();
|
||||
reset();
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(e.message);
|
||||
},
|
||||
});
|
||||
|
||||
const handleCancel = () => {
|
||||
reset();
|
||||
setMode(ProjectPageMode.member);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
toast.error("TODO: Need to save changes");
|
||||
const handleSave: SubmitHandler<EditProjectForm> = (data): void => {
|
||||
if (!project || isUpdating) {
|
||||
return;
|
||||
}
|
||||
mutate({ projectId: project.id, ...data });
|
||||
setMode(ProjectPageMode.member);
|
||||
};
|
||||
|
||||
const handlePreview = () => {
|
||||
setMode(ProjectPageMode.preview);
|
||||
toast("This feature is coming soon.", {
|
||||
icon: "🚧",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<GenericDisplay {...rest}>
|
||||
<span className="text-r-base">Editing</span>
|
||||
<Toolbar.Separator className="mx-3 h-5 w-[2px] rounded-full bg-fg/30" />
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="-my-2 mr-1 rounded-full p-2 hover:bg-bg-300/30"
|
||||
onClick={handlePreview}
|
||||
>
|
||||
<FiEye className="font-bold text-r-2xl" />
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
<div className="flex-grow" />
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="mr-5 text-fg/80 text-r-base hover:text-fg-600/80"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="font-semibold text-fg text-r-base hover:text-fg-600"
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save changes
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
</GenericDisplay>
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
<form className="flex-1" onSubmit={handleSubmit(handleSave)}>
|
||||
<GenericDisplay
|
||||
{...rest}
|
||||
toolbar={
|
||||
<>
|
||||
<span className="text-r-base">Editing</span>
|
||||
<Toolbar.Separator className="mx-3 h-5 w-[2px] rounded-full bg-fg/30" />
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="-my-2 mr-1 rounded-full p-2 hover:bg-bg-300/30"
|
||||
onClick={handlePreview}
|
||||
>
|
||||
<FiEye className="font-bold text-r-2xl" />
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
<div className="flex-grow" />
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
className="mr-5 text-fg/80 text-r-base hover:text-fg-600/80"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
<Toolbar.Button asChild>
|
||||
<button
|
||||
type="submit"
|
||||
className="font-semibold text-fg text-r-base hover:text-fg-600"
|
||||
>
|
||||
Save changes
|
||||
</button>
|
||||
</Toolbar.Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="mb-8 flex flex-col gap-y-4">
|
||||
<TextInput
|
||||
label="Title"
|
||||
placeholder={proposalSchema.shape.title.description}
|
||||
error={formState.errors.title?.message}
|
||||
{...register("title")}
|
||||
/>
|
||||
<MultilineTextInput
|
||||
label="Description"
|
||||
placeholder={proposalSchema.shape.description.description}
|
||||
// className=""
|
||||
error={formState.errors.description?.message}
|
||||
{...register("description")}
|
||||
/>
|
||||
</div>
|
||||
</GenericDisplay>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,10 +13,9 @@ const Home: NextPage = () => {
|
||||
<Hero />
|
||||
<SpacedDivider />
|
||||
<About />
|
||||
{/* <SpacedDivider />
|
||||
<Premium /> */}
|
||||
<SpacedDivider />
|
||||
<Premium />
|
||||
<SpacedDivider />
|
||||
<CTA />
|
||||
</HomeLayout>
|
||||
);
|
||||
};
|
||||
@@ -126,18 +125,3 @@ const Premium: React.FC = () => {
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const CTA: React.FC = () => {
|
||||
return (
|
||||
<section>
|
||||
<Button
|
||||
onClick={() =>
|
||||
void signIn(undefined, { callbackUrl: "/projects", redirect: false })
|
||||
}
|
||||
className="mx-auto block"
|
||||
>
|
||||
Sign Up Now
|
||||
</Button>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { MainLayout } from "@components/layouts";
|
||||
import {
|
||||
Project,
|
||||
ProjectLifecycle,
|
||||
ProjectPreview,
|
||||
User,
|
||||
} from "@prisma/client";
|
||||
import type { Project, ProjectPreview, User } from "@prisma/client";
|
||||
import { ProjectLifecycle } from "@prisma/client";
|
||||
import { api } from "@utils/api";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
@@ -55,7 +51,7 @@ const StateOrder: ProjectLifecycle[] = Object.values(ProjectLifecycle);
|
||||
|
||||
const SpecificProject: NextPage<
|
||||
InferGetServerSidePropsType<typeof getServerSideProps>
|
||||
> = ({ projectId, description }) => {
|
||||
> = ({ projectId, description, descriptionAsString }) => {
|
||||
const [mode, setMode] = useState(ProjectPageMode.visitor);
|
||||
|
||||
const session = useSession();
|
||||
@@ -108,7 +104,12 @@ const SpecificProject: NextPage<
|
||||
return (
|
||||
<MainLayout>
|
||||
<div className="flex flex-row justify-between">
|
||||
<Display project={data} description={description} setMode={setMode} />
|
||||
<Display
|
||||
project={data}
|
||||
description={description}
|
||||
descriptionAsString={descriptionAsString}
|
||||
setMode={setMode}
|
||||
/>
|
||||
{/* 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" />
|
||||
@@ -120,12 +121,13 @@ const SpecificProject: NextPage<
|
||||
<div className="flex flex-row gap-x-4">
|
||||
{/* TODO: Handle overflow */}
|
||||
<ProfilePicture
|
||||
name={data?.author.name}
|
||||
username={data?.author.username}
|
||||
image={data?.author.image}
|
||||
name={data?.author?.name}
|
||||
username={data?.author?.username}
|
||||
image={data?.author?.image}
|
||||
/>
|
||||
{data?.members.map((member) => (
|
||||
<ProfilePicture
|
||||
key={member.id}
|
||||
name={member.name}
|
||||
username={member.username}
|
||||
image={member.image}
|
||||
@@ -190,7 +192,13 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
mdxOptions: { rehypePlugins: [rehypeHighlight] },
|
||||
});
|
||||
|
||||
return { props: { projectId, description: mdxSource } };
|
||||
return {
|
||||
props: {
|
||||
projectId,
|
||||
description: mdxSource,
|
||||
descriptionAsString: projectDescription,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface ProjectProgressProps {
|
||||
|
||||
@@ -91,6 +91,18 @@ export const projectsRouter = createTRPCRouter({
|
||||
|
||||
return proposal;
|
||||
}),
|
||||
editProject: protectedProcedure
|
||||
.input(proposalSchema.extend({ projectId: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { title, description, projectId } = input;
|
||||
await ctx.prisma.project.update({
|
||||
where: { id: projectId },
|
||||
data: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
});
|
||||
}),
|
||||
updateState: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
|
||||
6
src/utils/cn.ts
Normal file
6
src/utils/cn.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
Reference in New Issue
Block a user