Skip to content

Commit

Permalink
refactor(app): move quiz detail page into domain/quiz/views
Browse files Browse the repository at this point in the history
  • Loading branch information
ourai committed Feb 25, 2025
1 parent e73acfe commit d3cdcab
Show file tree
Hide file tree
Showing 14 changed files with 131 additions and 125 deletions.
93 changes: 3 additions & 90 deletions src/app/quiz/[id]/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,95 +16,8 @@

'use client';

import { useSession } from 'next-auth/react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import QuizBannerPic from 'public/images/quiz-banner.png';
import { useState } from 'react';
import useSWR from 'swr';
import { QuizDetailViewWidget } from '#/domain/quiz';

import { Button } from '@/components/Button';
import { ArrowUturnLeftIcon } from '@/components/icon/solid';
import { HistoryIcon } from '@/components/Icons';
import { OViewer } from '@/components/MarkDown';
import { fetcher } from '@/utils/request';

import { CourseListViewWidget } from '#/domain/course';
import { QuizLimiterWidget, RankListViewWidget } from '#/domain/quiz';
import { useMediaUrl } from '#/state/application/hooks';

import { RankListModal } from './RankListModal';
import { Record } from './Record';

export default function Quiz({params}) {
const mediaUrl = useMediaUrl();
const [openChallenge, setOpenChallenge] = useState(false);
const [openRankList, setOpenRankList] = useState(false);
const [checkLimit, setCheckLimit] = useState(false);
const { data } = useSWR(`/ts/v1/quiz/${params.id}/index`, fetcher);
const { data: coursesList } = useSWR(`v1/learn/course/opencourse?skip=0&take=2&order=default&quiz_bind_id=${params.id}`, fetcher);
const { status } = useSession();
const router = useRouter();

return (
<QuizLimiterWidget
id={params.id}
type={data?.limit?.limit_type}
check={checkLimit}
quiz
onReset={() => setCheckLimit(false)}
>
<div className="max-md:flex max-md:flex-col max-md:gap-y-4 h-[250px] md:h-[360px] bg-cover bg-center bg-no-repeat relative" style={{ backgroundImage: `url(${QuizBannerPic.src})` }}>
<div className="md:absolute flex items-center mt-[15px] md:mt-6 mx-4 md:mx-14">
<span
onClick={() => window.history.back()}
className="transition-all hidden md:flex items-center cursor-pointer text-sm opacity-80 rounded py-2 px-3 border border-gray-1100 text-black mr-2 hover:border-gray">
<ArrowUturnLeftIcon className="h-4 w-4 mr-2" />Return
</span>
<span onClick={() => {
if (status !== 'authenticated') {
router.push(`/signin?from=/quiz/${params.id}`);
} else {
setOpenChallenge(true);
}
}} className="cursor-pointer transition-all flex text-sm items-center opacity-80 rounded py-2 px-3 border border-gray-1100 text-black hover:border-gray">
<HistoryIcon className="mr-2" />Challenge Record
</span>
</div>
<div className="flex items-center justify-center md:pt-9 md:pb-4">
{data?.quiz_user?.user_avatar && mediaUrl && (
<Image
className="h-7 w-7 rounded object-cover mr-2"
height={24}
width={24}
alt={'user_avatar'}
src={mediaUrl + data?.quiz_user?.user_avatar}
/>
)}
<p className="opacity-90 max-md:text-[18px]">by <a href={`/u/${data?.quiz_user?.user_handle}`}><strong>{data?.quiz_user?.user_nick_name}</strong></a></p>
</div>
<h1 className="text-[28px] md:text-[42px] leading-[32px] md:leading-[52px] text-center max-md:px-6 md:max-w-[692px] mx-auto">{data?.title}</h1>
</div>
<div className="max-w-[800px] mx-auto bg-white rounded-xl p-6 md:px-9 md:pt-10 md:pb-6 relative z-[2] md:top-[-155px]">
<h5 className="text-lg mb-4 md:mb-3">Quiz Describe</h5>
<OViewer value={data?.describe} />
<Button
onClick={() => setCheckLimit(true)}
className="mt-4 md:mt-6 mb-9 md:mb-10 !font-bold px-[64px] !text-base max-md:w-full">
Challenge now
</Button>
<RankListViewWidget rank={data?.my_rank} list={data?.rank}/>
<p className="text-sm text-center mt-6 cursor-pointer" onClick={()=>{setOpenRankList(true);}}><strong>{data?.user_num}</strong> builders have participated</p>
</div>
{
coursesList?.count > 0 && (
<div className="max-w-[800px] max-md:mt-9 mx-6 md:mx-auto relative md:top-[-105px] max-md:pb-14">
<h3 className="text-[18px] max-md:leading-[24px] md:text-lg mb-6">Related courses</h3>
<CourseListViewWidget className="gap-y-6 md:gap-4 md:grid-cols-2" data={coursesList?.list} />
</div>)
}
<RankListModal quizId={params.id} shown={openRankList} onClose={() => setOpenRankList(false)} rank={data?.my_rank}/>
<Record quizId={params.id} shown={openChallenge} onClose={() => setOpenChallenge(false)} />
</QuizLimiterWidget>
);
export default function QuizDetailPage({ params }) {
return <QuizDetailViewWidget quizId={params.id} />;
}
2 changes: 0 additions & 2 deletions src/domain/course/views/course-detail/ChapterSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
* limitations under the License.
*/

'use client';

import BigNumber from 'bignumber.js';
import { useRouter } from 'next/navigation';
import { useMemo } from 'react';
Expand Down
5 changes: 1 addition & 4 deletions src/domain/quiz/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@
* limitations under the License.
*/

export { fetchRankList } from './repository';

export { default as QuizLimiterWidget } from './widgets/quiz-limiter';

export { default as QuizListViewWidget } from './views/quiz-list';
export { default as RankListViewWidget } from './views/rank-list';
export { default as RecordListViewWidget } from './views/record-list';
export { default as QuizDetailViewWidget } from './views/quiz-detail';
111 changes: 111 additions & 0 deletions src/domain/quiz/views/quiz-detail/QuizDetail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Copyright 2024 OpenBuild
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { useSession } from 'next-auth/react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import QuizBannerPic from 'public/images/quiz-banner.png';
import { useState } from 'react';
import useSWR from 'swr';

import { Button } from '@/components/Button';
import { ArrowUturnLeftIcon } from '@/components/icon/solid';
import { HistoryIcon } from '@/components/Icons';
import { OViewer } from '@/components/MarkDown';
import { fetcher } from '@/utils/request';

import { useMediaUrl } from '#/state/application/hooks';

import { CourseListViewWidget } from '../../../course';
import QuizLimiterWidget from '../../widgets/quiz-limiter';
import RankList from './RankList';
import RankListModal from './RankListModal';
import RecordListModal from './RecordListModal';

function QuizDetailView({ quizId }) {
const mediaUrl = useMediaUrl();
const [openChallenge, setOpenChallenge] = useState(false);
const [openRankList, setOpenRankList] = useState(false);
const [checkLimit, setCheckLimit] = useState(false);
const { data } = useSWR(`/ts/v1/quiz/${quizId}/index`, fetcher);
const { data: coursesList } = useSWR(`v1/learn/course/opencourse?skip=0&take=2&order=default&quiz_bind_id=${quizId}`, fetcher);
const { status } = useSession();
const router = useRouter();

return (
<QuizLimiterWidget
id={quizId}
type={data?.limit?.limit_type}
check={checkLimit}
quiz
onReset={() => setCheckLimit(false)}
>
<div className="max-md:flex max-md:flex-col max-md:gap-y-4 h-[250px] md:h-[360px] bg-cover bg-center bg-no-repeat relative" style={{ backgroundImage: `url(${QuizBannerPic.src})` }}>
<div className="md:absolute flex items-center mt-[15px] md:mt-6 mx-4 md:mx-14">
<span
onClick={() => window.history.back()}
className="transition-all hidden md:flex items-center cursor-pointer text-sm opacity-80 rounded py-2 px-3 border border-gray-1100 text-black mr-2 hover:border-gray">
<ArrowUturnLeftIcon className="h-4 w-4 mr-2" />Return
</span>
<span onClick={() => {
if (status !== 'authenticated') {
router.push(`/signin?from=/quiz/${quizId}`);
} else {
setOpenChallenge(true);
}
}} className="cursor-pointer transition-all flex text-sm items-center opacity-80 rounded py-2 px-3 border border-gray-1100 text-black hover:border-gray">
<HistoryIcon className="mr-2" />Challenge Record
</span>
</div>
<div className="flex items-center justify-center md:pt-9 md:pb-4">
{data?.quiz_user?.user_avatar && mediaUrl && (
<Image
className="h-7 w-7 rounded object-cover mr-2"
height={24}
width={24}
alt={'user_avatar'}
src={mediaUrl + data?.quiz_user?.user_avatar}
/>
)}
<p className="opacity-90 max-md:text-[18px]">by <a href={`/u/${data?.quiz_user?.user_handle}`}><strong>{data?.quiz_user?.user_nick_name}</strong></a></p>
</div>
<h1 className="text-[28px] md:text-[42px] leading-[32px] md:leading-[52px] text-center max-md:px-6 md:max-w-[692px] mx-auto">{data?.title}</h1>
</div>
<div className="max-w-[800px] mx-auto bg-white rounded-xl p-6 md:px-9 md:pt-10 md:pb-6 relative z-[2] md:top-[-155px]">
<h5 className="text-lg mb-4 md:mb-3">Quiz Describe</h5>
<OViewer value={data?.describe} />
<Button
onClick={() => setCheckLimit(true)}
className="mt-4 md:mt-6 mb-9 md:mb-10 !font-bold px-[64px] !text-base max-md:w-full">
Challenge now
</Button>
<RankList rank={data?.my_rank} list={data?.rank}/>
<p className="text-sm text-center mt-6 cursor-pointer" onClick={()=>{setOpenRankList(true);}}><strong>{data?.user_num}</strong> builders have participated</p>
</div>
{
coursesList?.count > 0 && (
<div className="max-w-[800px] max-md:mt-9 mx-6 md:mx-auto relative md:top-[-105px] max-md:pb-14">
<h3 className="text-[18px] max-md:leading-[24px] md:text-lg mb-6">Related courses</h3>
<CourseListViewWidget className="gap-y-6 md:gap-4 md:grid-cols-2" data={coursesList?.list} />
</div>)
}
<RankListModal quizId={quizId} shown={openRankList} onClose={() => setOpenRankList(false)} rank={data?.my_rank}/>
<RecordListModal quizId={quizId} shown={openChallenge} onClose={() => setOpenChallenge(false)} />
</QuizLimiterWidget>
);
}

export default QuizDetailView;
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Rank1Icon from './rank-1.svg';
import Rank2Icon from './rank-2.svg';
import Rank3Icon from './rank-3.svg';

export default function RankListView({ rank, list }) {
export default function RankList({ rank, list }) {
const mediaUrl = useMediaUrl();

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import Loader from '@/components/Loader';
import { Modal } from '@/components/Modal';
import useMounted from '@/hooks/useMounted';

import { fetchRankList, RankListViewWidget } from '#/domain/quiz';
import { fetchRankList } from '../../repository';
import RankList from './RankList';

export function RankListModal({ quizId, shown, onClose, rank }) {
function RankListModal({ quizId, shown, onClose, rank }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);

Expand All @@ -46,8 +47,10 @@ export function RankListModal({ quizId, shown, onClose, rank }) {
/>
<div className="md:h-[600px] h-[400px] flex flex-col overflow-y-auto rounded-inherit overflow-hidden">
{loading && <Loader classname="mr-2" />}
<RankListViewWidget rank={rank} list={data} />
<RankList rank={rank} list={data} />
</div>
</Modal>
);
}

export default RankListModal;
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { formatTime, fromUtcOffset, formatTimeMeridiem } from '@/utils/date';

import { useMediaUrl } from '#/state/application/hooks';

function RecordListView({ data = [] }) {
function RecordList({ data = [] }) {
const mediaUrl = useMediaUrl();

return (
Expand Down Expand Up @@ -76,4 +76,4 @@ function RecordListView({ data = [] }) {
);
}

export default RecordListView;
export default RecordList;
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ import useSWR from 'swr';

import { ModalCloseIcon } from '@/components/Icons';
import { Modal } from '@/components/Modal';
// import clsx from 'clsx'
import { NoData } from '@/components/NoData';
import { fetcher } from '@/utils/request';

import { RecordListViewWidget } from '#/domain/quiz';
import RecordList from './RecordList';

export function Record({quizId, shown, onClose}) {
function RecordListModal({quizId, shown, onClose}) {
const { data } = useSWR(shown ? `/ts/v1/quiz/${quizId}/answer` : null, fetcher);

return (
Expand All @@ -35,10 +34,12 @@ export function Record({quizId, shown, onClose}) {
<h3 className="text-center py-4 border-b border-gray-600">
Challenge Record
</h3>
<RecordListViewWidget data={data} />
<RecordList data={data} />
{(!data|| data?.length === 0) && <div className="pb-12"><NoData /></div>}
</div>
</div>
</Modal>
);
}

export default RecordListModal;
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
* limitations under the License.
*/

export { default } from './RankList';
export { default } from './QuizDetail';
File renamed without changes
File renamed without changes
File renamed without changes
17 changes: 0 additions & 17 deletions src/domain/quiz/views/record-list/index.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/entry/pages/quiz-list/QuizListPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import QuizBannerPic from 'public/images/quiz-banner.svg';
import { useState } from 'react';

import { ArrowRightIcon } from '@/components/Icons';
import useMounted from '@/hooks/useMounted';

import { QuizListViewWidget } from '#/domain/quiz';
import useMounted from '#/shared/hooks/useMounted';

import StartOnOpenBuild from '../../components/StartOnOpenBuild';
import QuizS1 from './quiz-s-1.svg';
Expand Down

0 comments on commit d3cdcab

Please sign in to comment.