diff --git a/packages/web/public/temp/student-fee.svg b/packages/web/public/temp/student-fee.svg
new file mode 100644
index 0000000..574ab7b
--- /dev/null
+++ b/packages/web/public/temp/student-fee.svg
@@ -0,0 +1,14 @@
+
diff --git a/packages/web/src/app/my/student-fee/page.tsx b/packages/web/src/app/my/student-fee/page.tsx
new file mode 100644
index 0000000..867f396
--- /dev/null
+++ b/packages/web/src/app/my/student-fee/page.tsx
@@ -0,0 +1,85 @@
+"use client";
+
+import React, { useState } from "react";
+
+import Image from "next/image";
+
+import FlexWrapper from "@sparcs-students/web/common/components/FlexWrapper";
+import Typography from "@sparcs-students/web/common/components/Typography";
+import StudentFeeChange from "@sparcs-students/web/features/studentFee/components/StudentFeeChange";
+import StudentFeeStatus from "@sparcs-students/web/features/studentFee/components/StudentFeeStatus";
+
+const mockSemesters = [
+ {
+ year: 2023,
+ semester: "봄",
+ },
+ {
+ year: 2023,
+ semester: "가을",
+ },
+ {
+ year: 2024,
+ semester: "봄",
+ },
+ {
+ year: 2024,
+ semester: "가을",
+ },
+];
+
+const mockChangeDate = new Date("2025-09-08");
+const mockSemesterNow = {
+ year: 2024,
+ semester: "가을",
+};
+
+const StudentFee = () => {
+ const [payment, setPayment] = useState(true);
+
+ return (
+
+
+ 학생회비 납부 조회
+
+
+
+ 학생회비 납부 학기
+
+
+
+ 2015년 가을까지 모든 재적학기는 회비를 납부한 것으로 인정되며, 현재
+ 기록에 포함되어 있지 않습니다.
그 외 기록이 누락 된 경우
+ kaistua@kaist.ac.kr로 문의해 주십시오.
+ 8회 학생회비를 납부 완료하신 학우분들은 별도의 공제 신청 없이 자동
+ 공제가 적용됩니다.
+
+
+
+
+ 학생회비 공제 / 미공제 변경
+
+ = 8}
+ setPayment={setPayment}
+ />
+
+
+
+ 학생회비 관련 공지사항
+
+ {/* TODO: 임시 이미지 */}
+
+
+
+ );
+};
+export default StudentFee;
diff --git a/packages/web/src/common/components/FlexWrapper.tsx b/packages/web/src/common/components/FlexWrapper.tsx
new file mode 100644
index 0000000..ac7bad3
--- /dev/null
+++ b/packages/web/src/common/components/FlexWrapper.tsx
@@ -0,0 +1,22 @@
+import isPropValid from "@emotion/is-prop-valid";
+import styled from "styled-components";
+
+interface FlexWrapperProps {
+ direction: "row" | "column";
+ gap: number;
+ justify?: string;
+ padding?: string;
+}
+
+const FlexWrapper = styled.div.withConfig({
+ shouldForwardProp: prop => isPropValid(prop),
+})`
+ display: flex;
+ position: relative;
+ flex-direction: ${({ direction }) => direction};
+ gap: ${({ gap }) => `${gap}px`};
+ justify-content: ${({ justify }) => justify ?? "flex-start"};
+ padding: ${({ padding }) => padding ?? 0};
+`;
+
+export default FlexWrapper;
diff --git a/packages/web/src/common/components/Typography.tsx b/packages/web/src/common/components/Typography.tsx
index 6cd310d..9eb5e84 100644
--- a/packages/web/src/common/components/Typography.tsx
+++ b/packages/web/src/common/components/Typography.tsx
@@ -1,7 +1,9 @@
-import { Theme } from "@sparcs-students/web/styles/themes";
import React from "react";
+
import styled from "styled-components";
+import { Theme } from "@sparcs-students/web/styles/themes";
+
interface TypographyPropsBase extends React.HTMLAttributes {
children: React.ReactNode;
}
@@ -45,7 +47,8 @@ interface TypographyProps extends TypographyPropsBase {
const TypographyInner = styled.div`
color: ${({ color, theme }) =>
color ? getColorFromTheme(theme, color) : "inherit"};
- font-family: ${({ theme, ff }) => (ff ? theme.fonts.FAMILY[ff] : "inherit")};
+ font-family: ${({ theme, ff }) =>
+ ff ? theme.fonts.FAMILY[ff] : theme.fonts.FAMILY.PRETENDARD};
font-size: ${({ fs }) => (fs ? `${fs}px` : "inherit")};
line-height: ${({ lh }) => (lh ? `${lh}px` : "inherit")};
font-weight: ${({ fw, theme }) => (fw ? theme.fonts.WEIGHT[fw] : "inherit")};
diff --git a/packages/web/src/features/studentFee/components/StudentFeeChange.tsx b/packages/web/src/features/studentFee/components/StudentFeeChange.tsx
new file mode 100644
index 0000000..3aba15a
--- /dev/null
+++ b/packages/web/src/features/studentFee/components/StudentFeeChange.tsx
@@ -0,0 +1,87 @@
+import React from "react";
+import Button from "@sparcs-students/web/common/components/Buttons/Button";
+import FlexWrapper from "@sparcs-students/web/common/components/FlexWrapper";
+import Typography from "@sparcs-students/web/common/components/Typography";
+import { formatSimpleDate } from "@sparcs-students/web/utils/Date/formatDate";
+import styled from "styled-components";
+import { Semester } from "@sparcs-students/web/types/semester.type";
+
+interface StudentFeeChangeProp {
+ changeDate: Date;
+ semesterNow: Semester;
+ paymentStatus: boolean;
+ finishPayment: boolean;
+ setPayment: (payment: boolean) => void;
+}
+
+const StudentFeeChangeWrapper = styled.div`
+ width: 840px; // TODO: 반응형
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ gap: 24px;
+ border-top: 1px solid ${({ theme }) => theme.colors.GRAY[400]};
+ border-bottom: 1px solid ${({ theme }) => theme.colors.GRAY[400]};
+ padding: 20px 12px;
+ align-items: center;
+`;
+
+const ButtonWrapper = styled.div`
+ width: 100px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const StudentFeeChange: React.FC = ({
+ changeDate,
+ semesterNow,
+ paymentStatus,
+ finishPayment,
+ setPayment,
+}) => {
+ const isChangeable = changeDate >= new Date();
+
+ return (
+
+
+
+ {semesterNow.year} {semesterNow.semester}학기
+
+ {isChangeable ? (
+
+ {formatSimpleDate(changeDate)}까지 변경 가능
+
+ ) : (
+
+ 변경 불가 기간
+
+ )}
+
+ {finishPayment ? (
+
+ 학생회비 8회 납부 완료자
+
+ ) : (
+
+ 학생회비 {paymentStatus ? "납부 (공제)" : "미납부 (미공제)"}{" "}
+ {isChangeable ? "예정" : ""}
+
+ )}
+
+ {isChangeable && !finishPayment ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
+export default StudentFeeChange;
diff --git a/packages/web/src/features/studentFee/components/StudentFeeStatus.tsx b/packages/web/src/features/studentFee/components/StudentFeeStatus.tsx
new file mode 100644
index 0000000..7056bd9
--- /dev/null
+++ b/packages/web/src/features/studentFee/components/StudentFeeStatus.tsx
@@ -0,0 +1,33 @@
+import React from "react";
+import styled from "styled-components";
+import { Semester } from "@sparcs-students/web/types/semester.type";
+import StudentFeePaid from "./_atomic/StudentFeePaid";
+import StudentFeeNotPaid from "./_atomic/StudentFeeNotPaid";
+
+interface StudentFeeStatusProp {
+ semesters: Semester[];
+}
+
+const StudentFeeStatusWrapper = styled.div`
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ // TODO: 반응형
+ gap: 24px;
+`;
+
+const StudentFeeStatus: React.FC = ({ semesters }) => (
+
+ {semesters.map(semester => (
+
+ ))}
+ {Array.from({ length: 8 - semesters.length }).map(_ => (
+
+ ))}
+
+);
+
+export default StudentFeeStatus;
diff --git a/packages/web/src/features/studentFee/components/_atomic/StudentFeeNotPaid.tsx b/packages/web/src/features/studentFee/components/_atomic/StudentFeeNotPaid.tsx
new file mode 100644
index 0000000..a808dc0
--- /dev/null
+++ b/packages/web/src/features/studentFee/components/_atomic/StudentFeeNotPaid.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+
+import Typography from "@sparcs-students/web/common/components/Typography";
+import styled from "styled-components";
+
+const StudentFeeNotPaidWrapper = styled.div`
+ width: 192px;
+ height: 60px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: ${({ theme }) => theme.colors.GRAY[400]};
+ border-radius: 20px;
+`;
+
+const StudentFeeNotPaid: React.FC = () => (
+
+
+ -
+
+
+);
+export default StudentFeeNotPaid;
diff --git a/packages/web/src/features/studentFee/components/_atomic/StudentFeePaid.tsx b/packages/web/src/features/studentFee/components/_atomic/StudentFeePaid.tsx
new file mode 100644
index 0000000..e7965b5
--- /dev/null
+++ b/packages/web/src/features/studentFee/components/_atomic/StudentFeePaid.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+
+import Typography from "@sparcs-students/web/common/components/Typography";
+import { Semester } from "@sparcs-students/web/types/semester.type";
+import styled from "styled-components";
+
+const StudentFeePaidWrapper = styled.div`
+ width: 192px;
+ height: 60px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: ${({ theme }) => theme.colors.GREEN[100]};
+ border-radius: 20px;
+`;
+
+const StudentFeePaid: React.FC = ({ year, semester }) => (
+
+
+ {year} {semester}학기
+
+
+);
+export default StudentFeePaid;
diff --git a/packages/web/src/styles/themes/colors.ts b/packages/web/src/styles/themes/colors.ts
index be6d447..a6f6007 100644
--- a/packages/web/src/styles/themes/colors.ts
+++ b/packages/web/src/styles/themes/colors.ts
@@ -27,6 +27,10 @@ const colors = {
200: "#F5A3A8",
700: "#B7202A",
},
+
+ BLUE: {
+ 900: "#3A2682",
+ },
};
export default colors;
diff --git a/packages/web/src/types/semester.type.ts b/packages/web/src/types/semester.type.ts
new file mode 100644
index 0000000..cf351a2
--- /dev/null
+++ b/packages/web/src/types/semester.type.ts
@@ -0,0 +1,4 @@
+export type Semester = {
+ year: number;
+ semester: string;
+};
diff --git a/packages/web/src/utils/Date/formatDate.ts b/packages/web/src/utils/Date/formatDate.ts
new file mode 100644
index 0000000..f35ffaf
--- /dev/null
+++ b/packages/web/src/utils/Date/formatDate.ts
@@ -0,0 +1,7 @@
+import { format } from "date-fns";
+import { ko } from "date-fns/locale";
+
+const formatSimpleDate = (date: Date) =>
+ format(date, "M월 d일", { locale: ko });
+
+export { formatSimpleDate };