diff --git a/web/app/chat/layout.tsx b/web/app/chat/layout.tsx index 96f0d8d7..ef128dd9 100644 --- a/web/app/chat/layout.tsx +++ b/web/app/chat/layout.tsx @@ -10,7 +10,7 @@ export default function ChatPageLayout({ return (
-
{children}
+
{children}
); diff --git a/web/app/chat/page.tsx b/web/app/chat/page.tsx index ac10655a..6a9e3cb2 100644 --- a/web/app/chat/page.tsx +++ b/web/app/chat/page.tsx @@ -18,7 +18,9 @@ function ChatListContent() { return state.current === "loading" ? ( ) : state.current === "error" ? ( -

Error: {state.error.message}

+

+ エラーが発生しました。リロードしてください。 +

) : ( ); diff --git a/web/app/friends/page.tsx b/web/app/friends/page.tsx index 412fa5c4..35329816 100644 --- a/web/app/friends/page.tsx +++ b/web/app/friends/page.tsx @@ -18,17 +18,16 @@ export default function Friends() {
-
- {/* コンテンツ部分 */} -
+
{activeTab === "matching" ? : }
diff --git a/web/app/home/layout.tsx b/web/app/home/layout.tsx index 792f2828..6b41946a 100644 --- a/web/app/home/layout.tsx +++ b/web/app/home/layout.tsx @@ -10,7 +10,7 @@ export default function HomePageLayout({ return (
-
{children}
+
{children}
); diff --git a/web/app/home/page.tsx b/web/app/home/page.tsx index b2d4a8eb..a8ca5867 100644 --- a/web/app/home/page.tsx +++ b/web/app/home/page.tsx @@ -88,7 +88,7 @@ export default function Home() { return ( -
+
{displayedUser && ( <>
diff --git a/web/app/signup/common.tsx b/web/app/signup/common.tsx index c4cdbe32..6cd4a870 100644 --- a/web/app/signup/common.tsx +++ b/web/app/signup/common.tsx @@ -2,7 +2,7 @@ export type Caller = "registration" | "configMenu"; export type StepProps = { onSave: (t: T) => void; prev?: T; - caller: Caller; + caller?: Caller; }; export type BackProp = { diff --git a/web/app/signup/page.tsx b/web/app/signup/page.tsx index 47d14a1a..155cb036 100644 --- a/web/app/signup/page.tsx +++ b/web/app/signup/page.tsx @@ -12,11 +12,12 @@ import Step1 from "./steps/step1_profile"; import Step2, { type Step2Data } from "./steps/step2_img"; import Confirmation from "./steps/step3_confirmation"; import Step4 from "./steps/step4_course"; +import Step5 from "./steps/step5_interests"; function Registration() { const { enqueueSnackbar } = useSnackbar(); const router = useRouter(); - const [step, setStep] = useState<1 | 2 | 3 | 4>(1); + const [step, setStep] = useState<1 | 2 | 3 | 4 | 5>(1); const [step1Data, setStep1Data] = useState(); const [step2Data, setStep2Data] = useState(); @@ -71,7 +72,9 @@ function Registration() { /> ); case 4: - return ; + return setStep(5)} />; + case 5: + return setStep(4)} />; } } export default function RegistrationPage() { @@ -79,7 +82,7 @@ export default function RegistrationPage() {
-
+
diff --git a/web/app/signup/steps/step1_profile.tsx b/web/app/signup/steps/step1_profile.tsx index 485bffde..4b84521e 100644 --- a/web/app/signup/steps/step1_profile.tsx +++ b/web/app/signup/steps/step1_profile.tsx @@ -43,7 +43,7 @@ export default function Step1({ onSave, prev, caller }: StepProps) { }, [selectedFaculty, setValue, resetField]); return ( <> -
+

アカウント設定

diff --git a/web/app/signup/steps/step4_course.tsx b/web/app/signup/steps/step4_course.tsx index 4498e917..1193426e 100644 --- a/web/app/signup/steps/step4_course.tsx +++ b/web/app/signup/steps/step4_course.tsx @@ -1,9 +1,9 @@ -import Link from "next/link"; import { useMyID } from "~/api/user"; import FullScreenCircularProgress from "~/components/common/FullScreenCircularProgress"; import EditableCoursesTable from "~/components/course/EditableCoursesTable"; +import type { StepProps } from "../common"; -export default function Step4() { +export default function Step4({ onSave }: StepProps) { const { state } = useMyID(); return (
@@ -23,9 +23,13 @@ export default function Step4() {
- +
); diff --git a/web/app/signup/steps/step5_interests.tsx b/web/app/signup/steps/step5_interests.tsx new file mode 100644 index 00000000..7b60ea04 --- /dev/null +++ b/web/app/signup/steps/step5_interests.tsx @@ -0,0 +1,237 @@ +"use client"; + +import type { InterestSubject } from "common/types"; +import { useRouter } from "next/navigation"; +import { enqueueSnackbar } from "notistack"; +import { useEffect, useState } from "react"; +import { MdAdd, MdClose } from "react-icons/md"; +import FullScreenCircularProgress from "~/components/common/FullScreenCircularProgress"; +import { useAlert } from "~/components/common/alert/AlertProvider"; +import * as subject from "../../../api/subject"; +import type { BackProp } from "../common"; + +export default function Step5({ back }: BackProp) { + const { state } = subject.useMyInterests(); + const data = state.data; + const error = state.current === "error" ? state.error : null; + const loading = state.current === "loading"; + + const router = useRouter(); + const { showAlert } = useAlert(); + + const [allSubjects, setAllSubjects] = useState([]); + const [filteredSubjects, setFilteredSubjects] = useState( + [], + ); + const [draftSubjects, setDraftSubjects] = useState( + data ?? [], + ); + const [isOpen, setIsOpen] = useState(false); + const [newSubjectName, setNewSubjectName] = useState(""); + + useEffect(() => { + getSubjects(); + }, []); + + useEffect(() => { + setDraftSubjects(data ?? []); + }, [data]); + + async function getSubjects() { + const subjects = await subject.getAll(); + setAllSubjects(subjects); + setFilteredSubjects(subjects); + } + + async function updateInterests(data: { + interestSubjects: InterestSubject[]; + }) { + const ids = data.interestSubjects.map((d) => d.id); + const result = await subject.update(ids); + if (!result.ok) { + enqueueSnackbar("興味分野の保存に失敗しました", { variant: "error" }); + } else { + enqueueSnackbar("興味分野を保存しました", { variant: "success" }); + } + } + + async function createSubject(name: string) { + const result = await subject.create(name); + if (!result.ok) { + enqueueSnackbar("興味分野の作成に失敗しました", { variant: "error" }); + } else { + enqueueSnackbar("興味分野を作成しました", { variant: "success" }); + } + } + + function handleBack() { + // TODO: 差分がないときは確認なしで戻る + showAlert({ + AlertMessage: "変更がある場合は、破棄されます。", + subAlertMessage: "本当にページを移動しますか?", + yesMessage: "移動", + clickYes: () => { + back(); + }, + }); + } + + return loading ? ( + + ) : error ? ( +

Error: {error.message}

+ ) : !data ? ( +

データがありません。

+ ) : ( + <> +
+
+
+
+ {draftSubjects.map((subject, index) => ( + + #{subject.name} + + + ))} +
+
+ { + const newFilteredSubjects = allSubjects.filter((subject) => + subject.name.includes(e.target.value.trim()), + ); + setFilteredSubjects(newFilteredSubjects); + }} + placeholder="興味分野タグを絞り込み" + className="input input-bordered w-full" + /> +
+
    + {filteredSubjects.length !== 0 ? ( + filteredSubjects + .filter( + (subject) => + !draftSubjects.some((draft) => draft.id === subject.id), + ) + .map((subject) => ( +
  • + +
  • + )) + ) : ( +
  • + 検索結果がありません +
  • + )} +
  • + +
  • +
+
+
+ + +
+
+
+ {isOpen && ( + setIsOpen(false)} + > +
+

興味分野タグの作成

+ setNewSubjectName(e.target.value)} + placeholder="タグ名を入力" + /> + {newSubjectName && ( +

+ 興味分野タグ{" "} + #{newSubjectName}{" "} + を作成します +

+ )} +
+ +
+ + +
+ +
+
+
+ )} + + ); +} diff --git a/web/app/tutorial/layout.tsx b/web/app/tutorial/layout.tsx new file mode 100644 index 00000000..d252f74e --- /dev/null +++ b/web/app/tutorial/layout.tsx @@ -0,0 +1,21 @@ +import Link from "next/link"; +import Header from "~/components/Header"; +import { NavigateByAuthState } from "~/components/common/NavigateByAuthState"; + +export default function TutorialPageLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + +
+
{children}
+
+ + ホーム画面へ + +
+ + ); +} diff --git a/web/app/tutorial/page.tsx b/web/app/tutorial/page.tsx index 7982323d..87ed8771 100644 --- a/web/app/tutorial/page.tsx +++ b/web/app/tutorial/page.tsx @@ -1,12 +1,10 @@ "use client"; -import { Navigation, Pagination } from "swiper/modules"; +import { Navigation } from "swiper/modules"; import { Swiper, SwiperSlide } from "swiper/react"; import "swiper/css"; import "swiper/css/navigation"; import "swiper/css/pagination"; -import Link from "next/link"; -import Header from "~/components/Header"; const tutorialSteps = [ { @@ -41,36 +39,26 @@ const tutorialSteps = [ export default function Tutorial() { return ( -
-
-
- - {tutorialSteps.map((step) => ( - -
-

{step.label}

- {step.label} -
-
- ))} -
-
- - ホーム画面へ - -
-
+
+ + {tutorialSteps.map((step) => ( + +
+

{step.label}

+ {step.label} +
+
+ ))} +
); } diff --git a/web/components/BottomBar.tsx b/web/components/BottomBar.tsx index 00e55bb1..ba972f3a 100644 --- a/web/components/BottomBar.tsx +++ b/web/components/BottomBar.tsx @@ -33,31 +33,31 @@ function BottomBarCell({ export default function BottomBar(props: Props) { const { activeTab } = props; return ( -
+
} + iconComponent={} /> } + iconComponent={} /> } + iconComponent={} /> } + iconComponent={} /> } + iconComponent={} />
); diff --git a/web/components/Header.tsx b/web/components/Header.tsx index 43715ac8..94ee4a15 100644 --- a/web/components/Header.tsx +++ b/web/components/Header.tsx @@ -12,15 +12,15 @@ export default function Header(props: Props) {
- + -

+

{title}

- +
diff --git a/web/components/chat/RoomWindow.tsx b/web/components/chat/RoomWindow.tsx index e5a69d3d..ecbfa406 100644 --- a/web/components/chat/RoomWindow.tsx +++ b/web/components/chat/RoomWindow.tsx @@ -24,7 +24,6 @@ export function RoomWindow(props: Props) { ); } - console.log("rendering"); useEffect(() => { (async () => { const lastM = room.messages.at(-1); @@ -146,7 +145,7 @@ export function RoomWindow(props: Props) { ); return ( - <> +
{room.matchingStatus !== "matched" && ( )} - -
+
-
- {messages && messages.length > 0 ? ( -
- {messages.map((m) => - m.isPicture ? ( - - ) : ( -
- {editingMessageId === m.id ? ( -
-