From 298fd929990f06771ed3220469c964275b55e232 Mon Sep 17 00:00:00 2001 From: "Siaw A. Nicholas" Date: Sat, 23 Mar 2024 21:21:34 +0000 Subject: [PATCH 1/4] feat: initial boilerplate from other branch and setup auth with clerk --- client/components.json | 17 ++ client/next.config.mjs | 11 +- client/package.json | 26 ++- client/src/app/globals.css | 89 +++++++--- client/src/app/layout.tsx | 47 ++++-- client/src/app/page.tsx | 104 ------------ client/src/components/core/header.tsx | 130 +++++++++++++++ client/src/components/core/recent-posts.tsx | 13 ++ client/src/components/core/side-bar.tsx | 52 ++++++ client/src/components/posts/posts.tsx | 5 + client/src/components/ui/button.tsx | 56 +++++++ client/src/components/ui/collapsible.tsx | 11 ++ client/src/components/ui/form.tsx | 176 ++++++++++++++++++++ client/src/components/ui/input.tsx | 24 +++ client/src/components/ui/label.tsx | 26 +++ client/src/components/ui/separator.tsx | 31 ++++ client/src/components/ui/sheet.tsx | 140 ++++++++++++++++ client/src/lib/utils.ts | 6 + client/src/middleware.ts | 25 +++ client/tailwind.config.ts | 86 ++++++++-- 20 files changed, 915 insertions(+), 160 deletions(-) create mode 100644 client/components.json create mode 100644 client/src/components/core/header.tsx create mode 100644 client/src/components/core/recent-posts.tsx create mode 100644 client/src/components/core/side-bar.tsx create mode 100644 client/src/components/posts/posts.tsx create mode 100644 client/src/components/ui/button.tsx create mode 100644 client/src/components/ui/collapsible.tsx create mode 100644 client/src/components/ui/form.tsx create mode 100644 client/src/components/ui/input.tsx create mode 100644 client/src/components/ui/label.tsx create mode 100644 client/src/components/ui/separator.tsx create mode 100644 client/src/components/ui/sheet.tsx create mode 100644 client/src/lib/utils.ts create mode 100644 client/src/middleware.ts diff --git a/client/components.json b/client/components.json new file mode 100644 index 0000000..597fc69 --- /dev/null +++ b/client/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/app/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "~/components", + "utils": "~/lib/utils" + } +} \ No newline at end of file diff --git a/client/next.config.mjs b/client/next.config.mjs index 4678774..b78abe2 100644 --- a/client/next.config.mjs +++ b/client/next.config.mjs @@ -1,4 +1,13 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'ui-avatars.com', + } + ] + } +}; export default nextConfig; diff --git a/client/package.json b/client/package.json index c6c38a6..8bb0a01 100644 --- a/client/package.json +++ b/client/package.json @@ -9,19 +9,33 @@ "lint": "next lint" }, "dependencies": { + "@clerk/clerk-react": "^4.16.3", + "@clerk/nextjs": "^4.29.9", + "@radix-ui/react-collapsible": "^1.0.3", + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-separator": "^1.0.3", + "@radix-ui/react-slot": "^1.0.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "lucide-react": "^0.363.0", + "next": "14.1.4", "react": "^18", "react-dom": "^18", - "next": "14.1.4" + "react-icons": "^5.0.1", + "tailwind-merge": "^2.2.2", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.22.4" }, "devDependencies": { - "typescript": "^5", - "@types/node": "^20", - "@types/react": "^18", + "@types/node": "^20.11.30", + "@types/react": "^18.2.67", "@types/react-dom": "^18", "autoprefixer": "^10.0.1", + "eslint": "^8", + "eslint-config-next": "14.1.4", "postcss": "^8", "tailwindcss": "^3.3.0", - "eslint": "^8", - "eslint-config-next": "14.1.4" + "typescript": "^5.4.3" } } diff --git a/client/src/app/globals.css b/client/src/app/globals.css index 875c01e..7d2d81f 100644 --- a/client/src/app/globals.css +++ b/client/src/app/globals.css @@ -2,32 +2,75 @@ @tailwind components; @tailwind utilities; -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; -} +/*--primary: 45.882 100% 55.9%;*/ +/*--secondary: 211.578 16.4% 47.1%;*/ + + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 20 14.3% 4.1%; + --card: 0 0% 100%; + --card-foreground: 20 14.3% 4.1%; + --popover: 0 0% 100%; + --popover-foreground: 20 14.3% 4.1%; + --primary: 45.882 100% 55.9%; + --primary-foreground: 26 83.3% 14.1%; + --secondary: 211.578 16.4% 47.1%; + --secondary-foreground: 24 9.8% 10%; + --muted: 60 4.8% 95.9%; + --muted-foreground: 25 5.3% 44.7%; + --accent: 60 4.8% 95.9%; + --accent-foreground: 24 9.8% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 60 9.1% 97.8%; + --border: 20 5.9% 90%; + --input: 20 5.9% 90%; + --ring: 20 14.3% 4.1%; + --radius: 0.5rem; + } -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - } + .dark { + --background: 20 14.3% 4.1%; + --foreground: 60 9.1% 97.8%; + --card: 20 14.3% 4.1%; + --card-foreground: 60 9.1% 97.8%; + --popover: 20 14.3% 4.1%; + --popover-foreground: 60 9.1% 97.8%; + --primary: 47.9 95.8% 53.1%; + --primary-foreground: 26 83.3% 14.1%; + --secondary: 12 6.5% 15.1%; + --secondary-foreground: 60 9.1% 97.8%; + --muted: 12 6.5% 15.1%; + --muted-foreground: 24 5.4% 63.9%; + --accent: 12 6.5% 15.1%; + --accent-foreground: 60 9.1% 97.8%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 60 9.1% 97.8%; + --border: 12 6.5% 15.1%; + --input: 12 6.5% 15.1%; + --ring: 35.5 91.7% 32.9%; + } } -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); + +@layer base { + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + } } -@layer utilities { - .text-balance { - text-wrap: balance; - } +::-webkit-scrollbar { + width: .60rem; + background-color: rgba(20, 20, 20, 0.301); + border-radius: 5rem; } + +::-webkit-scrollbar-thumb { + background: rgba(122, 128, 138, 0.8); + border-radius: .75rem; +} \ No newline at end of file diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx index 3314e47..53860a3 100644 --- a/client/src/app/layout.tsx +++ b/client/src/app/layout.tsx @@ -1,22 +1,43 @@ -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; +import type {Metadata} from "next"; +import {Inter} from "next/font/google"; +import Header from "~/components/core/header"; +import SideBar from "~/components/core/side-bar"; +import RecentPosts from "~/components/core/recent-posts"; import "./globals.css"; +import {Separator} from "~/components/ui/separator"; +import {ClerkProvider} from "@clerk/nextjs"; +import Posts from "~/components/posts/posts"; -const inter = Inter({ subsets: ["latin"] }); +const inter = Inter({subsets: ["latin"]}); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Create Next App", + description: "Generated by create next app", }; export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; + children, + }: Readonly<{ + children: React.ReactNode; }>) { - return ( - - {children} - - ); + return ( + + + +
+ +
+ +
+ {children} + +
+
+ +
+
+ + + + ); } diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index b81507d..6303b10 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -3,111 +3,7 @@ import Image from "next/image"; export default function Home() { return (
-
-

- Get started by editing  - src/app/page.tsx -

- -
-
- Next.js Logo -
- -
); } diff --git a/client/src/components/core/header.tsx b/client/src/components/core/header.tsx new file mode 100644 index 0000000..43bbce3 --- /dev/null +++ b/client/src/components/core/header.tsx @@ -0,0 +1,130 @@ +'use client' + +import {Input} from "~/components/ui/input" +import React, {useState} from "react"; +import {FaArrowDown, FaBars, FaPlus} from "react-icons/fa6"; +import {FaHome, FaSearch} from "react-icons/fa"; +import {FaRocket} from "react-icons/fa"; +import {Separator} from "~/components/ui/separator" +import { UserButton, useClerk } from "@clerk/nextjs"; + +// import {Button} from "~/components/ui/button"; +import { + Sheet, + SheetContent, +} from "~/components/ui/sheet" + +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "~/components/ui/collapsible" + +import Link from "next/link"; +import {Button} from "~/components/ui/button"; +import Image from "next/image"; + + +export default function Header() { + const [searchValue, setSearchValue] = useState(''); + const [open, setOpen] = useState(false) + const {openSignIn, openSignUp} = useClerk() + + const handleChange = (event: React.ChangeEvent) => { + setSearchValue(event.target.value) + } + + const handleSearch: React.KeyboardEventHandler = (event) => { + if (event.key === 'Enter') { + console.log(searchValue) + setSearchValue('') + } + } + + const handleShow = () => { + setOpen(!open) + } + return ( + + ) +} diff --git a/client/src/components/core/recent-posts.tsx b/client/src/components/core/recent-posts.tsx new file mode 100644 index 0000000..18897f2 --- /dev/null +++ b/client/src/components/core/recent-posts.tsx @@ -0,0 +1,13 @@ +import {Separator} from "~/components/ui/separator"; +import React from "react"; + +export default function RecentPosts() { + return ( +
+ +
+

Recent Posts

+
+
+ ) +} \ No newline at end of file diff --git a/client/src/components/core/side-bar.tsx b/client/src/components/core/side-bar.tsx new file mode 100644 index 0000000..fad6238 --- /dev/null +++ b/client/src/components/core/side-bar.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import Link from "next/link"; +import {FaArrowDown, FaPlus} from "react-icons/fa6"; +import {FaHome} from "react-icons/fa"; +import {FaRocket} from "react-icons/fa"; +import {Separator} from "~/components/ui/separator" +import {Button} from "~/components/ui/button"; +import {Collapsible, CollapsibleContent, CollapsibleTrigger} from "~/components/ui/collapsible"; + +export default function SideBar() { + return ( +
+
+
    +
  • + + Home +
  • +
  • + + Popular +
  • +
+ +
+ +
+ +
+ +
+

Channels

+ + + + +
+ + {/*TODO Top communities */} + Python + +
+
+
+ + +
+ + ) +} diff --git a/client/src/components/posts/posts.tsx b/client/src/components/posts/posts.tsx new file mode 100644 index 0000000..5008d5d --- /dev/null +++ b/client/src/components/posts/posts.tsx @@ -0,0 +1,5 @@ +export default function Posts() { + return (
+ {/* TODO Create a filter tab */} +
) +} \ No newline at end of file diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx new file mode 100644 index 0000000..d754ca0 --- /dev/null +++ b/client/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "~/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/client/src/components/ui/collapsible.tsx b/client/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..9fa4894 --- /dev/null +++ b/client/src/components/ui/collapsible.tsx @@ -0,0 +1,11 @@ +"use client" + +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +const Collapsible = CollapsiblePrimitive.Root + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/client/src/components/ui/form.tsx b/client/src/components/ui/form.tsx new file mode 100644 index 0000000..7b52676 --- /dev/null +++ b/client/src/components/ui/form.tsx @@ -0,0 +1,176 @@ +import * as React from "react" +import type * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + type ControllerProps, + type FieldPath, + type FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form" + +import { cn } from "~/lib/utils" +import { Label } from "~/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +