Skip to content
2 changes: 1 addition & 1 deletion web/app/chat/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function ChatPageLayout({
return (
<NavigateByAuthState type="toLoginForUnauthenticated">
<Header title="チャット/Chat" />
<div className="grow overflow-y-auto">{children}</div>
<div className="flex-1 overflow-hidden">{children}</div>
<BottomBar activeTab="3_chat" />
</NavigateByAuthState>
);
Expand Down
4 changes: 3 additions & 1 deletion web/app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ function ChatListContent() {
return state.current === "loading" ? (
<FullScreenCircularProgress />
) : state.current === "error" ? (
<p className="decoration-red">Error: {state.error.message}</p>
<p className="p-4 decoration-red">
エラーが発生しました。リロードしてください。
</p>
) : (
<RoomList roomsData={state.data} />
);
Expand Down
8 changes: 3 additions & 5 deletions web/app/friends/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,16 @@ export default function Friends() {
<div className="flex w-full border-gray-200 border-b">
<button
type="button"
className={`relative flex-1 py-2 text-center ${
className={`relative flex-1 py-3 text-center ${
activeTab === "matching" ? "text-primary" : "text-gray-600"
}`}
onClick={() => setActiveTab("matching")}
>
<span>マッチ中</span>
<span>マッチ</span>
{activeTab === "matching" && (
<span className="absolute bottom-0 left-0 h-1 w-full bg-primary" />
)}
</button>

<button
type="button"
className={`relative flex-1 py-2 text-center ${
Expand All @@ -43,8 +42,7 @@ export default function Friends() {
</button>
</div>

{/* コンテンツ部分 */}
<div className="mt-4 text-center text-gray-700 text-lg">
<div className="text-center text-gray-700 text-lg">
{activeTab === "matching" ? <NoSSRMatchings /> : <NoSSRRequests />}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion web/app/home/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function HomePageLayout({
return (
<NavigateByAuthState type="toLoginForUnauthenticated">
<Header title="ホーム/Home" />
<div className="grow overflow-y-auto">{children}</div>
<div className="grow overflow-y-auto ">{children}</div>
<BottomBar activeTab="0_home" />
</NavigateByAuthState>
);
Expand Down
2 changes: 1 addition & 1 deletion web/app/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default function Home() {

return (
<NavigateByAuthState type="toLoginForUnauthenticated">
<div className="flex h-full flex-col items-center justify-center">
<div className="flex h-full flex-col items-center justify-center p-4">
{displayedUser && (
<>
<div className="flex h-full flex-col items-center justify-center">
Expand Down
2 changes: 1 addition & 1 deletion web/app/signup/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type Caller = "registration" | "configMenu";
export type StepProps<T> = {
onSave: (t: T) => void;
prev?: T;
caller: Caller;
caller?: Caller;
};

export type BackProp = {
Expand Down
9 changes: 6 additions & 3 deletions web/app/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Step1User>();
const [step2Data, setStep2Data] = useState<Step2Data>();
Expand Down Expand Up @@ -71,15 +72,17 @@ function Registration() {
/>
);
case 4:
return <Step4 />;
return <Step4 onSave={() => setStep(5)} />;
case 5:
return <Step5 back={() => setStep(4)} />;
}
}
export default function RegistrationPage() {
return (
<NavigateByAuthState type="toHomeForAuthenticated">
<div className="flex h-screen flex-col">
<Header title="登録/Register" />
<div className="mt-14 flex-1">
<div className="flex-1">
<Registration />
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion web/app/signup/steps/step1_profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function Step1({ onSave, prev, caller }: StepProps<Step1User>) {
}, [selectedFaculty, setValue, resetField]);
return (
<>
<div className="m-4 mb-8 flex flex-col gap-4">
<div className="m-4 flex h-full flex-col gap-4">
<h1 className="text-xl">アカウント設定</h1>
<div className="flex flex-col gap-2">
<form onSubmit={handleSubmit(onSubmit)}>
Expand Down
12 changes: 8 additions & 4 deletions web/app/signup/steps/step4_course.tsx
Original file line number Diff line number Diff line change
@@ -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<void>) {
const { state } = useMyID();
return (
<div className="flex h-full flex-col">
Expand All @@ -23,9 +23,13 @@ export default function Step4() {
</div>
<div className="flex w-full justify-between p-6">
<span />
<Link href="/tutorial" className="btn btn-primary">
<button
type="button"
onClick={() => onSave()}
className="btn btn-primary"
>
次へ
</Link>
</button>
</div>
</div>
);
Expand Down
237 changes: 237 additions & 0 deletions web/app/signup/steps/step5_interests.tsx
Original file line number Diff line number Diff line change
@@ -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<InterestSubject[]>([]);
const [filteredSubjects, setFilteredSubjects] = useState<InterestSubject[]>(
[],
);
const [draftSubjects, setDraftSubjects] = useState<InterestSubject[]>(
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 ? (
<FullScreenCircularProgress />
) : error ? (
<p>Error: {error.message}</p>
) : !data ? (
<p>データがありません。</p>
) : (
<>
<div className="h-full overflow-y-scroll">
<div className="mx-auto flex h-full max-w-lg flex-col px-4">
<div className="flex-1">
<div className="flex flex-wrap gap-2 p-2">
{draftSubjects.map((subject, index) => (
<span
key={subject.id}
className="rounded-md bg-[#F7FCFF] px-2 py-1 text-md text-primary"
>
#{subject.name}
<button
type="button"
className="btn btn-circle btn-xs ml-1"
onClick={() =>
setDraftSubjects((prev) => {
const copy = [...prev];
copy.splice(index, 1);
return copy;
})
}
>
<MdClose className="text-xs" />
</button>
</span>
))}
</div>
<div className="mt-2 w-full">
<input
type="text"
onChange={(e) => {
const newFilteredSubjects = allSubjects.filter((subject) =>
subject.name.includes(e.target.value.trim()),
);
setFilteredSubjects(newFilteredSubjects);
}}
placeholder="興味分野タグを絞り込み"
className="input input-bordered w-full"
/>
</div>
<ul className="mt-2">
{filteredSubjects.length !== 0 ? (
filteredSubjects
.filter(
(subject) =>
!draftSubjects.some((draft) => draft.id === subject.id),
)
.map((subject) => (
<li key={subject.id}>
<button
type="button"
className="btn btn-ghost inline-flex h-full w-full justify-start p-2"
onClick={() =>
setDraftSubjects((prev) => [...prev, subject])
}
>
<span className="font-normal text-lg">
#{subject.name}
</span>
</button>
</li>
))
) : (
<li key="empty" className="p-2 text-gray-500">
検索結果がありません
</li>
)}
<li className="flex w-full items-center justify-center py-2">
<button
type="button"
className="btn btn-secondary px-6 font-normal"
onClick={() => setIsOpen(true)}
>
<MdAdd />
タグを新規作成
</button>
</li>
</ul>
</div>
<div className="my-2 flex justify-between">
<button type="button" className="btn " onClick={handleBack}>
前へ
</button>
<button
type="button"
className="btn btn-primary"
onClick={() => {
updateInterests({ interestSubjects: draftSubjects });
router.push("/tutorial");
}}
>
次へ
</button>
</div>
</div>
</div>
{isOpen && (
<dialog
id="add-dialog"
className="modal modal-open"
onClose={() => setIsOpen(false)}
>
<div className="modal-box">
<h3 className="mb-4 font-bold text-lg">興味分野タグの作成</h3>
<input
type="text"
className="input input-bordered my-2 w-full"
value={newSubjectName}
onChange={(e) => setNewSubjectName(e.target.value)}
placeholder="タグ名を入力"
/>
{newSubjectName && (
<p className="py-4">
興味分野タグ{" "}
<span className="text-primary">#{newSubjectName}</span>{" "}
を作成します
</p>
)}
<div className="modal-action">
<form method="dialog">
<div className="flex gap-3">
<button
type="button"
className="btn"
onClick={() => {
setIsOpen(false);
setNewSubjectName("");
}}
>
キャンセル
</button>
<button
type="button"
className="btn btn-primary"
onClick={async () => {
await createSubject(newSubjectName);
setIsOpen(false);
getSubjects();
setNewSubjectName("");
}}
>
作成
</button>
</div>
</form>
</div>
</div>
</dialog>
)}
</>
);
}
Loading
Loading