Skip to content

Commit

Permalink
Merge branch 'main' into 1884-get-contest-submission-informations-abo…
Browse files Browse the repository at this point in the history
…ut-one-user
  • Loading branch information
Jaehyeon1020 authored Aug 7, 2024
2 parents 53d9186 + 137ac90 commit f33cfe3
Show file tree
Hide file tree
Showing 43 changed files with 649 additions and 350 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Warnings:
- You are about to drop the `Image` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "Image" DROP CONSTRAINT "Image_createdById_fkey";

-- DropTable
DROP TABLE "Image";

-- CreateTable
CREATE TABLE "image" (
"filename" TEXT NOT NULL,
"createdById" INTEGER,
"create_time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "image_pkey" PRIMARY KEY ("filename")
);

-- CreateIndex
CREATE UNIQUE INDEX "image_filename_key" ON "image"("filename");

-- AddForeignKey
ALTER TABLE "image" ADD CONSTRAINT "image_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE;
2 changes: 2 additions & 0 deletions apps/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ model Image {
createdBy User? @relation(fields: [createdById], references: [id], onDelete: SetNull)
createdById Int?
createTime DateTime @default(now()) @map("create_time")
@@map("image")
}

enum Level {
Expand Down
115 changes: 68 additions & 47 deletions apps/frontend/app/(main)/_components/ContestCard.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,91 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
'use client'

import { cn, dateFormatter } from '@/lib/utils'
import CalendarIcon from '@/public/20_calendar.svg'
import ClockIcon from '@/public/20_clock.svg'
import type { Contest } from '@/types/type'
import { FaRegClock } from 'react-icons/fa'
import { FaRegCalendarAlt } from 'react-icons/fa'
import Badge from './Badge'
import Image from 'next/image'
import { CircularProgressbar } from 'react-circular-progressbar'
import 'react-circular-progressbar/dist/styles.css'
import StatusBadge from './StatusBadge'
import TimeDiff from './TimeDiff'

const variants = {
ongoing: 'bg-gradient-to-br from-blue-500 to-blue-950',
upcoming: 'bg-gradient-to-br from-emerald-600 to-emerald-900',
const bgVariants = {
ongoing: 'bg-gradient-to-b from-blue-100 to-white',
upcoming: 'bg-white',
finished: 'bg-gray-500',
registeredOngoing: 'bg-gradient-to-br from-blue-500 to-blue-950',
registeredUpcoming: 'bg-gradient-to-br from-emerald-600 to-emerald-900'
registeredOngoing: 'bg-gradient-to-b from-blue-100 to-white',
registeredUpcoming: 'bg-white'
}

const txtVariants = {
ongoing: 'text-blue-500',
upcoming: 'text-red-400',
finished: 'text-gray-500',
registeredOngoing: 'text-blue-500',
registeredUpcoming: 'text-red-400'
}
interface Props {
contest: Contest
}

const format = (target: Date, year: number): string =>
new Date(target).getFullYear() === year
? dateFormatter(target, 'MMM DD')
: dateFormatter(target, 'MMM DD, YYYY')

export default function ContestCard({ contest }: Props) {
const year = new Date().getFullYear()
const startTime = format(contest.startTime, year)
const endTime = format(contest.endTime, year)
const startTime = dateFormatter(contest.startTime, 'YYYY-MM-DD')
const endTime = dateFormatter(contest.endTime, 'YYYY-MM-DD')

return (
<Card
<div
className={cn(
'flex w-full flex-col justify-between gap-1 border-none text-white shadow-none transition hover:scale-105 hover:opacity-80',
variants[contest.status]
'flex w-full flex-col justify-between gap-1 rounded-md border border-gray-200 px-3 shadow-none transition hover:scale-105 hover:opacity-80',
bgVariants[contest.status]
)}
>
<CardHeader className="pb-0">
<Badge type={contest.status}>
<p>
{contest.status.startsWith('registered')
? 'registered'
: contest.status}
</p>
</Badge>
<CardTitle className="overflow-hidden text-ellipsis whitespace-nowrap text-lg font-semibold">
<div
className={cn(
'flex flex-col justify-between gap-4 pt-4 uppercase',
txtVariants[contest.status]
)}
>
<StatusBadge variant={contest.status} />
<div className="line-clamp-2 h-14 whitespace-pre-wrap text-lg font-semibold leading-tight text-black">
{contest.title}
</CardTitle>
</CardHeader>
<CardContent className="inline-flex items-center gap-1 whitespace-nowrap text-xs text-white opacity-80">
{contest.status === 'finished' ? (
<>
<FaRegCalendarAlt className="shrink-0" />
</div>
</div>
<div className="mb-4 flex items-center justify-between">
<div className="flex flex-col gap-2">
<div className="inline-flex items-center gap-2 whitespace-nowrap text-xs text-gray-800 opacity-80">
<Image src={CalendarIcon} alt="Calendar" />
<p className="overflow-hidden text-ellipsis whitespace-nowrap">
{startTime} - {endTime}
{startTime} ~ {endTime}
</p>
</>
) : (
<>
<FaRegClock className="shrink-0" />
{contest.status === 'ongoing' ? 'Ends in' : 'Starts in'}
<p className="overflow-hidden text-ellipsis whitespace-nowrap">
<TimeDiff timeRef={contest.endTime}></TimeDiff>
</p>
</>
</div>

<div className="inline-flex items-center gap-2 whitespace-nowrap text-xs text-gray-800 opacity-80">
{contest.status === 'finished' ? (
<>
<Image src={CalendarIcon} alt="Calendar" />
<p className="overflow-hidden text-ellipsis whitespace-nowrap">
{startTime} - {endTime}
</p>
</>
) : (
<>
<Image src={ClockIcon} alt="Clock" />
{contest.status === 'ongoing' ? 'Ends in' : 'Starts in'}
<p className="overflow-hidden text-ellipsis whitespace-nowrap">
<TimeDiff timeRef={contest.endTime}></TimeDiff>
</p>
</>
)}
</div>
</div>
{(contest.status == 'ongoing' ||
contest.status == 'registeredOngoing') && (
<div className="h-12 w-12">
<CircularProgressbar value={60} text={`60%`} />
</div>
)}
</CardContent>
</Card>
</div>
</div>
)
}
88 changes: 44 additions & 44 deletions apps/frontend/app/(main)/_components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use client'

import { IoIosLink } from 'react-icons/io'
import { RiGithubFill } from 'react-icons/ri'
import CodedangLogo from '@/public/codedang.svg'
import Image from 'next/image'
import { AiOutlineLink } from 'react-icons/ai'
import { AiFillMail } from 'react-icons/ai'
import { RiKakaoTalkFill } from 'react-icons/ri'
import { TbMail } from 'react-icons/tb'
import { TbBrandGithubFilled } from 'react-icons/tb'
import { toast } from 'sonner'

export default function Footer() {
Expand All @@ -17,48 +19,46 @@ export default function Footer() {
}
}
return (
<footer className="mt-8 flex h-[100px] w-full items-center justify-center bg-gray-50 md:h-[125px] md:items-end">
<div className="flex w-full max-w-7xl flex-col justify-center gap-1 p-5 text-gray-400 md:flex-row md:justify-between md:gap-3">
<p className="text-center text-sm font-bold">
(c) SKKUDING / Since 2021
<footer className="mt-8 flex h-80 w-full flex-col items-center justify-center gap-8 bg-gray-50 md:h-80 md:items-center">
<div className="flex min-w-fit items-center justify-center">
<Image src={CodedangLogo} alt="코드당" width={180} height={28} />
</div>
<div className="flex items-center justify-center gap-3">
<a
rel="noreferrer noopener"
target="_blank"
href="https://github.com/skkuding/"
className="rounded border border-gray-200 p-1"
>
<TbBrandGithubFilled className="text-gray-500" size="17" />
</a>
<a
href="https://pf.kakao.com/_UKraK/chat"
rel="noreferrer noopener"
target="_blank"
className="rounded border border-gray-200 p-1"
>
<RiKakaoTalkFill className="text-gray-500" size="17" />
</a>
<AiFillMail
onClick={copyToClipboard}
className="rounded border border-gray-200 p-1 text-gray-500"
size="27"
/>
<a
rel="noreferrer noopener"
target="_blank"
href="https://skkuding.dev/"
className="rounded border border-gray-200 p-1"
>
<AiOutlineLink className="text-gray-500" size="17" />
</a>
</div>
<div className="text-black-400 flex max-w-7xl flex-col justify-center gap-1 md:justify-center">
<p className="text-center text-sm font-light">
ⓒ 2024. CODEDANG All rights reserved.
</p>
<div className="flex items-center justify-center gap-4">
<a
href="https://pf.kakao.com/_UKraK/chat"
rel="noreferrer noopener"
target="_blank"
>
<RiKakaoTalkFill
className="cursor-pointer hover:text-gray-500"
size="27"
/>
</a>
<TbMail
onClick={copyToClipboard}
className="cursor-pointer hover:text-gray-500"
size="27"
/>
<a
rel="noreferrer noopener"
target="_blank"
href="https://github.com/skkuding/"
>
<RiGithubFill
className="cursor-pointer hover:text-gray-500"
size="27"
/>
</a>
<a
rel="noreferrer noopener"
target="_blank"
href="https://skkuding.dev/"
>
<IoIosLink
className="cursor-pointer hover:text-gray-500"
size="26"
/>
</a>
</div>
<p className="text-center text-sm font-light">Since 2021</p>
</div>
</footer>
)
Expand Down
21 changes: 13 additions & 8 deletions apps/frontend/app/(main)/_components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ import NavLink from './NavLink'
export default async function Header() {
const session = await auth()
return (
<header className="border-b-gray grid h-16 w-full place-items-center border-b bg-white px-5">
<div className="flex w-full max-w-7xl items-center justify-between gap-5">
<header className="fixed z-40 grid h-16 w-full place-items-center bg-white/80">
<div className="flex w-full max-w-7xl items-center justify-between gap-5 px-5">
{/* FIXME: If you uncomment a group tab, you have to remove a pr-20 tailwind class */}
<div className="flex w-1/2 min-w-fit items-center justify-between gap-8 pr-20">
<div className="flex min-w-fit items-center justify-between gap-8 text-[16px]">
<Link href="/">
<Image src={CodedangLogo} alt="코드당" width={90} />
<Image
src={CodedangLogo}
alt="코드당"
width={135.252}
height={28}
/>
</Link>

<nav className="hidden gap-8 capitalize md:flex">
<NavLink href="/notice" text="Notice" />
<NavLink href="/contest" text="Contest" />
<NavLink href="/problem" text="Problem" />
<nav className="hidden gap-10 font-mono font-medium capitalize md:flex">
<NavLink href="/notice" text="NOTICE" />
<NavLink href="/contest" text="CONTEST" />
<NavLink href="/problem" text="PROBLEM" />
{/* TODO: Uncomment a group tab when we start to implement a group feature*/}
{/* <NavLink href="/group" text="Group" /> */}
</nav>
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/app/(main)/_components/NavLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function NavLink<T extends string>({
<Link
href={href}
className={cn(
'text-lg hover:opacity-60',
'text-md hover:text-primary',
pathname.startsWith(String(href)) && 'text-primary'
)}
>
Expand Down
6 changes: 2 additions & 4 deletions apps/frontend/app/(main)/_components/ProblemCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ export default function ProblemCard({ problem }: Props) {
<div className="flex w-full flex-col justify-between gap-4 rounded-md border border-gray-200 p-4 shadow-none transition hover:scale-105">
<div className="flex flex-col justify-between gap-4 pt-2">
<p className="text-sm font-medium text-gray-500">{`Problem #${problem.id}`}</p>
<div className="line-clamp-2 whitespace-pre-wrap text-lg font-semibold leading-tight">
{/* code to keep two lines of problem title */}
{`${problem.title}
`}
<div className="line-clamp-2 h-11 whitespace-pre-wrap text-lg font-semibold leading-tight">
{problem.title}
</div>
</div>
<div className="border-t-2"></div>
Expand Down
3 changes: 2 additions & 1 deletion apps/frontend/app/(main)/_components/ProblemCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const getProblems = async () => {
const problemRes: ProblemCardsProps = await fetcher
.get('problem', {
searchParams: {
take: 3
take: 3,
order: 'submit-desc'
// workbookId: 1
}
})
Expand Down
48 changes: 48 additions & 0 deletions apps/frontend/app/(main)/_components/StatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { cn } from '@/lib/utils'
import FinishedIcon from '@/public/20_finished.svg'
import OngoingIcon from '@/public/20_ongoing.svg'
import UpcomingIcon from '@/public/20_upcoming.svg'
import Image from 'next/image'
import React from 'react'

const variants = {
registeredOngoing: {
image: OngoingIcon,
text: 'ONGOING',
color: 'text-blue-500'
},
registeredUpcoming: {
image: UpcomingIcon,
text: 'UPCOMING',
color: 'text-red-400'
},
ongoing: {
image: OngoingIcon,
text: 'ONGOING',
color: 'text-blue-500'
},
upcoming: {
image: UpcomingIcon,
text: 'UPCOMING',
color: 'text-red-400'
},
finished: {
image: FinishedIcon,
text: 'FINISHED',
color: 'text-gray-400'
}
}

interface Props {
variant: keyof typeof variants
}

export default function StatusBadge({ variant }: Props) {
const { image, text, color } = variants[variant]
return (
<div className="inline-flex items-center gap-2">
<Image src={image} alt={text} />
<p className={cn('font-mono font-medium', color)}>{text}</p>
</div>
)
}
2 changes: 1 addition & 1 deletion apps/frontend/app/(main)/_components/TimeDiff.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function TimeDiff({ timeRef }: Props) {

return (
<span suppressHydrationWarning>
D-{days} {hours + diff.format(':mm:ss')}
{days > 0 ? `${days} DAYS` : `${hours + diff.format(':mm:ss')}`}
</span>
)
}
Loading

0 comments on commit f33cfe3

Please sign in to comment.