From 2b1b020cb639edfa3da6d16f45373bcd16516298 Mon Sep 17 00:00:00 2001 From: Michael Amponsah <48608838+nanordev@users.noreply.github.com> Date: Sun, 2 Feb 2025 16:08:31 +0000 Subject: [PATCH 1/2] Feature/election flow (#90) * rewire waitlist page * change waitlist destination * bringing the waitlist notification local * wip: light mode * minor light mode fixes * refactor: remove election-related pages and components * feat: add framer-motion for animations * feat: add hide-scrollbar utility and update animations in landing components * only deploy if branch is main * updating hardhat config * cleaning up the root folder * feat: add Logo and LightSource components, update Footer and Header to use Logo, and enhance social links with dark mode support * add supportedBy dark mode images * fix: update border color in WaitlistForm for better visibility * feat: create LandingLayout component to encapsulate Header and improve page structure * feat: implement Dashboard layout with Sidebar component and navigation links * feat: add Header component to Dashboard layout and create placeholder pages for election-related sections * feat: implement CreateElection page with tabbed navigation and form components * feat: add candidates slide to createElection flow * cleanup sponsors * feat: add mobile navigation * feat: implement theme switcher * update tailwind config * feat: add image upload functionality to candidate form * feat: add Voters component and update election creation page * complete Voters template * feat: add Summary component to election creation page --------- Co-authored-by: Michael Amponsah Co-authored-by: mikehammond <54583027+mikehammond@users.noreply.github.com> --- frontend/package.json | 3 + .../_components/Candidates.tsx | 47 +++++++ .../create-election/_components/Election.tsx | 56 ++++++++ .../create-election/_components/Summary.tsx | 61 +++++++++ .../create-election/_components/Voters.tsx | 47 +++++++ .../_components/inputs/ImagePicker.tsx | 75 +++++++++++ .../_components/inputs/InputWrapper.tsx | 22 ++++ .../_components/inputs/TextInput.tsx | 32 +++++ .../app/dashboard/create-election/page.tsx | 47 +++++++ .../src/app/dashboard/election-hub/page.tsx | 7 + frontend/src/app/dashboard/layout.tsx | 25 ++++ .../src/app/dashboard/my-elections/page.tsx | 7 + frontend/src/app/dashboard/page.tsx | 7 + frontend/src/app/dashboard/support/page.tsx | 7 + frontend/src/app/dashboard/vote/page.tsx | 7 + frontend/src/app/globals.css | 11 +- frontend/src/app/layout.tsx | 15 ++- frontend/src/components/dashboard/Header.jsx | 11 ++ frontend/src/components/dashboard/Sidebar.tsx | 120 ++++++++++++++++++ frontend/src/components/ui/accordion.tsx | 58 +++++++++ frontend/src/components/ui/date-picker.tsx | 48 +++++++ frontend/src/components/ui/tabs.tsx | 55 ++++++++ frontend/tailwind.config.ts | 92 ++------------ 23 files changed, 773 insertions(+), 87 deletions(-) create mode 100644 frontend/src/app/dashboard/create-election/_components/Candidates.tsx create mode 100644 frontend/src/app/dashboard/create-election/_components/Election.tsx create mode 100644 frontend/src/app/dashboard/create-election/_components/Summary.tsx create mode 100644 frontend/src/app/dashboard/create-election/_components/Voters.tsx create mode 100644 frontend/src/app/dashboard/create-election/_components/inputs/ImagePicker.tsx create mode 100644 frontend/src/app/dashboard/create-election/_components/inputs/InputWrapper.tsx create mode 100644 frontend/src/app/dashboard/create-election/_components/inputs/TextInput.tsx create mode 100644 frontend/src/app/dashboard/create-election/page.tsx create mode 100644 frontend/src/app/dashboard/election-hub/page.tsx create mode 100644 frontend/src/app/dashboard/layout.tsx create mode 100644 frontend/src/app/dashboard/my-elections/page.tsx create mode 100644 frontend/src/app/dashboard/page.tsx create mode 100644 frontend/src/app/dashboard/support/page.tsx create mode 100644 frontend/src/app/dashboard/vote/page.tsx create mode 100644 frontend/src/components/dashboard/Header.jsx create mode 100644 frontend/src/components/dashboard/Sidebar.tsx create mode 100644 frontend/src/components/ui/accordion.tsx create mode 100644 frontend/src/components/ui/date-picker.tsx create mode 100644 frontend/src/components/ui/tabs.tsx diff --git a/frontend/package.json b/frontend/package.json index 37fb589..982d51a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "@hookform/resolvers": "^3.9.1", "@magiclabs/wagmi-connector": "^2.1.0", "@mailchimp/mailchimp_marketing": "^3.0.80", + "@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.5", "@radix-ui/react-popover": "^1.1.2", @@ -19,6 +20,7 @@ "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.4", "@rainbow-me/rainbowkit": "^2.2.0", @@ -40,6 +42,7 @@ "react": "^18", "react-day-picker": "^8.10.1", "react-dom": "^18", + "react-dropzone": "^14.3.5", "react-hook-form": "^7.53.2", "sharp": "^0.33.5", "sonner": "^1.7.0", diff --git a/frontend/src/app/dashboard/create-election/_components/Candidates.tsx b/frontend/src/app/dashboard/create-election/_components/Candidates.tsx new file mode 100644 index 0000000..039512a --- /dev/null +++ b/frontend/src/app/dashboard/create-election/_components/Candidates.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import TextInput from "./inputs/TextInput"; +import ImagePicker from "./inputs/ImagePicker"; + +const Candidates = () => { + const arr = [1, 2, 3]; + return ( + + {arr.map((item) => ( + + ))} + +
+ + + +
+
+ ); +}; + +const CandidateForm = ({ i }: { i: number }) => { + return ( + + + Candidate {i} + + +
+ + + + +
+
+ ); +}; + +export default Candidates; \ No newline at end of file diff --git a/frontend/src/app/dashboard/create-election/_components/Election.tsx b/frontend/src/app/dashboard/create-election/_components/Election.tsx new file mode 100644 index 0000000..0da72d5 --- /dev/null +++ b/frontend/src/app/dashboard/create-election/_components/Election.tsx @@ -0,0 +1,56 @@ +import { DatePicker } from "@/components/ui/date-picker"; +import { RadioGroupItem, RadioGroup } from "@/components/ui/radio-group"; +import React from "react"; +import TextInput from "./inputs/TextInput"; +import InputWrapper from "./inputs/InputWrapper"; + +const Election = () => { + return ( +
+ + + +
+ +
+ + +
+
+ + + +
+ + +
+
+ + +
+
+
+ +
+ +
+ + ); +}; + +export default Election; diff --git a/frontend/src/app/dashboard/create-election/_components/Summary.tsx b/frontend/src/app/dashboard/create-election/_components/Summary.tsx new file mode 100644 index 0000000..6387a12 --- /dev/null +++ b/frontend/src/app/dashboard/create-election/_components/Summary.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import InputWrapper from "./inputs/InputWrapper"; + +const items = [ + { + title: "Election Title", + values: ["2024 SRC President - UG"], + }, + { + title: "Description", + values: [ + "This election is being held to elect a new SRC president for the University of Ghana", + ], + }, + { + title: "Election Period", + values: ["Aug 17, 2024 - Aug 29, 2024"], + }, + { + title: "Election Type", + values: ["Private"], + }, + { + title: "Candidates", + values: [ + "Joshua Mensah", + "Alisson Newton", + "James Hammond", + "Michael Brown", + ], + }, + { + title: "Voter Count", + values: ["24"], + }, +]; + +const Summary = () => { + return ( +
+ {items.map((item, index) => ( + +
+ {item.values.map((value, index) => ( +

+ {value} +

+ ))} +
+
+ ))} +
+ +
+
+ ); +}; + +export default Summary; diff --git a/frontend/src/app/dashboard/create-election/_components/Voters.tsx b/frontend/src/app/dashboard/create-election/_components/Voters.tsx new file mode 100644 index 0000000..c935519 --- /dev/null +++ b/frontend/src/app/dashboard/create-election/_components/Voters.tsx @@ -0,0 +1,47 @@ +import { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent, +} from "@/components/ui/accordion"; +import React from "react"; + +const Voters = () => { + return ( + + + + Enter Voters Manually + + +
+ + +
+ + +

O Voter(s)

+
+ +
+ +
+
+
+
+
+ ); +}; + +export default Voters; diff --git a/frontend/src/app/dashboard/create-election/_components/inputs/ImagePicker.tsx b/frontend/src/app/dashboard/create-election/_components/inputs/ImagePicker.tsx new file mode 100644 index 0000000..47f63ba --- /dev/null +++ b/frontend/src/app/dashboard/create-election/_components/inputs/ImagePicker.tsx @@ -0,0 +1,75 @@ +"use client"; +import React, { useCallback, useState } from "react"; +import InputWrapper from "./InputWrapper"; +import { ImageIcon } from "lucide-react"; +import { useDropzone } from "react-dropzone"; +import { cn } from "@/lib/utils"; +import Image from "next/image"; + +const ImagePicker = () => { + return ( + + + + ); +}; + +export default ImagePicker; + +const Dropzone = () => { + const [image, setImage] = useState<{ file: File; preview: string } | null>( + null + ); + + const onDrop = useCallback((acceptedFiles: File[]) => { + acceptedFiles.forEach((file) => { + const reader = new FileReader(); + + reader.onabort = () => console.log("file reading was aborted"); + reader.onerror = () => console.log("file reading has failed"); + reader.onload = () => { + // Do whatever you want with the file contents + const binaryStr = reader.result; + console.log(binaryStr); + setImage({ file, preview: URL.createObjectURL(file) }); + }; + reader.readAsArrayBuffer(file); + }); + }, []); + const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }); + return ( +
+ {image?.preview ? ( + Candidate image + ) : null} +
+ + + {isDragActive ? ( +

Drop here...

+ ) : ( + <> +

Drop your image here or browse

+

Support JPG, PNG, SVG

+ + )} +
+
+ ); +}; diff --git a/frontend/src/app/dashboard/create-election/_components/inputs/InputWrapper.tsx b/frontend/src/app/dashboard/create-election/_components/inputs/InputWrapper.tsx new file mode 100644 index 0000000..81cfaf9 --- /dev/null +++ b/frontend/src/app/dashboard/create-election/_components/inputs/InputWrapper.tsx @@ -0,0 +1,22 @@ +import React, { ReactNode } from "react"; + +const InputWrapper = ({ + name, + label, + children, +}: { + name: string; + label: string; + children: ReactNode; +}) => { + return ( +
+ + {children} +
+ ); +}; + +export default InputWrapper; diff --git a/frontend/src/app/dashboard/create-election/_components/inputs/TextInput.tsx b/frontend/src/app/dashboard/create-election/_components/inputs/TextInput.tsx new file mode 100644 index 0000000..8b1ae92 --- /dev/null +++ b/frontend/src/app/dashboard/create-election/_components/inputs/TextInput.tsx @@ -0,0 +1,32 @@ +import InputWrapper from "./InputWrapper"; + +type TextInputProps = { + type?: "text" | "email" | "password" | "date"; + name: string; + label: string; + placeholder: string; + required?: boolean; +}; + +const TextInput = ({ + type = "text", + name, + label, + placeholder, + required = true, +}: TextInputProps) => { + return ( + + + + ); +}; + +export default TextInput; diff --git a/frontend/src/app/dashboard/create-election/page.tsx b/frontend/src/app/dashboard/create-election/page.tsx new file mode 100644 index 0000000..3d31518 --- /dev/null +++ b/frontend/src/app/dashboard/create-election/page.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import Election from "./_components/Election"; +import Candidates from "./_components/Candidates"; +import Voters from "./_components/Voters"; +import Summary from "./_components/Summary"; + +const tabHeaders = [ + { value: "election", label: "Election" }, + { value: "candidates", label: "Candidates" }, + { value: "voters", label: "Voters" }, + { value: "summary", label: "Summary" }, +]; + +const tabsContent = [ + { value: "election", component: }, + { value: "candidates", component: }, + { value: "voters", component: }, + { value: "summary", component: }, +]; + +const CreateElection = () => { + return ( +
+ + + {tabHeaders.map(({ value, label }) => ( + + {label} + + ))} + + {tabsContent.map(({ value, component }) => ( + + {component} + + ))} + +
+ ); +}; + +export default CreateElection; diff --git a/frontend/src/app/dashboard/election-hub/page.tsx b/frontend/src/app/dashboard/election-hub/page.tsx new file mode 100644 index 0000000..a54c709 --- /dev/null +++ b/frontend/src/app/dashboard/election-hub/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const page = () => { + return
page
; +}; + +export default page; diff --git a/frontend/src/app/dashboard/layout.tsx b/frontend/src/app/dashboard/layout.tsx new file mode 100644 index 0000000..4288650 --- /dev/null +++ b/frontend/src/app/dashboard/layout.tsx @@ -0,0 +1,25 @@ +import Sidebar from "@/components/dashboard/Sidebar"; +import Header from "@/components/dashboard/Header"; +import { Metadata } from "next"; +import React from "react"; + +export const metadata: Metadata = { + title: "Truecast | Landing", + description: "BUIDL with Mowblox", +}; + +export default function DashboardLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+ +
+
+
{children}
+
+
+ ); +} diff --git a/frontend/src/app/dashboard/my-elections/page.tsx b/frontend/src/app/dashboard/my-elections/page.tsx new file mode 100644 index 0000000..a54c709 --- /dev/null +++ b/frontend/src/app/dashboard/my-elections/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const page = () => { + return
page
; +}; + +export default page; diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx new file mode 100644 index 0000000..04a49f2 --- /dev/null +++ b/frontend/src/app/dashboard/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const page = () => { + return
Index of dashboard
; +}; + +export default page; diff --git a/frontend/src/app/dashboard/support/page.tsx b/frontend/src/app/dashboard/support/page.tsx new file mode 100644 index 0000000..a54c709 --- /dev/null +++ b/frontend/src/app/dashboard/support/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const page = () => { + return
page
; +}; + +export default page; diff --git a/frontend/src/app/dashboard/vote/page.tsx b/frontend/src/app/dashboard/vote/page.tsx new file mode 100644 index 0000000..a54c709 --- /dev/null +++ b/frontend/src/app/dashboard/vote/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const page = () => { + return
page
; +}; + +export default page; diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index e4b7c16..b47ca8e 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -17,7 +17,7 @@ select:focus { transition: background-color 0.3s ease-in-out; background-color: #1b1a23; /* bg-dark */ } -@layer base { +/* @layer base { :root { --background: 0 0% 100%; --foreground: 240 10% 3.9%; @@ -35,7 +35,8 @@ select:focus { --accent-foreground: 219, 72%, 44%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; - --success: 120, 60%, 40% --success-foreground: 120, 60%, 90%; + --success: 120, 60%, 40%; + --success-foreground: 120, 60%, 90%; --border: 240 5.9% 90%; --input: 240 5.9% 90%; --ring: 240 10% 3.9%; @@ -88,15 +89,15 @@ select:focus { --sidebar-border: 240 3.7% 15.9%; --sidebar-ring: 217.2 91.2% 59.8%; } -} -@layer base { +} */ +/* @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; } -} +} */ .PopoverContent { animation-duration: 0.6s; diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 4ff5209..f0b0edc 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,5 +1,11 @@ import { Metadata } from "next"; -import { Space_Grotesk, Roboto_Flex, Afacad, Abel } from "next/font/google"; +import { + Space_Grotesk, + Roboto_Flex, + Afacad, + Abel, + Mulish, +} from "next/font/google"; import "./globals.css"; import Header from "@/components/Header/Header"; import WalletProvider from "@/components/WalletProvider"; @@ -23,8 +29,13 @@ const abel = Abel({ variable: "--font-abel", weight: "400", }); +const mulish = Mulish({ + subsets: ["latin"], + variable: "--font-abel", + weight: "400", +}); -const fonts = [afacad, space_grotesk, roboto_flex, abel]; +const fonts = [afacad, space_grotesk, roboto_flex, abel, mulish]; const fontClasses = fonts.map((font) => font.variable).join(" "); export const metadata: Metadata = { diff --git a/frontend/src/components/dashboard/Header.jsx b/frontend/src/components/dashboard/Header.jsx new file mode 100644 index 0000000..14ef8a3 --- /dev/null +++ b/frontend/src/components/dashboard/Header.jsx @@ -0,0 +1,11 @@ +import React from "react"; + +const Header = () => { + return ( +
+ Header +
+ ); +}; + +export default Header; diff --git a/frontend/src/components/dashboard/Sidebar.tsx b/frontend/src/components/dashboard/Sidebar.tsx new file mode 100644 index 0000000..f6cdd64 --- /dev/null +++ b/frontend/src/components/dashboard/Sidebar.tsx @@ -0,0 +1,120 @@ +"use client"; +import { ReactNode } from "react"; +import Logo from "../landing/Logo"; +import Link from "next/link"; +import { + TrophyIcon, + Home, + ThumbsUp, + Folder, + UserPlus2, + PhoneCall, +} from "lucide-react"; +import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; +type NavLink = { + title: string; + href: string; + icon: ReactNode; +}; + +type NavBlock = { + title: string; + links: NavLink[]; +}; + +const navBlocks: NavBlock[] = [ + { + title: "Dashboard", + links: [ + { + title: "Overview", + href: "/dashboard", + icon: , + }, + { + title: "Create election", + href: "/dashboard/create-election", + icon: , + }, + { + title: "Vote", + href: "/dashboard/vote", + icon: , + }, + { + title: "Election hub", + href: "/dashboard/election-hub", + icon: , + }, + { + title: "My elections", + href: "/dashboard/my-elections", + icon: , + }, + ], + }, + { + title: "Support", + links: [ + { + title: "Support", + href: "/dashboard/settings/support", + icon: , + }, + ], + }, +]; + +const Sidebar = () => { + return ( +
+ + +
+ {navBlocks.map((block, i) => ( + <> + + {i !== navBlocks.length - 1 ? ( +
+ ) : null} + + ))} +
+
+ ); +}; + +const NavBlock = ({ block }: { block: (typeof navBlocks)["0"] }) => { + return ( +
    + {block.links.map((link) => ( + + ))} +
+ ); +}; + +const NavLink = ({ link }: { link: NavLink }) => { + const currentPath = usePathname(); + const isActive = + link.href === "/dashboard" + ? currentPath === "/dashboard" + : currentPath.startsWith(link.href); + + return ( + + {link.icon} + {link.title} + {isActive} + + ); +}; + +export default Sidebar; diff --git a/frontend/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx new file mode 100644 index 0000000..24c788c --- /dev/null +++ b/frontend/src/components/ui/accordion.tsx @@ -0,0 +1,58 @@ +"use client" + +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/frontend/src/components/ui/date-picker.tsx b/frontend/src/components/ui/date-picker.tsx new file mode 100644 index 0000000..4d043e0 --- /dev/null +++ b/frontend/src/components/ui/date-picker.tsx @@ -0,0 +1,48 @@ +"use client"; + +import * as React from "react"; +import { format } from "date-fns"; +import { Calendar as CalendarIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Calendar } from "@/components/ui/calendar"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; + +export function DatePicker({ placeholder }: { placeholder?: string }) { + const [date, setDate] = React.useState(); + + return ( + + + + + + + + + ); +} diff --git a/frontend/src/components/ui/tabs.tsx b/frontend/src/components/ui/tabs.tsx new file mode 100644 index 0000000..26eb109 --- /dev/null +++ b/frontend/src/components/ui/tabs.tsx @@ -0,0 +1,55 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index 2ad285b..15cd180 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -1,3 +1,4 @@ +import { m } from "framer-motion"; import type { Config } from "tailwindcss"; const config: Config = { @@ -26,6 +27,7 @@ const config: Config = { "roboto-flex": ["var(--font-roboto-flex)"], afacad: ["var(--font-afacad)"], abel: ["var(--font-abel)"], + mulish: ["var(--font-mulish)"], }, extend: { screens: { @@ -43,94 +45,24 @@ const config: Config = { transform: "rotate(1turn)", }, }, + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, }, animation: { l24: "l24 1s linear infinite", l24Slow: "l24 2s linear infinite", l24Slower: "l24 4s linear infinite", - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - colors: { - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - chart: { - "1": "hsl(var(--chart-1))", - "2": "hsl(var(--chart-2))", - "3": "hsl(var(--chart-3))", - "4": "hsl(var(--chart-4))", - "5": "hsl(var(--chart-5))", - }, - sidebar: { - DEFAULT: "hsl(var(--sidebar-background))", - foreground: "hsl(var(--sidebar-foreground))", - primary: "hsl(var(--sidebar-primary))", - "primary-foreground": "hsl(var(--sidebar-primary-foreground))", - accent: "hsl(var(--sidebar-accent))", - "accent-foreground": "hsl(var(--sidebar-accent-foreground))", - border: "hsl(var(--sidebar-border))", - ring: "hsl(var(--sidebar-ring))", - }, + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", }, }, }, - plugins: [ - function addVariablesForColors({ - addBase, - theme, - }: { - addBase: (base: any) => void; - theme: (path: string) => any; - }) { - const colors = theme("colors"); - const cssVariables = Object.entries(colors).reduce( - (vars, [name, value]) => { - if (typeof value === "string") { - vars[`--${name}`] = value; - } - return vars; - }, - {} as Record - ); - - addBase({ ":root": cssVariables }); - }, - require("tailwindcss-animate"), - ], }; export default config; From 060a8cf329c963907bafaeb894703b8562b519ff Mon Sep 17 00:00:00 2001 From: Michael Amponsah Date: Tue, 4 Feb 2025 05:42:02 +0000 Subject: [PATCH 2/2] add support for programmatic tab navigation through search params --- frontend/src/app/dashboard/create-election/page.tsx | 11 +++++++++-- frontend/src/app/dashboard/layout.tsx | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/dashboard/create-election/page.tsx b/frontend/src/app/dashboard/create-election/page.tsx index 3d31518..8eed44c 100644 --- a/frontend/src/app/dashboard/create-election/page.tsx +++ b/frontend/src/app/dashboard/create-election/page.tsx @@ -1,9 +1,11 @@ +"use client"; import React from "react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import Election from "./_components/Election"; import Candidates from "./_components/Candidates"; import Voters from "./_components/Voters"; import Summary from "./_components/Summary"; +import { useSearchParams } from "next/navigation"; const tabHeaders = [ { value: "election", label: "Election" }, @@ -20,14 +22,19 @@ const tabsContent = [ ]; const CreateElection = () => { + const searchParams = useSearchParams(); + let tab = searchParams.get("tab"); + tab = tabHeaders.find(({ value }) => value === tab)?.value || "election"; + return (
- + {tabHeaders.map(({ value, label }) => ( {label} diff --git a/frontend/src/app/dashboard/layout.tsx b/frontend/src/app/dashboard/layout.tsx index 4288650..538443f 100644 --- a/frontend/src/app/dashboard/layout.tsx +++ b/frontend/src/app/dashboard/layout.tsx @@ -14,7 +14,7 @@ export default function DashboardLayout({ children: React.ReactNode; }>) { return ( -
+