mirror of
https://github.com/khairul169/code-share.git
synced 2026-10-24 11:53:58 +07:00
feat: add homepage & auth
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
import MainLayout from "~/components/layout/main-layout";
|
||||
|
||||
export { MainLayout as Layout };
|
||||
@@ -0,0 +1,61 @@
|
||||
import { z } from "zod";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import Card, { CardTitle } from "~/components/ui/card";
|
||||
import Divider from "~/components/ui/divider";
|
||||
import Input from "~/components/ui/input";
|
||||
import { useForm } from "~/hooks/useForm";
|
||||
import trpc from "~/lib/trpc";
|
||||
import { useSearchParams } from "~/renderer/hooks";
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(1),
|
||||
});
|
||||
|
||||
const initialValue: z.infer<typeof schema> = {
|
||||
email: "",
|
||||
password: "",
|
||||
};
|
||||
|
||||
const LoginPage = () => {
|
||||
const form = useForm(schema, initialValue);
|
||||
const searchParams = useSearchParams();
|
||||
const login = trpc.auth.login.useMutation({
|
||||
onSuccess() {
|
||||
const prevPage = searchParams.get("return");
|
||||
window.location.href = prevPage || "/";
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = form.handleSubmit((values) => {
|
||||
login.mutate(values);
|
||||
});
|
||||
|
||||
return (
|
||||
<main className="container max-w-xl min-h-[80dvh] flex flex-col items-center justify-center py-8 md:py-16">
|
||||
<Card className="w-full md:p-8">
|
||||
<CardTitle>Log in</CardTitle>
|
||||
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="space-y-3">
|
||||
<Input form={form} name="email" label="Email Address" />
|
||||
<Input
|
||||
form={form}
|
||||
type="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Divider className="mt-8 mb-4" />
|
||||
|
||||
<Button type="submit" isLoading={login.isPending}>
|
||||
Login
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
@@ -0,0 +1,3 @@
|
||||
import MainLayout from "~/components/layout/main-layout";
|
||||
|
||||
export { MainLayout as Layout };
|
||||
+49
-8
@@ -1,20 +1,61 @@
|
||||
import { Button } from "~/components/ui/button";
|
||||
import type { Data } from "./+data";
|
||||
import { useData } from "~/renderer/hooks";
|
||||
import Link from "~/renderer/link";
|
||||
import Footer from "~/components/containers/footer";
|
||||
|
||||
const HomePage = () => {
|
||||
const { projects } = useData<Data>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Posts</h1>
|
||||
<>
|
||||
<main>
|
||||
<section className="container py-8 md:py-16 min-h-dvh md:min-h-[80vh] flex items-center justify-center flex-col text-center">
|
||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-medium text-transparent bg-gradient-to-b from-gray-200 to-gray-100 bg-clip-text">
|
||||
Create and Share Web Snippets
|
||||
</h1>
|
||||
<p className="text-lg mt-8 md:mt-12 text-gray-300">
|
||||
Collaborate with Ease, Share with the World
|
||||
</p>
|
||||
|
||||
{projects.map((project: any) => (
|
||||
<Link key={project.id} href={`/${project.slug}`}>
|
||||
{project.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-3 items-center mt-8 md:mt-12">
|
||||
<Button href="/get-started" size="lg">
|
||||
Getting Started
|
||||
</Button>
|
||||
<Button href="#browse" size="lg" variant="outline">
|
||||
Browse Projects
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="browse" className="container max-w-5xl py-8 md:py-16">
|
||||
<h2 className="text-4xl font-medium border-b pb-3 mb-8 border-white/20">
|
||||
Community Projects
|
||||
</h2>
|
||||
|
||||
<div className="grid sm:grid-cols-2 md:grid-cols-3 gap-3 md:gap-4">
|
||||
{projects.map((project) => (
|
||||
<Link
|
||||
key={project.id}
|
||||
href={`/${project.slug}`}
|
||||
className="border border-white/20 hover:border-white/40 rounded-lg transition-colors overflow-hidden"
|
||||
>
|
||||
<div className="w-full aspect-[3/2] bg-gray-900"></div>
|
||||
<div className="py-2 px-3 flex items-center gap-3">
|
||||
<div className="size-8 rounded-full bg-white/80"></div>
|
||||
<div>
|
||||
<p className="text-md truncate">{project.title}</p>
|
||||
<p className="text-xs truncate">{project.user.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import { Button } from "~/components/ui/button";
|
||||
import Card, { CardTitle } from "~/components/ui/card";
|
||||
import { FormLabel } from "~/components/ui/form-field";
|
||||
import Input from "~/components/ui/input";
|
||||
import { useData } from "~/renderer/hooks";
|
||||
import { Data } from "./+data";
|
||||
import { useForm } from "~/hooks/useForm";
|
||||
import { z } from "zod";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { navigate } from "vike/client/router";
|
||||
import Divider from "~/components/ui/divider";
|
||||
import trpc from "~/lib/trpc";
|
||||
import { useAuth } from "~/hooks/useAuth";
|
||||
import { usePageContext } from "~/renderer/context";
|
||||
|
||||
const schema = z.object({
|
||||
forkFromId: z.number(),
|
||||
title: z.string(),
|
||||
user: z
|
||||
.object({
|
||||
name: z.string().min(3),
|
||||
email: z.string().email(),
|
||||
password: z.string().min(6),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const initialValue: z.infer<typeof schema> = {
|
||||
forkFromId: 0,
|
||||
title: "",
|
||||
};
|
||||
|
||||
const GetStartedPage = () => {
|
||||
const { presets } = useData<Data>();
|
||||
const { isLoggedIn } = useAuth();
|
||||
const ctx = usePageContext();
|
||||
|
||||
const form = useForm(schema, initialValue);
|
||||
const create = trpc.project.create.useMutation({
|
||||
onSuccess(data) {
|
||||
navigate(`/${data.slug}`);
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = form.handleSubmit((values) => {
|
||||
create.mutate(values);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="container max-w-3xl min-h-[80dvh] flex flex-col items-center justify-center py-8 md:py-16">
|
||||
<Card className="w-full md:p-8">
|
||||
<CardTitle>Create New Project</CardTitle>
|
||||
|
||||
<form onSubmit={onSubmit}>
|
||||
<FormLabel>Select Preset</FormLabel>
|
||||
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="forkFromId"
|
||||
render={({ field }) => (
|
||||
<div className="flex md:grid md:grid-cols-3 gap-4 overflow-x-auto md:overflow-x-hidden">
|
||||
{presets.map((preset) => (
|
||||
<Button
|
||||
key={preset.projectId}
|
||||
variant={
|
||||
field.value === preset.projectId ? "default" : "outline"
|
||||
}
|
||||
className="flex py-16 border border-white/40 shrink-0 w-[160px] md:w-auto"
|
||||
onClick={() => field.onChange(preset.projectId)}
|
||||
>
|
||||
<p className="text-wrap">{preset.title}</p>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Input
|
||||
form={form}
|
||||
name="title"
|
||||
label="Title"
|
||||
placeholder="Optional"
|
||||
className="mt-4"
|
||||
/>
|
||||
|
||||
{!isLoggedIn ? (
|
||||
<div className="mt-8">
|
||||
<p className="text-lg">Account detail</p>
|
||||
<Divider className="mt-2 mb-4" />
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-x-2 gap-y-4">
|
||||
<div className="order-1 md:order-2 flex flex-col items-center justify-center gap-3 py-4">
|
||||
<p className="text-center">Already have an account?</p>
|
||||
<Button
|
||||
href={`/auth/login?return=${encodeURI(ctx.urlPathname)}`}
|
||||
variant="outline"
|
||||
>
|
||||
Log in now
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="order-2 md:order-1 space-y-3">
|
||||
<Input
|
||||
form={form}
|
||||
name="user.name"
|
||||
label="Full Name"
|
||||
placeholder="John Doe"
|
||||
/>
|
||||
<Input
|
||||
form={form}
|
||||
name="user.email"
|
||||
label="Email Address"
|
||||
placeholder="[email protected]"
|
||||
/>
|
||||
<Input
|
||||
form={form}
|
||||
type="password"
|
||||
name="user.password"
|
||||
label="Password"
|
||||
placeholder="P@ssw0rd123!"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Divider className="my-6" />
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
isLoading={create.isPending}
|
||||
className="w-full md:w-[160px]"
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GetStartedPage;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { PageContext } from "vike/types";
|
||||
import trpcServer from "~/server/api/trpc/trpc";
|
||||
|
||||
export const data = async (ctx: PageContext) => {
|
||||
const trpc = await trpcServer(ctx);
|
||||
const presets = await trpc.project.getTemplates();
|
||||
|
||||
return { presets };
|
||||
};
|
||||
|
||||
export type Data = Awaited<ReturnType<typeof data>>;
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
DialogTitle,
|
||||
} from "../../../../components/ui/dialog";
|
||||
import { UseDiscloseReturn } from "~/hooks/useDisclose";
|
||||
import { Input } from "../../../../components/ui/input";
|
||||
import { BaseInput } from "../../../../components/ui/input";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import { useForm } from "~/hooks/useForm";
|
||||
import { z } from "zod";
|
||||
@@ -76,7 +76,7 @@ const CreateFileDialog = ({ disclose, onSuccess }: Props) => {
|
||||
<form onSubmit={onSubmit}>
|
||||
<FormErrorMessage form={form} />
|
||||
|
||||
<Input
|
||||
<BaseInput
|
||||
placeholder={isDir ? "Directory Name" : "Filename"}
|
||||
autoFocus
|
||||
{...form.register("filename")}
|
||||
|
||||
@@ -3,8 +3,7 @@ import Panel from "~/components/ui/panel";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useProjectContext } from "../context/project";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { FaEllipsisV, FaRedo } from "react-icons/fa";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import { FaRedo } from "react-icons/fa";
|
||||
import { previewStore } from "../stores/web-preview";
|
||||
|
||||
type WebPreviewProps = {
|
||||
|
||||
Reference in New Issue
Block a user