Skip to content
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
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
refetchOnMount: 'always',
refetchOnMount: true,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
staleTime: 1000 * 10, // 10 seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,7 @@ const CourseSearchDataList = forwardRef<HTMLDivElement, Props>(({ year, semester
if (hasNextPage && !isFetching) fetchNextPage()
})

if ((searchQuery.category === undefined || searchQuery.category === 'All Class') && !searchQuery.keyword)
// is initial state
return (
<div className={SearchMessageStyle}>
Enter keywords to search (e.g., course name, professor name, or course number)
</div>
)

// TODO: Vanilla-extract & Pagination 컴포넌트 사용 로직으로 변경
if (searchData.length)
return (
<div
Expand All @@ -69,7 +62,7 @@ const CourseSearchDataList = forwardRef<HTMLDivElement, Props>(({ year, semester
{searchData.map((data, index) => (
<SearchLectureCard key={index} data={data} addCourse={addCourse} />
))}
<div ref={fetchNextRef} className={css({ height: 1 })} />
<div ref={fetchNextRef} className={css({ height: '0.25rem', flexShrink: 0 })} />
</div>
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,8 @@ const AddClass = ({ timetableId, year, semester }: AddClassProps) => {

const handleSearchBoxOnSubmit = useCallback(
(queryKeyword: string) => {
if (
(queryKeyword.length === 0 && searchQuery.category !== undefined && searchQuery.category !== 'All Class') ||
queryKeyword.length > 2
) {
// All Class가 아닌 Category에서 빈 글자 입력
// 또는 2글자 이상 검색
if (queryKeyword.length !== 1) {
// 글자수 충족 시
search({ ...searchQuery, keyword: queryKeyword })
} else
toast.custom(() => <Toast message="Please enter at least two letters for your search term." type="default" />)
Expand Down
3 changes: 3 additions & 0 deletions src/domain/Banner/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const BANNER_QUERY_KEY = {
banner: () => ['banner'] as const,
}
18 changes: 18 additions & 0 deletions src/domain/Banner/useReadHomeBanner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { queryOptions, useSuspenseQuery } from '@tanstack/react-query'

import { useAsyncRead } from '@/common/hooks/useAsyncRead'
import { BANNER_QUERY_KEY } from '@/domain/Banner/queries'
import { kuKeyClient } from '@/packages/api'

export const useQueryHomeBanner = () => {
const read = useAsyncRead(kuKeyClient.api.BannerApi.bannerGet)
return queryOptions({
queryKey: BANNER_QUERY_KEY.banner(),
queryFn: () => read(),
})
}

export const useReadHomeBanner = () => {
const query = useQueryHomeBanner()
return useSuspenseQuery(query)
}
22 changes: 9 additions & 13 deletions src/domain/Course/components/CourseItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,20 @@ import * as s from './style.css'

import RateTag from '@/domain/Course/components/RateTag'
import SemesterTag from '@/domain/Course/components/SemesterTag'
import { CommonCourseResponseDto } from '@/packages/api/ku-key/models'
import { Typography } from '@/ui/Typography'
import { numberToSemester } from '@/util/timetableUtil'

type Props = {
title: string
professor: string
courseRate: string
semester: string
//TODO: 추천 강의 id 받아야 함.
}
type Props = Pick<CommonCourseResponseDto, 'courseName' | 'professorName' | 'totalRate' | 'semester' | 'year'>

const CourseItem = ({ title, professor, courseRate, semester }: Props) => {
const CourseItem = ({ courseName, professorName, totalRate, semester, year }: Props) => {
const semesterText = numberToSemester[Number(semester)]
return (
<div className={s.Wrapper}>
<div className={s.Header}>
<SemesterTag semester={semester} />
<SemesterTag semester={semesterText} year={year} />
<div className={s.Rate}>
🍪
<RateTag rate={Number(courseRate)} />
<RateTag rate={Number(totalRate)} />
</div>
</div>
<div className={s.Body}>
Expand All @@ -28,14 +24,14 @@ const CourseItem = ({ title, professor, courseRate, semester }: Props) => {
mobileTypography="miniTag1M"
style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', width: '100%' }}
>
{title}
{courseName}
</Typography>
<div className={s.Professor}>
<Typography typography="body2R" mobileTypography="miniTag2" color="darkGray1">
Prof.
</Typography>
<Typography typography="body1M" mobileTypography="miniTag2">
{professor}
{professorName}
</Typography>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/domain/Course/components/RateTag/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const RateTag = ({ rate }: Props) => {
return (
<div className={s.Wrapper} style={{ backgroundColor: getCourseRateBackgroundColor(rate) }}>
<Typography typography="heading1SB" mobileTypography="bodySB" style={{ color: getCourseRateTextColor(rate) }}>
{rate}
{rate.toFixed(1)}
</Typography>
</div>
)
Expand Down
5 changes: 3 additions & 2 deletions src/domain/Course/components/SemesterTag/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { Typography } from '@/ui/Typography'

type Props = {
semester: string
year: string
}

const SemesterTag = ({ semester }: Props) => {
const SemesterTag = ({ semester, year }: Props) => {
return (
<div className={s.Wrapper}>
<Typography typography="body1M" color="darkGray1" mobileTypography="miniTag2">
{semester}
{`${year} - ${semester}`}
</Typography>
</div>
)
Expand Down
21 changes: 21 additions & 0 deletions src/domain/Course/hooks/useReadCourseRecommendation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { queryOptions, useSuspenseQuery } from '@tanstack/react-query'

import { useAsyncRead } from '@/common/hooks/useAsyncRead'
import { COURSE_QUERY_KEY } from '@/domain/Course/queries'
import { kuKeyClient } from '@/packages/api'
import { CourseRecommendationGetRequestParams } from '@/packages/api/ku-key/api/course-api'

type Props = CourseRecommendationGetRequestParams

export const useQueryCourseRecommendation = ({ limit }: Props) => {
const read = useAsyncRead(kuKeyClient.api.CourseApi.courseRecommendationGet)
return queryOptions({
queryKey: COURSE_QUERY_KEY.recommendation(limit),
queryFn: () => read({ limit }),
})
}

export const useReadCourseRecommendation = ({ limit }: Props) => {
const read = useQueryCourseRecommendation({ limit })
return useSuspenseQuery(read)
}
4 changes: 4 additions & 0 deletions src/domain/Course/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const COURSE_QUERY_KEY = {
base: () => ['course'],
recommendation: (limit: number) => [...COURSE_QUERY_KEY.base(), 'recommendation', limit],
}
3 changes: 3 additions & 0 deletions src/domain/HomeCalendar/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const HOME_CALENDAR_QUERY_KEY = {
base: () => ['home-calendar'] as const,
}
20 changes: 20 additions & 0 deletions src/domain/Timetable/hooks/useReadTodayTimetable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { queryOptions, useSuspenseQuery } from '@tanstack/react-query'

import { useAsyncRead } from '@/common/hooks/useAsyncRead'
import { TIMETABLE_QUERY_KEY } from '@/domain/Timetable/queries'
import { kuKeyClient } from '@/packages/api'
import { TimetableTodayGetRequestParams } from '@/packages/api/ku-key/api/timetable-api'

type Props = TimetableTodayGetRequestParams
export const useQueryTodayTimetable = ({ semester, year }: Props) => {
const read = useAsyncRead(kuKeyClient.api.TimetableApi.timetableTodayGet)
return queryOptions({
queryKey: TIMETABLE_QUERY_KEY.today({ semester, year }),
queryFn: () => read({ semester, year }),
})
}

export const useReadTodayTimetable = ({ semester, year }: Props) => {
const read = useQueryTodayTimetable({ semester, year })
return useSuspenseQuery(read)
}
9 changes: 9 additions & 0 deletions src/domain/Timetable/queries.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { CourseSearchProps } from '@/domain/Timetable/hooks/useSearchCourse'
import { TimetableTodayGetRequestParams } from '@/packages/api/ku-key/api/timetable-api'

export const TIMETABLE_QUERY_KEY = {
base: () => ['timetable'] as const,
search: ({ year, semester, category, classification, keyword }: CourseSearchProps) => [
...TIMETABLE_QUERY_KEY.base(),
'courseSearchResult',
year,
semester,
category,
classification,
keyword,
],
today: ({ year, semester }: TimetableTodayGetRequestParams) => [
...TIMETABLE_QUERY_KEY.base(),
'today',
year,
semester,
],
}
75 changes: 24 additions & 51 deletions src/features/HomeBanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,21 @@ import Autoplay from 'embla-carousel-autoplay'
import useEmblaCarousel, { UseEmblaCarouselType } from 'embla-carousel-react'
import { useCallback, useEffect, useState } from 'react'
import { HiChevronLeft, HiChevronRight } from 'react-icons/hi'
import { useNavigate } from 'react-router-dom'

import * as s from './style.css'

import { useGetBannerImages } from '@/api/hooks/calendar'
import KUkeyLogo from '@/assets/Ku-key_Big.png'
import { Button } from '@/ui/Button'
import { useReadHomeBanner } from '@/domain/Banner/useReadHomeBanner'
import { Typography } from '@/ui/Typography'
import { useAuth } from '@/util/auth/useAuth'
import { usePrevNextButtons } from '@/util/carousel-button'
import { useMediaQueryByName } from '@/util/hooks/useMediaQueryByName'

const HomeBanner = () => {
const { authState } = useAuth()
const navigate = useNavigate()
const { data: banners } = useGetBannerImages()
const { data: banners } = useReadHomeBanner()

const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, align: 'center' }, [Autoplay()])
const isMobile = useMediaQueryByName('smDown')
const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true, align: 'center' }, isMobile ? [Autoplay()] : [])

const [currentSlide, setCurrentSlide] = useState(0)

const logSlidesInView = useCallback((emblaApi: UseEmblaCarouselType[1]) => {
if (emblaApi) {
const currentSlideIndex = emblaApi.internalEngine().index.get()
Expand All @@ -32,7 +27,8 @@ const HomeBanner = () => {
const { onPrevButtonClick, onNextButtonClick, onButtonAutoplayClick } = usePrevNextButtons(emblaApi)

useEffect(() => {
if (emblaApi) emblaApi.on('select', logSlidesInView)
if (!emblaApi) return
emblaApi.on('select', logSlidesInView)
}, [emblaApi, logSlidesInView])

return (
Expand All @@ -41,49 +37,26 @@ const HomeBanner = () => {
<div ref={emblaRef} className={s.EmblaViewport}>
{/* embla__container */}
<div className={s.EmblaContainer}>
{banners?.map(({ imageUrl }, index) => (
<div key={`banner-img-${index}`} className={s.EmblaSlide}>
<img src={imageUrl} alt={`banner-img-${index}`} style={{ width: '80%' }} />
</div>
{banners?.map(({ imageUrl, id }, index) => (
<img key={id} src={imageUrl} alt={`banner-img-${index}`} className={s.EmblaSlide} />
))}
</div>
</div>
<div className={s.RelativeWrapper}>
<div className={s.LoginWrapper}>
{!authState && (
<div className={s.LoginBox}>
<div className={s.LoginTitle}>
<img src={KUkeyLogo} alt="KUkeyLogo" style={{ maxWidth: '17.5rem' }} />
<Typography variant="desktop" typography="heading1SB" color="darkGray1">
For KU Exchange Students
</Typography>
</div>
<div className={s.ButtonWrapper}>
<Button variant="gray" style={{ width: '100%' }} onClick={() => navigate('/register')}>
Sign Up
</Button>
<Button variant="red" style={{ width: '100%' }} onClick={() => navigate('/login')}>
Login
</Button>
</div>
</div>
)}
<div className={s.CarouselButton}>
<HiChevronLeft className={s.Icon} onClick={() => onButtonAutoplayClick(onPrevButtonClick)} />
<Typography variant="desktop" typography="body1M" color="white">
{currentSlide + 1}/<span style={{ color: 'rgba(255, 255, 255, 0.60)' }}>{banners?.length}</span>
</Typography>
<HiChevronRight className={s.Icon} onClick={() => onButtonAutoplayClick(onNextButtonClick)} />
</div>
<div className={s.MobileCarouselButton}>
<Typography mobileTypography="miniTag1M" color="white">
{currentSlide + 1}/
</Typography>
<Typography mobileTypography="miniTag1M" color="lightGray1">
{banners?.length}
</Typography>
</div>
</div>

<div className={s.CarouselButton}>
<HiChevronLeft className={s.Icon} onClick={() => onButtonAutoplayClick(onPrevButtonClick)} />
<Typography variant="desktop" typography="body1M" color="white">
{currentSlide + 1}/<span style={{ color: 'rgba(255, 255, 255, 0.60)' }}>{banners?.length}</span>
</Typography>
<HiChevronRight className={s.Icon} onClick={() => onButtonAutoplayClick(onNextButtonClick)} />
</div>
<div className={s.MobileCarouselButton}>
<Typography mobileTypography="miniTag1M" color="white">
{currentSlide + 1}/
</Typography>
<Typography mobileTypography="miniTag1M" color="lightGray1">
{banners?.length}
</Typography>
</div>
</section>
)
Expand Down
Loading