From 6ef481a64b2ae56ba12c620fea578e716ef5608c Mon Sep 17 00:00:00 2001 From: jonrxu Date: Fri, 23 Feb 2024 16:39:51 -0500 Subject: [PATCH 01/37] new commit --- frontend/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/README.md b/frontend/README.md index 08868d1d..dab37cf0 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -20,3 +20,5 @@ A web-based portal for organizations to reach Penn Mobile users. you should be able to see the site at `localhost:3000`! 1. The backend should be running at `localhost:8000`. We proxy all requests from localhost:3000/api to localhost:8000/api (in `frontend/server.js`), so you can make requests to the backend from the frontend. If you want to directly see what the request should return, you can go to `localhost:8000/api/...` to see the response. 1. There's also some jankiness with login since we make requests to clubs and accounts -- for login to work in your dev environment, you'll need to go to `localhost:8000/admin` and add a valid access token (make sure the expiration date is some day in the far future). + +kek \ No newline at end of file From f6a1a86152d730ce38358844773225a4842da36c Mon Sep 17 00:00:00 2001 From: jonrxu Date: Fri, 23 Feb 2024 17:41:55 -0500 Subject: [PATCH 02/37] Add frontend for sublet portal --- frontend-sublet/.eslintrc.json | 3 + frontend-sublet/.gitignore | 36 + frontend-sublet/README.md | 36 + frontend-sublet/app/create/page.tsx | 9 + frontend-sublet/app/dashboard/page.tsx | 152 + frontend-sublet/app/favicon.ico | Bin 0 -> 28842 bytes frontend-sublet/app/globals.css | 76 + frontend-sublet/app/layout.tsx | 26 + frontend-sublet/app/page.tsx | 19 + frontend-sublet/components.json | 17 + .../components/custom/subletterlisting.tsx | 134 + frontend-sublet/components/ui/avatar.tsx | 50 + frontend-sublet/components/ui/badge.tsx | 36 + frontend-sublet/components/ui/button.tsx | 57 + frontend-sublet/components/ui/calendar.tsx | 72 + frontend-sublet/components/ui/card.tsx | 76 + frontend-sublet/components/ui/carousel.tsx | 262 + frontend-sublet/components/ui/checkbox.tsx | 30 + frontend-sublet/components/ui/drawer.tsx | 118 + .../components/ui/dropdown-menu.tsx | 205 + frontend-sublet/components/ui/input.tsx | 25 + frontend-sublet/components/ui/label.tsx | 26 + frontend-sublet/components/ui/pagination.tsx | 121 + frontend-sublet/components/ui/radio-group.tsx | 44 + frontend-sublet/components/ui/select.tsx | 164 + frontend-sublet/components/ui/sheet.tsx | 140 + frontend-sublet/components/ui/skeleton.tsx | 15 + frontend-sublet/components/ui/slider.tsx | 28 + frontend-sublet/components/ui/switch.tsx | 29 + frontend-sublet/components/ui/tabs.tsx | 55 + frontend-sublet/components/ui/textarea.tsx | 24 + frontend-sublet/lib/utils.ts | 6 + frontend-sublet/next.config.mjs | 4 + frontend-sublet/package-lock.json | 5876 +++++++++++++++++ frontend-sublet/package.json | 47 + frontend-sublet/postcss.config.js | 6 + frontend-sublet/public/desk-graphic.svg | 88 + frontend-sublet/public/empty-clubs.svg | 11 + frontend-sublet/public/favicon.ico | Bin 0 -> 28842 bytes frontend-sublet/public/hamco.jpeg | Bin 0 -> 112009 bytes frontend-sublet/public/icons/analytics.svg | 3 + frontend-sublet/public/icons/check-circle.svg | 11 + frontend-sublet/public/icons/dashboard.svg | 5 + frontend-sublet/public/icons/edit.svg | 11 + frontend-sublet/public/icons/error.svg | 4 + frontend-sublet/public/icons/live.svg | 11 + frontend-sublet/public/icons/settings.svg | 11 + frontend-sublet/public/icons/tool.svg | 3 + frontend-sublet/public/icons/tutorials.svg | 4 + frontend-sublet/public/icons/x.svg | 4 + frontend-sublet/public/landing-page.jpg | Bin 0 -> 3170339 bytes frontend-sublet/public/landing-page.svg | 335 + frontend-sublet/public/penn-mobile.svg | 1 + frontend-sublet/public/phone_header.png | Bin 0 -> 156944 bytes frontend-sublet/tailwind.config.ts | 80 + frontend-sublet/tsconfig.json | 26 + 56 files changed, 8632 insertions(+) create mode 100644 frontend-sublet/.eslintrc.json create mode 100644 frontend-sublet/.gitignore create mode 100644 frontend-sublet/README.md create mode 100644 frontend-sublet/app/create/page.tsx create mode 100644 frontend-sublet/app/dashboard/page.tsx create mode 100644 frontend-sublet/app/favicon.ico create mode 100644 frontend-sublet/app/globals.css create mode 100644 frontend-sublet/app/layout.tsx create mode 100644 frontend-sublet/app/page.tsx create mode 100644 frontend-sublet/components.json create mode 100644 frontend-sublet/components/custom/subletterlisting.tsx create mode 100644 frontend-sublet/components/ui/avatar.tsx create mode 100644 frontend-sublet/components/ui/badge.tsx create mode 100644 frontend-sublet/components/ui/button.tsx create mode 100644 frontend-sublet/components/ui/calendar.tsx create mode 100644 frontend-sublet/components/ui/card.tsx create mode 100644 frontend-sublet/components/ui/carousel.tsx create mode 100644 frontend-sublet/components/ui/checkbox.tsx create mode 100644 frontend-sublet/components/ui/drawer.tsx create mode 100644 frontend-sublet/components/ui/dropdown-menu.tsx create mode 100644 frontend-sublet/components/ui/input.tsx create mode 100644 frontend-sublet/components/ui/label.tsx create mode 100644 frontend-sublet/components/ui/pagination.tsx create mode 100644 frontend-sublet/components/ui/radio-group.tsx create mode 100644 frontend-sublet/components/ui/select.tsx create mode 100644 frontend-sublet/components/ui/sheet.tsx create mode 100644 frontend-sublet/components/ui/skeleton.tsx create mode 100644 frontend-sublet/components/ui/slider.tsx create mode 100644 frontend-sublet/components/ui/switch.tsx create mode 100644 frontend-sublet/components/ui/tabs.tsx create mode 100644 frontend-sublet/components/ui/textarea.tsx create mode 100644 frontend-sublet/lib/utils.ts create mode 100644 frontend-sublet/next.config.mjs create mode 100644 frontend-sublet/package-lock.json create mode 100644 frontend-sublet/package.json create mode 100644 frontend-sublet/postcss.config.js create mode 100644 frontend-sublet/public/desk-graphic.svg create mode 100644 frontend-sublet/public/empty-clubs.svg create mode 100644 frontend-sublet/public/favicon.ico create mode 100644 frontend-sublet/public/hamco.jpeg create mode 100644 frontend-sublet/public/icons/analytics.svg create mode 100644 frontend-sublet/public/icons/check-circle.svg create mode 100644 frontend-sublet/public/icons/dashboard.svg create mode 100644 frontend-sublet/public/icons/edit.svg create mode 100644 frontend-sublet/public/icons/error.svg create mode 100644 frontend-sublet/public/icons/live.svg create mode 100644 frontend-sublet/public/icons/settings.svg create mode 100644 frontend-sublet/public/icons/tool.svg create mode 100644 frontend-sublet/public/icons/tutorials.svg create mode 100644 frontend-sublet/public/icons/x.svg create mode 100644 frontend-sublet/public/landing-page.jpg create mode 100644 frontend-sublet/public/landing-page.svg create mode 100644 frontend-sublet/public/penn-mobile.svg create mode 100644 frontend-sublet/public/phone_header.png create mode 100644 frontend-sublet/tailwind.config.ts create mode 100644 frontend-sublet/tsconfig.json diff --git a/frontend-sublet/.eslintrc.json b/frontend-sublet/.eslintrc.json new file mode 100644 index 00000000..bffb357a --- /dev/null +++ b/frontend-sublet/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/frontend-sublet/.gitignore b/frontend-sublet/.gitignore new file mode 100644 index 00000000..fd3dbb57 --- /dev/null +++ b/frontend-sublet/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# 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/frontend-sublet/README.md b/frontend-sublet/README.md new file mode 100644 index 00000000..c4033664 --- /dev/null +++ b/frontend-sublet/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/frontend-sublet/app/create/page.tsx b/frontend-sublet/app/create/page.tsx new file mode 100644 index 00000000..f516f3f0 --- /dev/null +++ b/frontend-sublet/app/create/page.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +const CreateListing = () => { + return ( +
CreateListing
+ ) +} + +export default CreateListing \ No newline at end of file diff --git a/frontend-sublet/app/dashboard/page.tsx b/frontend-sublet/app/dashboard/page.tsx new file mode 100644 index 00000000..1ffbd0c8 --- /dev/null +++ b/frontend-sublet/app/dashboard/page.tsx @@ -0,0 +1,152 @@ +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; + +import { PlusIcon, DotsHorizontalIcon as Dots } from "@radix-ui/react-icons"; + +import { BellIcon, CheckIcon } from "@radix-ui/react-icons"; + +import Image from "next/image"; + +import { cn } from "@/lib/utils"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Switch } from "@/components/ui/switch"; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +import SubletterListing from "@/components/custom/subletterlisting"; + +{ + /* Split inputted property listings into a Posted and Drafts list, and then show it based on which is selected.*/ +} +const property_listings = [ + { + title: "Radian 2bed/4ba", + image: "...", + pending: true, + startDate: "...", + endDate: "...", + }, + { + title: "Chestnut 2bed/2ba", + image: "...", + pending: true, + description: "1 hour ago", + }, + { + title: "Hamco 3bed/2ba", + description: "2 hours ago", + }, +]; + +const Dashboard = () => { + return ( +
+ +
+ + Posted + Drafts + + + + + + + + Edit profile + + Make changes to your profile here. Click save when you're + done. + + +
+
+ + +
+
+ + +
+
+ + + + + +
+
+
+ +
+

+ Dashboard +

+
+
+ + + +
+
+
+
+ +
+

+ Dashboard +

+
+ + + +
+
+
+
+
+ ); +}; + +export default Dashboard; diff --git a/frontend-sublet/app/favicon.ico b/frontend-sublet/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..454da996d868863f66ac45e89894ff1bb9db5297 GIT binary patch literal 28842 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 literal 0 HcmV?d00001 diff --git a/frontend-sublet/app/globals.css b/frontend-sublet/app/globals.css new file mode 100644 index 00000000..0b46ea13 --- /dev/null +++ b/frontend-sublet/app/globals.css @@ -0,0 +1,76 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 10% 3.9%; + + --radius: 0.5rem; + } + + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/frontend-sublet/app/layout.tsx b/frontend-sublet/app/layout.tsx new file mode 100644 index 00000000..b4f8b6a0 --- /dev/null +++ b/frontend-sublet/app/layout.tsx @@ -0,0 +1,26 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Sublet@Portal", + description: "Welcome to Sublet@Portal. The best place to sublet your room to other students!", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + +
+ {children} +
+ + + ); +} diff --git a/frontend-sublet/app/page.tsx b/frontend-sublet/app/page.tsx new file mode 100644 index 00000000..ceae06f7 --- /dev/null +++ b/frontend-sublet/app/page.tsx @@ -0,0 +1,19 @@ +import Image from "next/image"; + +export default function Home() { + return ( +
+

+ + Welcome to Sublet@Portal + +

+
+
+ Sublet@Portal Logo +
+

The best place to sublet your room to other students!

+
+
+ ); +} diff --git a/frontend-sublet/components.json b/frontend-sublet/components.json new file mode 100644 index 00000000..58b812d0 --- /dev/null +++ b/frontend-sublet/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} \ No newline at end of file diff --git a/frontend-sublet/components/custom/subletterlisting.tsx b/frontend-sublet/components/custom/subletterlisting.tsx new file mode 100644 index 00000000..5558cc3d --- /dev/null +++ b/frontend-sublet/components/custom/subletterlisting.tsx @@ -0,0 +1,134 @@ +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import Image from "next/image"; +import { Button } from "@/components/ui/button"; +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; + +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; + +import { CheckIcon, DotsHorizontalIcon as Dots } from "@radix-ui/react-icons"; + +const SubletterListing = () => { + return ( +
+ + +
+ Property Title + + + + + + + Delete + + + +
+
+ +
+ Hamco pic +
+ +
+
+
+ + +
+

Pending

+
+

Jun 27 - Jul 24

+
+
+ +
+ + + + + + + + + Edit profile + + Make changes to your profile here. Click save when you're + done. + + +
+
+ + +
+
+ + +
+
+ + + + + +
+
+
+
+
+
+ ); +}; + +export default SubletterListing; diff --git a/frontend-sublet/components/ui/avatar.tsx b/frontend-sublet/components/ui/avatar.tsx new file mode 100644 index 00000000..51e507ba --- /dev/null +++ b/frontend-sublet/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/frontend-sublet/components/ui/badge.tsx b/frontend-sublet/components/ui/badge.tsx new file mode 100644 index 00000000..e87d62bf --- /dev/null +++ b/frontend-sublet/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/frontend-sublet/components/ui/button.tsx b/frontend-sublet/components/ui/button.tsx new file mode 100644 index 00000000..0270f644 --- /dev/null +++ b/frontend-sublet/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 whitespace-nowrap 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-background 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/frontend-sublet/components/ui/calendar.tsx b/frontend-sublet/components/ui/calendar.tsx new file mode 100644 index 00000000..018fe264 --- /dev/null +++ b/frontend-sublet/components/ui/calendar.tsx @@ -0,0 +1,72 @@ +"use client" + +import * as React from "react" +import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons" +import { DayPicker } from "react-day-picker" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +export type CalendarProps = React.ComponentProps + +function Calendar({ + className, + classNames, + showOutsideDays = true, + ...props +}: CalendarProps) { + return ( + .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md" + : "[&:has([aria-selected])]:rounded-md" + ), + day: cn( + buttonVariants({ variant: "ghost" }), + "h-8 w-8 p-0 font-normal aria-selected:opacity-100" + ), + day_range_start: "day-range-start", + day_range_end: "day-range-end", + day_selected: + "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground", + day_today: "bg-accent text-accent-foreground", + day_outside: + "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30", + day_disabled: "text-muted-foreground opacity-50", + day_range_middle: + "aria-selected:bg-accent aria-selected:text-accent-foreground", + day_hidden: "invisible", + ...classNames, + }} + components={{ + IconLeft: ({ ...props }) => , + IconRight: ({ ...props }) => , + }} + {...props} + /> + ) +} +Calendar.displayName = "Calendar" + +export { Calendar } diff --git a/frontend-sublet/components/ui/card.tsx b/frontend-sublet/components/ui/card.tsx new file mode 100644 index 00000000..77e9fb78 --- /dev/null +++ b/frontend-sublet/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/frontend-sublet/components/ui/carousel.tsx b/frontend-sublet/components/ui/carousel.tsx new file mode 100644 index 00000000..f9b68404 --- /dev/null +++ b/frontend-sublet/components/ui/carousel.tsx @@ -0,0 +1,262 @@ +"use client" + +import * as React from "react" +import { ArrowLeftIcon, ArrowRightIcon } from "@radix-ui/react-icons" +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from "embla-carousel-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +type CarouselApi = UseEmblaCarouselType[1] +type UseCarouselParameters = Parameters +type CarouselOptions = UseCarouselParameters[0] +type CarouselPlugin = UseCarouselParameters[1] + +type CarouselProps = { + opts?: CarouselOptions + plugins?: CarouselPlugin + orientation?: "horizontal" | "vertical" + setApi?: (api: CarouselApi) => void +} + +type CarouselContextProps = { + carouselRef: ReturnType[0] + api: ReturnType[1] + scrollPrev: () => void + scrollNext: () => void + canScrollPrev: boolean + canScrollNext: boolean +} & CarouselProps + +const CarouselContext = React.createContext(null) + +function useCarousel() { + const context = React.useContext(CarouselContext) + + if (!context) { + throw new Error("useCarousel must be used within a ") + } + + return context +} + +const Carousel = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & CarouselProps +>( + ( + { + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props + }, + ref + ) => { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins + ) + const [canScrollPrev, setCanScrollPrev] = React.useState(false) + const [canScrollNext, setCanScrollNext] = React.useState(false) + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return + } + + setCanScrollPrev(api.canScrollPrev()) + setCanScrollNext(api.canScrollNext()) + }, []) + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev() + }, [api]) + + const scrollNext = React.useCallback(() => { + api?.scrollNext() + }, [api]) + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault() + scrollPrev() + } else if (event.key === "ArrowRight") { + event.preventDefault() + scrollNext() + } + }, + [scrollPrev, scrollNext] + ) + + React.useEffect(() => { + if (!api || !setApi) { + return + } + + setApi(api) + }, [api, setApi]) + + React.useEffect(() => { + if (!api) { + return + } + + onSelect(api) + api.on("reInit", onSelect) + api.on("select", onSelect) + + return () => { + api?.off("select", onSelect) + } + }, [api, onSelect]) + + return ( + +
+ {children} +
+
+ ) + } +) +Carousel.displayName = "Carousel" + +const CarouselContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { carouselRef, orientation } = useCarousel() + + return ( +
+
+
+ ) +}) +CarouselContent.displayName = "CarouselContent" + +const CarouselItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const { orientation } = useCarousel() + + return ( +
+ ) +}) +CarouselItem.displayName = "CarouselItem" + +const CarouselPrevious = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollPrev, canScrollPrev } = useCarousel() + + return ( + + ) +}) +CarouselPrevious.displayName = "CarouselPrevious" + +const CarouselNext = React.forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ className, variant = "outline", size = "icon", ...props }, ref) => { + const { orientation, scrollNext, canScrollNext } = useCarousel() + + return ( + + ) +}) +CarouselNext.displayName = "CarouselNext" + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +} diff --git a/frontend-sublet/components/ui/checkbox.tsx b/frontend-sublet/components/ui/checkbox.tsx new file mode 100644 index 00000000..7d2b3c3b --- /dev/null +++ b/frontend-sublet/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/frontend-sublet/components/ui/drawer.tsx b/frontend-sublet/components/ui/drawer.tsx new file mode 100644 index 00000000..6a0ef53d --- /dev/null +++ b/frontend-sublet/components/ui/drawer.tsx @@ -0,0 +1,118 @@ +"use client" + +import * as React from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +const Drawer = ({ + shouldScaleBackground = true, + ...props +}: React.ComponentProps) => ( + +) +Drawer.displayName = "Drawer" + +const DrawerTrigger = DrawerPrimitive.Trigger + +const DrawerPortal = DrawerPrimitive.Portal + +const DrawerClose = DrawerPrimitive.Close + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)) +DrawerContent.displayName = "DrawerContent" + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerHeader.displayName = "DrawerHeader" + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerFooter.displayName = "DrawerFooter" + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerTitle.displayName = DrawerPrimitive.Title.displayName + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerDescription.displayName = DrawerPrimitive.Description.displayName + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/frontend-sublet/components/ui/dropdown-menu.tsx b/frontend-sublet/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..242b07a6 --- /dev/null +++ b/frontend-sublet/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/frontend-sublet/components/ui/input.tsx b/frontend-sublet/components/ui/input.tsx new file mode 100644 index 00000000..a92b8e0e --- /dev/null +++ b/frontend-sublet/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/frontend-sublet/components/ui/label.tsx b/frontend-sublet/components/ui/label.tsx new file mode 100644 index 00000000..53418217 --- /dev/null +++ b/frontend-sublet/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/frontend-sublet/components/ui/pagination.tsx b/frontend-sublet/components/ui/pagination.tsx new file mode 100644 index 00000000..7715f05a --- /dev/null +++ b/frontend-sublet/components/ui/pagination.tsx @@ -0,0 +1,121 @@ +import * as React from "react" +import { + ChevronLeftIcon, + ChevronRightIcon, + DotsHorizontalIcon, +} from "@radix-ui/react-icons" + +import { cn } from "@/lib/utils" +import { ButtonProps, buttonVariants } from "@/components/ui/button" + +const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( +