Cleaned up home page + added editing to projects page

This commit is contained in:
2023-11-29 23:44:34 -06:00
parent 425591aa5e
commit 47e375f76a
11 changed files with 382 additions and 165 deletions

View File

@@ -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
View File

@@ -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",

View File

@@ -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": {

View File

@@ -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} />
),

View File

@@ -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}
/>

View File

@@ -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>
</a>
</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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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 {

View File

@@ -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
View 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));
}