-
-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ui/portal): create dashboard #309
Changes from all commits
cc87b89
cf04079
aac246b
5ee3799
2d1ca50
bcfcc50
8f88930
2809056
e3d2221
fa86749
2a7c6e2
115071e
7b476a5
c11c3a6
352a680
50c6392
0a111e5
122b3fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,16 @@ | ||
export default function Index() { | ||
const links = ['github'] | ||
import dashboard_background from '@cuhacking/portal/assets/backgrounds/dashboard-bg-1.webp' | ||
import { Home } from '@cuhacking/portal/pages/index/index' | ||
|
||
export default function Signin() { | ||
return ( | ||
<div> | ||
<h1>Social Links</h1> | ||
<ul> | ||
{links.map(text => ( | ||
<li key={text}>{text}</li> | ||
))} | ||
</ul> | ||
<div className="w-full"> | ||
<Home /> | ||
<img | ||
src={dashboard_background} | ||
alt="Background" | ||
aria-hidden="true" | ||
className="absolute top-0 left-0 w-full h-full object-cover z-[-1]" | ||
/> | ||
</div> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import type { Meta, StoryObj } from '@storybook/react' | ||
import { Button } from '../button' | ||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip' | ||
|
||
const meta = { | ||
title: 'shadcn-ui/Tooltip', | ||
component: TooltipContent, | ||
tags: ['autodocs'], | ||
parameters: { | ||
layout: 'centered', | ||
}, | ||
args: { | ||
side: 'top', | ||
content: 'Tooltip content', | ||
}, | ||
argTypes: { | ||
side: { | ||
options: ['top', 'bottom', 'left', 'right'], | ||
control: { type: 'select' }, | ||
}, | ||
content: { control: 'text' }, | ||
}, | ||
} satisfies Meta<typeof Tooltip> | ||
|
||
export default meta | ||
type Story = StoryObj<typeof meta> | ||
|
||
export const Default: Story = { | ||
render: (args: any) => ( | ||
<TooltipProvider> | ||
<Tooltip> | ||
<TooltipTrigger asChild> | ||
<Button variant="outline">Hover me</Button> | ||
</TooltipTrigger> | ||
<TooltipContent side={args.side}>{args.content}</TooltipContent> | ||
</Tooltip> | ||
</TooltipProvider> | ||
), | ||
} | ||
|
||
export const Bottom: Story = { | ||
args: { | ||
side: 'bottom', | ||
}, | ||
render: Default.render, | ||
} | ||
|
||
export const Left: Story = { | ||
args: { | ||
side: 'left', | ||
}, | ||
render: Default.render, | ||
} | ||
|
||
export const Right: Story = { | ||
args: { | ||
side: 'right', | ||
}, | ||
render: Default.render, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import * as TooltipPrimitive from '@radix-ui/react-tooltip' | ||
import * as React from 'react' | ||
import { cn } from '../../../lib/utils' | ||
|
||
const TooltipProvider = TooltipPrimitive.Provider | ||
|
||
const Tooltip = TooltipPrimitive.Root | ||
|
||
const TooltipTrigger = TooltipPrimitive.Trigger | ||
|
||
const TooltipContent = React.forwardRef< | ||
React.ElementRef<typeof TooltipPrimitive.Content>, | ||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> | ||
>(({ className, sideOffset = 4, ...props }, ref) => ( | ||
<TooltipPrimitive.Portal> | ||
<TooltipPrimitive.Content | ||
ref={ref} | ||
sideOffset={sideOffset} | ||
className={cn( | ||
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', | ||
className, | ||
)} | ||
{...props} | ||
/> | ||
</TooltipPrimitive.Portal> | ||
)) | ||
TooltipContent.displayName = TooltipPrimitive.Content.displayName | ||
|
||
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { UserHackathonApplicationStatus } from '@cuhacking/portal/types/user' | ||
import { Banner } from '@cuhacking/portal/ui/dashboard/banner' | ||
import { HackathonCountdown } from '@cuhacking/portal/ui/dashboard/hackathon-countdown' | ||
import { Stat } from '@cuhacking/portal/ui/dashboard/stat' | ||
import { UserStatus } from '@cuhacking/portal/ui/dashboard/user-status' | ||
import { Sidebar } from '@cuhacking/portal/ui/sidebar' | ||
import { Button } from '@cuhacking/shared/ui/button' | ||
|
||
const constants = { | ||
user: { | ||
name: 'Saim', | ||
}, | ||
hackathonDate: new Date(2025, 2, 14), | ||
hackathonApplicationStatus: UserHackathonApplicationStatus.notComplete, | ||
stats: [ | ||
{ | ||
label: 'Money Raised', | ||
value: '$20 030', | ||
}, | ||
], | ||
} | ||
|
||
export function Home() { | ||
return ( | ||
<div className="flex h-screen"> | ||
<Sidebar /> | ||
<div className="flex flex-1 flex-col w-full p-4 gap-5"> | ||
<Banner name={constants.user.name} /> | ||
<div className="flex justify-center w-full"> | ||
{constants.hackathonApplicationStatus === UserHackathonApplicationStatus.notComplete | ||
&& ( | ||
<Button | ||
className="text-black px-8 md:px-12 py-2.5" | ||
variant="secondary" | ||
> | ||
APPLY TO CUHACKING 6 | ||
</Button> | ||
)} | ||
</div> | ||
<HackathonCountdown date={constants.hackathonDate} /> | ||
{constants.stats.map(stat => ( | ||
<Stat key={stat.label} label={stat.label} value={stat.value} /> | ||
))} | ||
{ constants.hackathonApplicationStatus !== UserHackathonApplicationStatus.notComplete && <UserStatus name={constants.user.name} status={constants.status} /> } | ||
</div> | ||
</div> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type { Meta, StoryObj } from '@storybook/react' | ||
import { Banner } from './banner' | ||
|
||
const meta = { | ||
title: '🌀 Portal/Banner', | ||
component: Banner, | ||
tags: ['autodocs'], | ||
args: { | ||
name: 'Hasith', | ||
}, | ||
argTypes: { | ||
name: { control: 'text' }, | ||
}, | ||
} satisfies Meta<typeof Banner> | ||
|
||
export default meta | ||
|
||
type Story = StoryObj<typeof meta> | ||
|
||
export const Default: Story = {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { GlassmorphicCard } from '@cuhacking/shared/ui/glassmorphic-card' | ||
|
||
export function Banner({ name }: { name: string }) { | ||
return ( | ||
<GlassmorphicCard className="gap-y-4 p-3 text-center w-full"> | ||
<h1 className="text-4xl">WELCOME</h1> | ||
<h2 className="text-transparent bg-greendiant bg-clip-text font-extrabold text-5xl"> | ||
{name} | ||
</h2> | ||
</GlassmorphicCard> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { Banner } from './banner' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
export function getCountdownTo(date: Date): { days: number, hrs: number, mins: number, secs: number } { | ||
const now = new Date() | ||
|
||
if (now > date) { | ||
throw new Error('Target date must be in the future.') | ||
} | ||
|
||
let diff = date.getTime() - now.getTime() | ||
|
||
const days = Math.floor(diff / (1000 * 60 * 60 * 24)) | ||
diff -= days * 1000 * 60 * 60 * 24 | ||
|
||
const hrs = Math.floor(diff / (1000 * 60 * 60)) | ||
diff -= hrs * 1000 * 60 * 60 | ||
|
||
const mins = Math.floor(diff / (1000 * 60)) | ||
diff -= mins * 1000 * 60 | ||
|
||
const secs = Math.floor(diff / 1000) | ||
|
||
return { days, hrs, mins, secs } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import type { Meta, StoryObj } from '@storybook/react' | ||
import { HackathonCountdown } from './hackathon-countdown' | ||
|
||
const meta = { | ||
title: '🌀 Portal/Hackathon Countdown', | ||
component: HackathonCountdown, | ||
tags: ['autodocs'], | ||
parameters: { | ||
layout: 'centered', | ||
}, | ||
args: { | ||
date: new Date(2025, 3, 14), | ||
}, | ||
argTypes: { | ||
date: { control: 'date' }, | ||
}, | ||
} satisfies Meta<typeof HackathonCountdown> | ||
|
||
export default meta | ||
|
||
type Story = StoryObj<typeof meta> | ||
|
||
export const Default: Story = {} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,38 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
import { GlassmorphicCard } from '@cuhacking/shared/ui/glassmorphic-card' | ||||||||||||||||||||||||||||||||||||||||||||||||
import { Typography } from '@cuhacking/shared/ui/typography' | ||||||||||||||||||||||||||||||||||||||||||||||||
import { useEffect, useState } from 'react' | ||||||||||||||||||||||||||||||||||||||||||||||||
import { getCountdownTo } from './countdown' | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
interface Countdown { | ||||||||||||||||||||||||||||||||||||||||||||||||
days: number | ||||||||||||||||||||||||||||||||||||||||||||||||
hrs: number | ||||||||||||||||||||||||||||||||||||||||||||||||
mins: number | ||||||||||||||||||||||||||||||||||||||||||||||||
secs: number | ||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
export function HackathonCountdown({ date }: { date: Date }) { | ||||||||||||||||||||||||||||||||||||||||||||||||
const [countdown, setCountdown] = useState<Countdown>(getCountdownTo(date)) | ||||||||||||||||||||||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||
const interval = setInterval(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||
setCountdown(getCountdownTo(date)) | ||||||||||||||||||||||||||||||||||||||||||||||||
}, 1000) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
return () => clearInterval(interval) | ||||||||||||||||||||||||||||||||||||||||||||||||
}, [date]) | ||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+13
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add validation for past dates. The countdown logic should handle cases where the target date is in the past to avoid negative values. export function HackathonCountdown({ date }: { date: Date }) {
+ const isDatePast = date.getTime() < Date.now()
const [countdown, setCountdown] = useState<Countdown>(getCountdownTo(date))
useEffect(() => {
+ if (isDatePast) {
+ setCountdown({ days: 0, hrs: 0, mins: 0, secs: 0 })
+ return
+ }
const interval = setInterval(() => {
setCountdown(getCountdownTo(date))
}, 1000)
return () => clearInterval(interval)
}, [date]) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||
<GlassmorphicCard className="flex flex-col gap-y-1 p-3"> | ||||||||||||||||||||||||||||||||||||||||||||||||
<Typography variant="h6" className="text-center">HACKATHON COUNTDOWN</Typography> | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
<div className="flex gap-x-1"> | ||||||||||||||||||||||||||||||||||||||||||||||||
{ Object.entries(countdown).map(([dateType, timeRemaining]) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||
<GlassmorphicCard key={dateType} className="w-16 gap-1-y p-3 flex items-center justify-center flex-col w-full"> | ||||||||||||||||||||||||||||||||||||||||||||||||
<Typography variant="h4">{timeRemaining}</Typography> | ||||||||||||||||||||||||||||||||||||||||||||||||
<Typography variant="paragraph-xs">{dateType}</Typography> | ||||||||||||||||||||||||||||||||||||||||||||||||
</GlassmorphicCard> | ||||||||||||||||||||||||||||||||||||||||||||||||
))} | ||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||
</GlassmorphicCard> | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { HackathonCountdown } from './hackathon-countdown' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { Legal } from './legal' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import type { Meta, StoryObj } from '@storybook/react' | ||
import { Legal } from './legal' | ||
|
||
const meta = { | ||
title: '🌀 Portal/Legal', | ||
component: Legal, | ||
tags: ['autodocs'], | ||
args: { | ||
}, | ||
argTypes: { | ||
}, | ||
} satisfies Meta<typeof Legal> | ||
|
||
export default meta | ||
|
||
type Story = StoryObj<typeof meta> | ||
|
||
export const Default: Story = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the hackathon date initialization.
The JavaScript Date constructor uses 0-based month indexing (0-11). Currently,
new Date(2025, 2, 14)
will create a date for March 14, 2025. If you intended for February 14, 2025, you should use:📝 Committable suggestion