Skip to content

Commit

Permalink
Add initial UI for voting
Browse files Browse the repository at this point in the history
  • Loading branch information
sembrestels committed Sep 8, 2024
1 parent c7a356c commit 946d98c
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 110 deletions.
22 changes: 0 additions & 22 deletions apps/web/app/layout.tsx

This file was deleted.

9 changes: 0 additions & 9 deletions apps/web/app/page.tsx

This file was deleted.

30 changes: 30 additions & 0 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import "@repo/ui/globals.css";
import type { Metadata } from "next";
import { Newsreader } from "next/font/google";
import Header from "../components/Header";
import Footer from "../components/Footer";

const newsreader = Newsreader({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "CouncilHaus",
description: "Democratically allocate a budget across projects",
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}): JSX.Element {
return (
<html lang="en">
<body className={newsreader.className}>
<div className='flex flex-col min-h-screen'>
<Header />
<main className='flex-grow px-4 container max-w-3xl mx-auto'>{children}</main>
<Footer />
</div>
</body>
</html>
);
}
16 changes: 16 additions & 0 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Button } from "@repo/ui/components/ui/button";
import VotingCard from "../components/VotingCard";

async function getProjects() {
return ["Project 1", "Project 2", "Project 3", "Project 4"];
}

export default async function Page() {
const projects = await getProjects();
return (
<main>
<h1 className="text-4xl font-bold mb-4 text-accent">Frontier Guild</h1>
<VotingCard className="max-w-lg mx-auto" projects={projects} maxVotedProjects={3} />
</main>
);
}
12 changes: 12 additions & 0 deletions apps/web/src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";
import { Button } from "@repo/ui/components/ui/button";
import Link from "next/link";

export default function Footer() {
return <div className="bg-gray-800 py-4">
<div className="container max-w-4xl mx-auto flex justify-between items-center">
<p className="text-md text-gray-400">Built with ❤️ by <Button variant="link" className="text-gray-400 px-0 text-lg" asChild><Link href="https://blossom.software" target="_blank">Blossom Labs</Link></Button></p>
<p className="text-md text-gray-400">⚡️ Powered by <Button variant="link" className="text-gray-400 px-0 text-lg" asChild><Link href="https://superfluid.finance" target="_blank">Superfluid</Link></Button></p>
</div>
</div>;
}
11 changes: 11 additions & 0 deletions apps/web/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";
import { Button } from "@repo/ui/components/ui/button";

export default function Header() {
return <div className="bg-gray-900 py-4 mb-6">
<div className="container max-w-4xl mx-auto flex justify-between items-center">
<h1 className="text-2xl font-bold text-accent">Council Haus</h1>
<Button>Connect Wallet</Button>
</div>
</div>;
}
79 changes: 79 additions & 0 deletions apps/web/src/components/VotingCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client';

import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@repo/ui/components/ui/card';
import { Button } from '@repo/ui/components/ui/button';
import { Input } from '@repo/ui/components/ui/input';
import React, { useState } from 'react';

const VotingCard = ({ className, projects, maxVotedProjects = 3 }: { className: string, projects: string[], maxVotedProjects?: number }) => {
const [votes, setVotes] = useState<{ [key: string]: number }>(
Object.fromEntries(projects.map(project => [project, 0]))
);
const votedProjects = Object.keys(votes).filter(project => votes[project] ?? 0 > 0);

const totalVotes = Object.values(votes).reduce((a, b) => a + b, 0);

const handleVote = (project: string, value: number) => {
const newValue = Math.max(0, value || 0);
setVotes(prev => ({
...prev,
[project]: newValue
}));
};

return (
<Card className={className}>
<CardHeader>
<CardTitle>
Which project is doing better?
</CardTitle>
</CardHeader>
<CardContent>
{projects.length === 0 ? (
<div>No projects to vote on</div>
) : (
<>
<h4 className="text-xl mb-6 text-accent">Cast Your Vote ({votedProjects.length} / {maxVotedProjects})</h4>
{Object.entries(votes).map(([project, voteCount]) => (
<div key={project} className="flex items-center justify-between mb-3">
<span className="flex-grow">{project}</span>
<div className="flex items-center">
<Button
disabled={voteCount <= 0}
onClick={() => handleVote(project, voteCount - 1)}
className="bg-gray-700 w-8 py-1 text-white rounded-r-none"
>
-
</Button>
<Input
type="number"
value={voteCount}
onChange={(e) => handleVote(project, parseInt(e.target.value))}
className="w-16 bg-gray-600 text-center text-white rounded-none px-3 py-1 input-number-hide-arrows border-none"
/>
<Button
disabled={votedProjects.length >= maxVotedProjects && !votes[project as keyof typeof votes]}
onClick={() => handleVote(project, voteCount + 1)}
className="bg-gray-700 w-8 py-1 text-white rounded-l-none"
>
+
</Button>
<span className="w-12 text-right">
{totalVotes > 0 ? Math.round((voteCount / totalVotes) * 100) : 0}%
</span>
</div>
</div>
))}
</>
)}
</CardContent>
<CardFooter>
<Button disabled={votedProjects.length < 1} className="w-full py-2 rounded-lg mt-4 font-bold">
Vote
</Button>
</CardFooter>
</Card>
);
};

export default VotingCard;
4 changes: 2 additions & 2 deletions packages/ui/src/components/ui/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const CardTitle = React.forwardRef<
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
"text-2xl font-serif leading-none tracking-tight text-accent text-center",
className,
)}
{...props}
Expand All @@ -60,7 +60,7 @@ const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
<div ref={ref} className={cn("m-6 my-2 bg-gray-800 p-4 rounded-lg", className)} {...props} />
));
CardContent.displayName = "CardContent";

Expand Down
25 changes: 25 additions & 0 deletions packages/ui/src/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from "react"

import { cn } from "@repo/ui/lib/utils"

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"

export { Input }
71 changes: 13 additions & 58 deletions packages/ui/src/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,8 @@

@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 {
Expand All @@ -74,3 +16,16 @@
@apply bg-background text-foreground;
}
}

@layer components {
.input-number-hide-arrows::-webkit-outer-spin-button,
.input-number-hide-arrows::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

.input-number-hide-arrows {
-moz-appearance: textfield;
appearance: textfield;
}
}
39 changes: 20 additions & 19 deletions packages/ui/tailwind.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Config } from "tailwindcss";
import tailwindcssAnimate from "tailwindcss-animate";
import colors from "tailwindcss/colors";

const config = {
darkMode: ["class"],
Expand All @@ -21,38 +22,38 @@ const config = {
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
border: colors.yellow[500],
input: colors.gray[800],
ring: colors.gray[800],
background: colors.gray[800],
foreground: colors.gray[100],
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
DEFAULT: colors.yellow[400],
foreground: colors.gray[900],
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
DEFAULT: colors.gray[700],
foreground: colors.gray[100],
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
DEFAULT: colors.red[500],
foreground: colors.gray[100],
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
DEFAULT: colors.gray[700],
foreground: colors.gray[400],
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
DEFAULT: colors.yellow[500],
foreground: colors.gray[900],
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
DEFAULT: colors.gray[700],
foreground: colors.gray[100],
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
DEFAULT: colors.gray[900],
foreground: colors.gray[100],
},
},
borderRadius: {
Expand Down

0 comments on commit 946d98c

Please sign in to comment.