From f98072c7cf5434a15e4475c4507d991e5a102dd5 Mon Sep 17 00:00:00 2001 From: Christopher Arraya Date: Sat, 16 Sep 2023 23:15:44 -0400 Subject: [PATCH] initial commit --- .env | 2 + .gitignore | 35 + CONTRIBUTE.md | 78 + README.md | 1 + app/auth/callback/route.ts | 21 + app/auth/sign-in/route.ts | 33 + app/auth/sign-out/route.ts | 17 + app/auth/sign-up/route.ts | 39 + app/globals.css | 76 + app/layout.tsx | 21 + app/page.tsx | 9 + app/workspace/[workspaceID]/layout.tsx | 12 + app/workspace/[workspaceID]/page.tsx | 7 + .../project/[projectID]/layout.tsx | 12 + .../project/[projectID]/page.tsx | 7 + app/workspace/[workspaceID]/settings/page.tsx | 7 + components.json | 16 + components/ui/avatar.tsx | 50 + components/ui/badge.tsx | 36 + components/ui/button.tsx | 57 + components/ui/card.tsx | 76 + components/ui/checkbox.tsx | 30 + components/ui/command.tsx | 155 + components/ui/dialog.tsx | 123 + components/ui/dropdown-menu.tsx | 205 ++ components/ui/form.tsx | 176 ++ components/ui/input.tsx | 25 + components/ui/label.tsx | 26 + components/ui/popover.tsx | 31 + components/ui/progress.tsx | 28 + components/ui/scroll-area.tsx | 48 + components/ui/select.tsx | 120 + components/ui/separator.tsx | 31 + components/ui/sheet.tsx | 144 + components/ui/table.tsx | 117 + components/ui/tabs.tsx | 55 + components/ui/textarea.tsx | 24 + components/ui/toast.tsx | 127 + components/ui/toaster.tsx | 35 + components/ui/tooltip.tsx | 30 + components/ui/use-toast.ts | 192 ++ lib/utils.ts | 6 + middleware.ts | 17 + next.config.js | 8 + package.json | 48 + pnpm-lock.yaml | 2502 +++++++++++++++++ postcss.config.js | 6 + tailwind.config.js | 76 + tsconfig.json | 28 + 49 files changed, 5025 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 CONTRIBUTE.md create mode 100644 README.md create mode 100644 app/auth/callback/route.ts create mode 100644 app/auth/sign-in/route.ts create mode 100644 app/auth/sign-out/route.ts create mode 100644 app/auth/sign-up/route.ts create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 app/workspace/[workspaceID]/layout.tsx create mode 100644 app/workspace/[workspaceID]/page.tsx create mode 100644 app/workspace/[workspaceID]/project/[projectID]/layout.tsx create mode 100644 app/workspace/[workspaceID]/project/[projectID]/page.tsx create mode 100644 app/workspace/[workspaceID]/settings/page.tsx create mode 100644 components.json create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/badge.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/checkbox.tsx create mode 100644 components/ui/command.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/popover.tsx create mode 100644 components/ui/progress.tsx create mode 100644 components/ui/scroll-area.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/separator.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/table.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/toast.tsx create mode 100644 components/ui/toaster.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 components/ui/use-toast.ts create mode 100644 lib/utils.ts create mode 100644 middleware.ts create mode 100644 next.config.js create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 postcss.config.js create mode 100644 tailwind.config.js create mode 100644 tsconfig.json diff --git a/.env b/.env new file mode 100644 index 0000000..8762039 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +NEXT_PUBLIC_SUPABASE_URL="https://avnbaefgykxyfnteplrn.supabase.co" +NEXT_PUBLIC_SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImF2bmJhZWZneWt4eWZudGVwbHJuIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTE1MzI3OTQsImV4cCI6MjAwNzEwODc5NH0.Mliz3pITqRLHNrC2jjBwX4LaS4XU9mvi2f09GBAgh0g" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8f322f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md new file mode 100644 index 0000000..fb0a041 --- /dev/null +++ b/CONTRIBUTE.md @@ -0,0 +1,78 @@ +# CONTRIBUTION GUIDE + +## Introduction + +Welcome to Skalara's contribution guide! This document will guide you through our workflow, ensuring consistent and quality contributions. Please read it carefully and refer back to it when in doubt. + +## Setup + +1. Clone the repository to your local machine. +2. Ensure you have all necessary tools and dependencies installed as outlined in the `README.md`. + +## Branching Strategy + +We use a simple branching strategy: + +- `main`: This branch contains production-ready code. +- `dev`: This is where new features and bug fixes are merged before they are tested and ready for production. +- `feature/your-feature-name`: For new features. +- `bugfix/your-bugfix-name`: For bug fixes. +- `hotfix/your-hotfix-name`: For hotfixes. + +### Steps: + +1. Always pull the latest changes from `dev` before creating a new branch. +2. Branch off from `dev` for features or bug fixes. +3. Name your branch appropriately based on the task at hand. + +## Development Workflow + +1. **Identify the Task**: + + - Start with a clear understanding of the task at hand. This could be a new feature or a bug fix. + - An issue should be created on GitHub to represent this task. + +2. **Branching**: + + - Pull the latest version of `dev`. + - Create a new branch specific to your task. + +3. **Coding**: + + - Develop your feature or fix the bug in your branch. + - Commit often with clear, concise commit messages. + +4. **Testing**: + + - Test your changes thoroughly in your local environment. + - Ensure it adheres to the acceptance criteria mentioned in the task. + +5. **Pushing Changes**: + + - Push your branch and changes to the GitHub repository. + +6. **Pull Request (PR)**: + + - Create a PR from your branch to `dev`. + - Provide a detailed PR message, explaining your changes and any instructions for testing. + - Request reviews from appropriate team members. + +7. **Code Review**: + + - Address any feedback or changes requested by reviewers. + - Make required changes and push updates to the same branch. + +8. **Merging**: + - Once the PR is approved, it will be merged into `dev`. + - Delete the feature/bugfix branch after merging, to keep the repository clean. + +## Best Practices + +1. **Commit Messages**: Write meaningful commit messages that provide a clear understanding of the changes. Reference this [semantic commit guide](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716) for more information. +2. **Atomic Commits**: Each commit should represent a single logical change. Avoid bundling unrelated changes. +3. **Pull Often**: Regularly pull from `dev` to avoid major merge conflicts. +4. **Communication**: If stuck or in doubt, communicate. Use GitHub comments, or other communication tools adopted by the team. + +## Conclusion + +Your adherence to this guide ensures that our codebase remains clean, understandable, and easy to navigate. Happy coding! diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ba5004 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Skalara Web Application diff --git a/app/auth/callback/route.ts b/app/auth/callback/route.ts new file mode 100644 index 0000000..6730992 --- /dev/null +++ b/app/auth/callback/route.ts @@ -0,0 +1,21 @@ +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +export const dynamic = 'force-dynamic' + +export async function GET(request: Request) { + // The `/auth/callback` route is required for the server-side auth flow implemented + // by the Auth Helpers package. It exchanges an auth code for the user's session. + // https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-sign-in-with-code-exchange + const requestUrl = new URL(request.url) + const code = requestUrl.searchParams.get('code') + + if (code) { + const supabase = createRouteHandlerClient({ cookies }) + await supabase.auth.exchangeCodeForSession(code) + } + + // URL to redirect to after sign in process completes + return NextResponse.redirect(requestUrl.origin) +} diff --git a/app/auth/sign-in/route.ts b/app/auth/sign-in/route.ts new file mode 100644 index 0000000..accb946 --- /dev/null +++ b/app/auth/sign-in/route.ts @@ -0,0 +1,33 @@ +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +export const dynamic = 'force-dynamic' + +export async function POST(request: Request) { + const requestUrl = new URL(request.url) + const formData = await request.formData() + const email = String(formData.get('email')) + const password = String(formData.get('password')) + const supabase = createRouteHandlerClient({ cookies }) + + const { error } = await supabase.auth.signInWithPassword({ + email, + password, + }) + + if (error) { + return NextResponse.redirect( + `${requestUrl.origin}/login?error=Could not authenticate user`, + { + // a 301 status is required to redirect from a POST to a GET route + status: 301, + } + ) + } + + return NextResponse.redirect(requestUrl.origin, { + // a 301 status is required to redirect from a POST to a GET route + status: 301, + }) +} diff --git a/app/auth/sign-out/route.ts b/app/auth/sign-out/route.ts new file mode 100644 index 0000000..658e5c7 --- /dev/null +++ b/app/auth/sign-out/route.ts @@ -0,0 +1,17 @@ +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +export const dynamic = 'force-dynamic' + +export async function POST(request: Request) { + const requestUrl = new URL(request.url) + const supabase = createRouteHandlerClient({ cookies }) + + await supabase.auth.signOut() + + return NextResponse.redirect(`${requestUrl.origin}/login`, { + // a 301 status is required to redirect from a POST to a GET route + status: 301, + }) +} diff --git a/app/auth/sign-up/route.ts b/app/auth/sign-up/route.ts new file mode 100644 index 0000000..f7d2aef --- /dev/null +++ b/app/auth/sign-up/route.ts @@ -0,0 +1,39 @@ +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +export const dynamic = 'force-dynamic' + +export async function POST(request: Request) { + const requestUrl = new URL(request.url) + const formData = await request.formData() + const email = String(formData.get('email')) + const password = String(formData.get('password')) + const supabase = createRouteHandlerClient({ cookies }) + + const { error } = await supabase.auth.signUp({ + email, + password, + options: { + emailRedirectTo: `${requestUrl.origin}/auth/callback`, + }, + }) + + if (error) { + return NextResponse.redirect( + `${requestUrl.origin}/login?error=Could not authenticate user`, + { + // a 301 status is required to redirect from a POST to a GET route + status: 301, + } + ) + } + + return NextResponse.redirect( + `${requestUrl.origin}/login?message=Check email to continue sign in process`, + { + // a 301 status is required to redirect from a POST to a GET route + status: 301, + } + ) +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..8abdb15 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,76 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..523dcce --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,21 @@ +import "./globals.css"; + +export const metadata = { + title: "Skalara", + description: + "Automated project management for tech teams and indie developers.", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + +
{children}
+ + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..68c17e0 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,9 @@ +export const dynamic = "force-dynamic"; + +export default async function Index() { + return ( +
+

Hello, World!

+
+ ); +} diff --git a/app/workspace/[workspaceID]/layout.tsx b/app/workspace/[workspaceID]/layout.tsx new file mode 100644 index 0000000..46cde24 --- /dev/null +++ b/app/workspace/[workspaceID]/layout.tsx @@ -0,0 +1,12 @@ +export default function WorkspaceLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+

Workspace Layout

+ {children} +
+ ); +} diff --git a/app/workspace/[workspaceID]/page.tsx b/app/workspace/[workspaceID]/page.tsx new file mode 100644 index 0000000..878957d --- /dev/null +++ b/app/workspace/[workspaceID]/page.tsx @@ -0,0 +1,7 @@ +export default function Workspace() { + return ( +
+

Workspace Page

+
+ ); +} diff --git a/app/workspace/[workspaceID]/project/[projectID]/layout.tsx b/app/workspace/[workspaceID]/project/[projectID]/layout.tsx new file mode 100644 index 0000000..9de2955 --- /dev/null +++ b/app/workspace/[workspaceID]/project/[projectID]/layout.tsx @@ -0,0 +1,12 @@ +export default function ProjectLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+

Project Layout

+ {children} +
+ ); +} diff --git a/app/workspace/[workspaceID]/project/[projectID]/page.tsx b/app/workspace/[workspaceID]/project/[projectID]/page.tsx new file mode 100644 index 0000000..4dacf22 --- /dev/null +++ b/app/workspace/[workspaceID]/project/[projectID]/page.tsx @@ -0,0 +1,7 @@ +export default function Project() { + return ( +
+

Project Page

+
+ ); +} diff --git a/app/workspace/[workspaceID]/settings/page.tsx b/app/workspace/[workspaceID]/settings/page.tsx new file mode 100644 index 0000000..25ba9e9 --- /dev/null +++ b/app/workspace/[workspaceID]/settings/page.tsx @@ -0,0 +1,7 @@ +export default function WorkspaceSettings() { + return ( +
+

Workspace Settings

+
+ ); +} diff --git a/components.json b/components.json new file mode 100644 index 0000000..7681c2f --- /dev/null +++ b/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "app/globals.css", + "baseColor": "slate", + "cssVariables": true + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} \ No newline at end of file diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..51e507b --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..e87d62b --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..4ecf369 --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,57 @@ +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 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + 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/components/ui/card.tsx b/components/ui/card.tsx new file mode 100644 index 0000000..77e9fb7 --- /dev/null +++ b/components/ui/card.tsx @@ -0,0 +1,76 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx new file mode 100644 index 0000000..7d2b3c3 --- /dev/null +++ b/components/ui/checkbox.tsx @@ -0,0 +1,30 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { CheckIcon } from "@radix-ui/react-icons" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/components/ui/command.tsx b/components/ui/command.tsx new file mode 100644 index 0000000..6f4a5eb --- /dev/null +++ b/components/ui/command.tsx @@ -0,0 +1,155 @@ +"use client" + +import * as React from "react" +import { DialogProps } from "@radix-ui/react-dialog" +import { MagnifyingGlassIcon } from "@radix-ui/react-icons" +import { Command as CommandPrimitive } from "cmdk" + +import { cn } from "@/lib/utils" +import { Dialog, DialogContent } from "@/components/ui/dialog" + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Command.displayName = CommandPrimitive.displayName + +interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ) +} + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)) + +CommandInput.displayName = CommandPrimitive.Input.displayName + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandList.displayName = CommandPrimitive.List.displayName + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)) + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandGroup.displayName = CommandPrimitive.Group.displayName + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +CommandSeparator.displayName = CommandPrimitive.Separator.displayName + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +CommandItem.displayName = CommandPrimitive.Item.displayName + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +CommandShortcut.displayName = "CommandShortcut" + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx new file mode 100644 index 0000000..8cbe0d4 --- /dev/null +++ b/components/ui/dialog.tsx @@ -0,0 +1,123 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { Cross2Icon } from "@radix-ui/react-icons" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = ({ + className, + ...props +}: DialogPrimitive.DialogPortalProps) => ( + +) +DialogPortal.displayName = DialogPrimitive.Portal.displayName + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..242b07a --- /dev/null +++ b/components/ui/dropdown-menu.tsx @@ -0,0 +1,205 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { + CheckIcon, + ChevronRightIcon, + DotFilledIcon, +} from "@radix-ui/react-icons" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/components/ui/form.tsx b/components/ui/form.tsx new file mode 100644 index 0000000..f6afdaf --- /dev/null +++ b/components/ui/form.tsx @@ -0,0 +1,176 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + ControllerProps, + FieldPath, + 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 ( +