diff --git a/README.md b/README.md index 86f8dcc7..64dc7e54 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,6 @@ - 성적표 기반의 맞춤형 졸업 요건 검사 결과를 제공합니다. -### 🎃 조회수 & 사용자 통계 - - - ### 🎃 사용자 후기 diff --git a/app/(sub-page)/anonymous/components/anonymous-container.tsx b/app/(sub-page)/anonymous/components/anonymous-container.tsx new file mode 100644 index 00000000..c4871db4 --- /dev/null +++ b/app/(sub-page)/anonymous/components/anonymous-container.tsx @@ -0,0 +1,40 @@ +'use client'; +import useFunnel from '@/app/hooks/useFunnel'; +import SignUpTerm from '../../sign-up/components/sign-up-terms'; +import { AnonymousResultType } from '@/app/utils/parser/anonymous'; +import AnonymousUpload from './anonymous-upload'; +import { FormState } from '@/app/ui/view/molecule/form/form-root'; +import { useRouter } from 'next/navigation'; +import { useAnonymousContext } from '../result/provider'; + +function isAnonymousResultType(formdata: any): formdata is AnonymousResultType { + return formdata && 'graduationResult' in formdata && 'user' in formdata; +} + +export default function AnonymousContainer() { + const router = useRouter(); + const { setResult } = useAnonymousContext(); + const { Funnel, setStep } = useFunnel<'terms' | 'form'>('terms'); + + return ( + + + { + setStep('form'); + }} + /> + + + { + if (isAnonymousResultType(formState?.value)) { + setResult(formState.value); + router.push('/anonymous/result'); + } + }} + /> + + + ); +} diff --git a/app/(sub-page)/anonymous/components/anonymous-upload.tsx b/app/(sub-page)/anonymous/components/anonymous-upload.tsx new file mode 100644 index 00000000..81d0a3ab --- /dev/null +++ b/app/(sub-page)/anonymous/components/anonymous-upload.tsx @@ -0,0 +1,10 @@ +import UploadTakenLectureAnonymous from '@/app/ui/lecture/upload-taken-lecture/upload-taken-lectrue-anonymous'; +import { FormState } from '@/app/ui/view/molecule/form/form-root'; + +interface AnonymousUploadProp { + onNext?: (formState?: FormState) => void; +} + +export default function AnonymousUpload({ onNext }: AnonymousUploadProp) { + return ; +} diff --git a/app/(sub-page)/anonymous/layout.tsx b/app/(sub-page)/anonymous/layout.tsx new file mode 100644 index 00000000..5f3229e8 --- /dev/null +++ b/app/(sub-page)/anonymous/layout.tsx @@ -0,0 +1,10 @@ +import ContentContainer from '@/app/ui/view/atom/content-container/content-container'; +import { AnonymousProvider } from './result/provider'; + +interface AnonymousLayoutProp { + children: React.ReactNode; +} + +export default function AnonymousLayout({ children }: AnonymousLayoutProp) { + return {children}; +} diff --git a/app/(sub-page)/anonymous/page.tsx b/app/(sub-page)/anonymous/page.tsx new file mode 100644 index 00000000..e65e2da8 --- /dev/null +++ b/app/(sub-page)/anonymous/page.tsx @@ -0,0 +1,19 @@ +import { Metadata } from 'next'; +import AnonymousContainer from './components/anonymous-container'; +import { Suspense } from 'react'; +import ContentContainer from '@/app/ui/view/atom/content-container/content-container'; + +export const metadata: Metadata = { + title: '비회원으로 검사하기', + description: '로그인없이 졸업요건을 간편하게 검사해 보세요.', +}; + +export default function AnonymousPage() { + return ( + + + + + + ); +} diff --git a/app/(sub-page)/anonymous/result/component/anonymous-result.tsx b/app/(sub-page)/anonymous/result/component/anonymous-result.tsx new file mode 100644 index 00000000..bf36acbb --- /dev/null +++ b/app/(sub-page)/anonymous/result/component/anonymous-result.tsx @@ -0,0 +1,42 @@ +'use client'; +import { ResultCategoryViewer } from '@/app/ui/result/result-category/result-category'; +import UserInfoContentViewer from '@/app/ui/user/user-info-card/user-info-content/user-info-content-viewer'; +import { AnonymousResultType, parseCredit, parseCreditDetailInfo, parseUserInfo } from '@/app/utils/parser/anonymous'; +import { useAnonymousContext } from '../provider'; +import { useRouter } from 'next/navigation'; +import ResultCategoryDetailDialog from '@/app/ui/result/result-category-detail/result-category-detail-dialog'; +import { ResultCategoryDetailInfoViewer } from '@/app/ui/result/result-category-detail/result-category-detail-info'; +import { ResultCategoryKey } from '@/app/utils/key/result-category.key'; + +interface AnonymousResultProp { + category: ResultCategoryKey; +} + +const AnonymousResult = ({ category }: AnonymousResultProp) => { + const { result } = useAnonymousContext(); + const router = useRouter(); + if (!result) { + router.back(); + return <>; + } + + return ( +
+
+ +
+ + {category ? ( + + + + ) : null} +
+ ); +}; + +export default AnonymousResult; diff --git a/app/(sub-page)/anonymous/result/page.tsx b/app/(sub-page)/anonymous/result/page.tsx new file mode 100644 index 00000000..cf98e2fe --- /dev/null +++ b/app/(sub-page)/anonymous/result/page.tsx @@ -0,0 +1,37 @@ +import { Metadata } from 'next/types'; +import AnonymousResult from './component/anonymous-result'; +import { ResultCategoryKey } from '@/app/utils/key/result-category.key'; +import ContentContainer from '@/app/ui/view/atom/content-container/content-container'; + +export const metadata: Metadata = { + title: '졸업 요건 검사 결과', + description: '회원가입없이 졸업사정 결과와, 카테고리별 미이수 / 이수 과목정보 및 잔여학점을 확인해요', + openGraph: { + siteName: '졸업을 부탁해', + url: 'https://mju-graduate.com/result', + images: [ + { + url: 'https://github.com/user-attachments/assets/2093a57f-af35-4280-8acb-d403341fc8ff', + width: 1200, + height: 630, + alt: 'result-page iamge', + }, + ], + }, +}; + +interface AnonymousResultPageProp { + searchParams: { category: ResultCategoryKey }; +} + +function AnonymousResultPage({ searchParams }: AnonymousResultPageProp) { + const { category } = searchParams; + + return ( + + + + ); +} + +export default AnonymousResultPage; diff --git a/app/(sub-page)/anonymous/result/provider.tsx b/app/(sub-page)/anonymous/result/provider.tsx new file mode 100644 index 00000000..fdba7402 --- /dev/null +++ b/app/(sub-page)/anonymous/result/provider.tsx @@ -0,0 +1,24 @@ +'use client'; +import { createContext, useState, ReactNode, useContext } from 'react'; +import { AnonymousResultType } from '@/app/utils/parser/anonymous'; + +interface AnonymousContextType { + result?: AnonymousResultType; + setResult: (data: AnonymousResultType) => void; +} + +export const AnonymousContext = createContext(null); + +export const AnonymousProvider = ({ children }: { children: ReactNode }) => { + const [result, setResult] = useState(); + + return {children}; +}; + +export function useAnonymousContext() { + const context = useContext(AnonymousContext); + if (!context) { + throw new Error('useAnonymousContext must be used within an AnonymousProvider'); + } + return context; +} diff --git a/app/(sub-page)/components/navigation-items.tsx b/app/(sub-page)/components/navigation-items.tsx index c1aefd3e..0f741295 100644 --- a/app/(sub-page)/components/navigation-items.tsx +++ b/app/(sub-page)/components/navigation-items.tsx @@ -14,7 +14,10 @@ export default async function NavigationItems() { ) : ( - + <> + + + )} 3. 우측 상단 조회버튼 → 프린트 아이콘
4. 인쇄 정보의 대상(PDF로 저장) 설정 → 하단 저장 버튼
5. 저장한 파일 업로드
-
- • 회원 가입한 학번과 일치하는 학번의 성적표를 입력해야 합니다. -
diff --git a/app/(sub-page)/grade-upload/page.tsx b/app/(sub-page)/grade-upload/page.tsx index 140f400f..d93839e5 100644 --- a/app/(sub-page)/grade-upload/page.tsx +++ b/app/(sub-page)/grade-upload/page.tsx @@ -12,7 +12,7 @@ export const metadata: Metadata = { export default function GradeUploadPage() { return ( - + diff --git a/app/business/services/lecture/taken-lecture.command.ts b/app/business/services/lecture/taken-lecture.command.ts index 33b69e6d..1a80cd02 100644 --- a/app/business/services/lecture/taken-lecture.command.ts +++ b/app/business/services/lecture/taken-lecture.command.ts @@ -4,6 +4,8 @@ import { API_PATH } from '../../api-path'; import { BadRequestError, HttpError } from '@/app/utils/http/http-error'; import { instance } from '@/app/utils/api/instance'; import { ERROR_CODE } from '@/app/utils/api/constant'; +import { revalidateTag } from 'next/cache'; +import { TAG } from '@/app/utils/http/tag'; export const registerUserGrade = async (prevState: FormState, formData: FormData) => { try { @@ -33,6 +35,47 @@ export const registerUserGrade = async (prevState: FormState, formData: FormData } }; +export const registerAnonymousGrade = async (prevState: FormState, formData: FormData) => { + const engLv = formData.get('engLv'); + const file = formData.get('file'); + if (!(file instanceof File)) { + return { + isSuccess: false, + isFailure: true, + validationError: {}, + message: '등록할 수 없는 파일입니다.', + }; + } + + const gradePDF = new FormData(); + gradePDF.append('file', file); + + const parsingText = await parsePDFtoText(gradePDF); + const res = await fetch(`${API_PATH.graduations}/check`, { + method: 'POST', + body: JSON.stringify({ engLv, parsingText }), + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!res.ok) { + return { + isSuccess: false, + isFailure: true, + validationError: {}, + message: 'fail upload grade', + }; + } + return { + isSuccess: true, + isFailure: false, + validationError: {}, + message: 'success upload grade', + value: await res.json(), + }; +}; + export const parsePDFtoText = async (formData: FormData) => { return (await fetch(API_PATH.parsePDFtoText, { method: 'POST', body: formData })).text(); }; @@ -42,7 +85,6 @@ export const deleteTakenLecture = async (lectureId: number) => { await instance.delete(`${API_PATH.takenLectures}/${lectureId}`, { responseType: 'text', }); - return { isSuccess: true, }; @@ -66,6 +108,7 @@ export const addTakenLecture = async (lectureId: string) => { responseType: 'text', }, ); + revalidateTag(TAG.GET_TAKEN_LECTURES); return { isSuccess: true, isFailure: false, diff --git a/app/business/services/lecture/taken-lecture.query.ts b/app/business/services/lecture/taken-lecture.query.ts index ffc4a484..2885e07e 100644 --- a/app/business/services/lecture/taken-lecture.query.ts +++ b/app/business/services/lecture/taken-lecture.query.ts @@ -1,5 +1,6 @@ import { instance } from '@/app/utils/api/instance'; import { API_PATH } from '../../api-path'; +import { TAG } from '@/app/utils/http/tag'; export interface TakenLecturesResponse { totalCredit: number; @@ -17,6 +18,10 @@ export interface TakenLectureInfoResponse { } export const fetchTakenLectures = async () => { - const response = await instance.get(API_PATH.takenLectures); + const response = await instance.get(API_PATH.takenLectures, { + next: { + tags: [TAG.GET_TAKEN_LECTURES], + }, + }); return response.data; }; diff --git a/app/page.tsx b/app/page.tsx index e0f10200..3ba68c08 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -42,9 +42,11 @@ export default function HomePage() {
명지인을 위한 간편 졸업요건 검사 사이트

- -