Skip to content
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

Merged
merged 18 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default {
'../libs/website/ui/**/*.@(mdx|stories.@(js|jsx|ts|tsx))',
'../libs/portal/features/**/*.@(mdx|stories.@(js|jsx|ts|tsx))',
'../libs/portal/shared/features/**/*.@(mdx|stories.@(js|jsx|ts|tsx))',
'../libs/portal/ui/**/*.@(mdx|stories.@(js|jsx|ts|tsx))',
],
staticDirs: [
// './public',
Expand Down
20 changes: 11 additions & 9 deletions apps/portal/app/routes/_index.tsx
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>
)
}
Empty file.
60 changes: 60 additions & 0 deletions libs/external/shadcn/components/ui/tooltip/tooltip.stories.tsx
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,
}
29 changes: 29 additions & 0 deletions libs/external/shadcn/components/ui/tooltip/tooltip.tsx
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 }
48 changes: 48 additions & 0 deletions libs/portal/pages/index/index.tsx
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),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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:

-  hackathonDate: new Date(2025, 2, 14),
+  hackathonDate: new Date(2025, 1, 14),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
hackathonDate: new Date(2025, 2, 14),
hackathonDate: new Date(2025, 1, 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>
)
}
5 changes: 5 additions & 0 deletions libs/portal/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ export enum UserProfileStatus {
complete = 'complete',
notComplete = 'not_complete',
}
export enum UserHackathonApplicationStatus {
complete = 'complete',
notComplete = 'not_complete',
pending = 'pending',
}
20 changes: 20 additions & 0 deletions libs/portal/ui/dashboard/banner/banner.stories.tsx
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 = {}
12 changes: 12 additions & 0 deletions libs/portal/ui/dashboard/banner/banner.tsx
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>
)
}
1 change: 1 addition & 0 deletions libs/portal/ui/dashboard/banner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Banner } from './banner'
22 changes: 22 additions & 0 deletions libs/portal/ui/dashboard/hackathon-countdown/countdown.ts
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
Copy link

Choose a reason for hiding this comment

The 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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])
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])


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>

)
}
1 change: 1 addition & 0 deletions libs/portal/ui/dashboard/hackathon-countdown/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { HackathonCountdown } from './hackathon-countdown'
1 change: 1 addition & 0 deletions libs/portal/ui/dashboard/legal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Legal } from './legal'
18 changes: 18 additions & 0 deletions libs/portal/ui/dashboard/legal/legal.stories.tsx
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 = {}
Loading
Loading