Skip to content
This repository has been archived by the owner on Dec 16, 2024. It is now read-only.

Commit

Permalink
feat: apply variable font and theme toggles
Browse files Browse the repository at this point in the history
  • Loading branch information
jspark2000 committed Feb 27, 2024
1 parent b10c49a commit 222b419
Show file tree
Hide file tree
Showing 23 changed files with 979 additions and 790 deletions.
11 changes: 11 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ module.exports = {
'@typescript-eslint/no-import-type-side-effects': 'error',
'@typescript-eslint/no-inferrable-types': 'warn',
'func-style': ['error', 'expression'],
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['libs/*'],
message: 'Please import with path alias like `@libs/*`'
}
]
}
],
'object-shorthand': ['warn', 'always']
},
overrides: [
Expand Down
18 changes: 11 additions & 7 deletions frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,29 @@ module.exports = {
'@next/next/no-html-link-for-pages': [
'error',
require('path').join(__dirname, 'src/app')
],
'react/function-component-definition': [
'error',
{
namedComponents: 'function-declaration'
}
]
},
overrides: [
// TODO: If there is another way to solve the '@next/babel' error, remove below object
{
files: ['*.js'],
parser: 'espree',
parserOptions: {
ecmaVersion: 2020
}
},
{
files: ['*.tsx'],
excludedFiles: ['src/components/ui/*.tsx'],
rules: {
'react/function-component-definition': [
'error',
{
namedComponents: 'function-declaration'
}
],
'func-style': ['off']
'func-style': ['off'],
'no-restricted-imports': ['error']
}
},
{
Expand Down
7 changes: 6 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,23 @@
"dependencies": {
"@headlessui/react": "^1.7.18",
"@heroicons/react": "^2.1.1",
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-slot": "^1.0.2",
"@storybook/manager-api": "^7.6.13",
"@tailwindcss/forms": "^0.5.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"moment": "^2.30.1",
"next": "14.1.0",
"next-auth": "^4.24.6",
"next-themes": "^0.2.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.50.1",
"sonner": "^1.4.0",
"sooner": "^1.1.4",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.22.4"
Expand Down
1 change: 0 additions & 1 deletion frontend/public/next.svg

This file was deleted.

1 change: 0 additions & 1 deletion frontend/public/vercel.svg

This file was deleted.

4 changes: 0 additions & 4 deletions frontend/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,3 @@ html {
html::-webkit-scrollbar {
display: none;
}

html {
background-color: rgb(20, 21, 23);
}
43 changes: 28 additions & 15 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import Footer from '@/components/Footer'
import Header from '@/components/Header'
import { ThemeProvider } from '@/components/ThemeProvider'
import Toaster from '@/components/ui/sooner'
import { cn } from '@/lib/utils'
import type { Metadata } from 'next'
import { Noto_Sans_KR } from 'next/font/google'
import './globals.css'

const gothicA1 = Noto_Sans_KR({
const noto = Noto_Sans_KR({
subsets: ['latin'],
weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900']
weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
variable: '--font-noto'
})

export const metadata: Metadata = {
Expand All @@ -23,19 +26,29 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className={`${gothicA1.className} antialiased`}>
<div className="flex min-h-screen flex-col">
<Header />
<div className="h-[80px]"></div>
{children}
<Footer />
</div>
<Toaster
richColors
position="top-center"
closeButton={true}
duration={2000}
/>
<body
className={cn('font-sans antialiased', noto.variable)}
suppressHydrationWarning={true}
>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<div className="dark:bg-custom-black flex min-h-screen flex-col bg-white">
<Header />
<div className="h-[65px]"></div>
{children}
<Footer />
</div>
<Toaster
richColors
position="top-center"
closeButton={true}
duration={2000}
/>
</ThemeProvider>
</body>
</html>
)
Expand Down
175 changes: 61 additions & 114 deletions frontend/src/app/login/_components/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,146 +1,93 @@
'use client'

import { ExclamationCircleIcon } from '@heroicons/react/20/solid'
import { UserIcon, KeyIcon } from '@heroicons/react/24/outline'
import clsx from 'clsx'
import { Button } from '@/components/ui/button'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { LoginFormSchema } from '@/lib/forms'
import { zodResolver } from '@hookform/resolvers/zod'
import { signIn } from 'next-auth/react'
import { useState } from 'react'
import { useFormState } from 'react-dom'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { z } from 'zod'

const LoginFormSchema = z.object({
username: z.string().min(1),
password: z.string().min(1)
})

interface LoginFormState {
errors?: {
username?: string[]
password?: string[]
}
message?: string | null
}
import type { z } from 'zod'

export default function LoginForm() {
const [validationError, setValidationError] = useState(false)
const [isFetching, setIsFetching] = useState(false)

const loginAction = async (prevState: LoginFormState, formData: FormData) => {
const validatedFields = LoginFormSchema.safeParse({
username: formData.get('username'),
password: formData.get('password')
})

if (!validatedFields.success) {
setValidationError(true)
return {
errors: validatedFields.error.flatten().fieldErrors,
message: '입력하지 않은 필드가 존재합니다'
}
const form = useForm<z.infer<typeof LoginFormSchema>>({
resolver: zodResolver(LoginFormSchema),
defaultValues: {
username: '',
password: ''
}
})

const onSubmit = async (data: z.infer<typeof LoginFormSchema>) => {
try {
setIsFetching(true)
const res = await signIn('credentials', {
...validatedFields.data,
data,
redirect: false
})

if (!res?.error) {
toast.success('로그인 되었습니다')
return { ...prevState, message: '로그인 되었습니다' }
} else {
console.log(res.error)
toast.error('로그인 실패')
return { ...prevState, message: '로그인 실패' }
}
} catch (error) {
toast.error('로그인 실패')
return { ...prevState, message: '로그인 실패' }
} finally {
setIsFetching(false)
}
}

const initState = { message: null, errors: {} }
const [state, dispatch] = useFormState(loginAction, initState)

return (
<form action={dispatch} className="w-full">
<div className="isolate -space-y-px rounded-md">
<div
className={clsx(
'relative rounded-md rounded-b-none ring-1 ring-inset ring-gray-400 focus-within:z-10 focus-within:ring-2 focus-within:ring-inset focus-within:ring-amber-500',
{
'ring-red-600': state.errors?.username
}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-full space-y-3">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel className="sr-only">Username</FormLabel>
<FormControl>
<Input placeholder="아이디" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
>
<label htmlFor="username" className="sr-only">
username
</label>
<div className="relative rounded-md shadow-sm">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<UserIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
<input
type="text"
name="username"
id="username"
className="block w-full border-0 bg-transparent py-2.5 pl-10 text-gray-50 placeholder:text-gray-50 focus:outline-none focus:ring-0 sm:text-sm sm:leading-6"
placeholder="아이디"
/>
{state.errors?.username && (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<ExclamationCircleIcon
className="h-5 w-5 text-red-500"
aria-hidden="true"
/>
</div>
)}
</div>
</div>
<div
className={clsx(
'relative rounded-md rounded-t-none ring-1 ring-inset ring-gray-400 focus-within:z-10 focus-within:ring-2 focus-within:ring-inset focus-within:ring-amber-500',
{
'ring-red-600': state.errors?.password
}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel className="sr-only">Password</FormLabel>
<FormControl>
<Input type="password" placeholder="패스워드" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
variant="accent"
type="submit"
className="w-full"
disabled={isFetching}
>
<label htmlFor="password" className="sr-only">
password
</label>
<div className="relative rounded-md shadow-sm">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<KeyIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
<input
type="password"
name="password"
id="password"
className="block w-full border-0 bg-transparent py-2.5 pl-10 text-gray-50 placeholder:text-gray-50 focus:outline-none focus:ring-0 sm:text-sm sm:leading-6"
placeholder="패스워드"
/>
{state.errors?.password && (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<ExclamationCircleIcon
className="h-5 w-5 text-red-500"
aria-hidden="true"
/>
</div>
)}
</div>
</div>
</div>
{validationError && (
<p className="mt-1 text-xs text-red-600 sm:text-sm">
입력되지 않은 필드가 존재합니다
</p>
)}
<button
className="mt-5 w-full rounded-md bg-amber-400 px-2 py-1.5 font-bold text-gray-950 shadow-sm hover:bg-amber-500 sm:text-sm sm:leading-6"
type="submit"
>
로그인
</button>
</form>
로그인
</Button>
</form>
</Form>
)
}
9 changes: 5 additions & 4 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,28 @@ export default function Home() {
return (
<main className="mx-auto flex w-full max-w-7xl flex-grow flex-col items-center justify-center p-6 lg:grid lg:grid-cols-12 lg:px-8">
<div className="col-span-12 hidden items-end lg:col-span-5 lg:-mr-10 lg:flex lg:flex-col">
<h1 className="text-4xl font-bold text-gray-50 lg:text-5xl">
<h1 className="text-4xl font-bold text-zinc-900 lg:text-5xl dark:text-gray-50">
SKKU ROYALS
</h1>
<h1 className="-mt-1.5 text-sm font-light text-amber-400 lg:text-lg">
<h1 className="-mt-1.5 text-sm font-normal text-amber-500 lg:text-lg">
American Football Team
</h1>
</div>
<div className="col-span-12 -mb-12 flex flex-col flex-nowrap items-center lg:col-span-7 lg:mb-0">
<Image
width={1080}
height={1080}
priority={true}
src="/hero.png"
alt="hero image"
className="h-80 w-auto lg:h-[640px]"
/>
</div>
<div className="col-span-12 flex flex-col items-end lg:hidden">
<h1 className="text-4xl font-bold text-gray-50 lg:text-5xl">
<h1 className="text-4xl font-bold text-zinc-900 dark:text-gray-50">
SKKU ROYALS
</h1>
<h1 className="-mt-1.5 text-sm font-light text-amber-400 lg:text-lg">
<h1 className="-mt-1.5 text-sm font-light text-amber-400 lg:text-lg dark:text-amber-400">
American Football Team
</h1>
</div>
Expand Down
Loading

0 comments on commit 222b419

Please sign in to comment.