Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ea38b7d
カードのの表面のデザイン
GuY8528 Nov 30, 2024
df107f8
Merge branch 'main' of github.com:ut-code/CourseMate into feat/Card-w…
GuY8528 Dec 3, 2024
321fe54
Merge branch 'main' into feat/Card-with-interests
GuY8528 Dec 15, 2024
7a44c8d
Merge branch 'main' of github.com:ut-code/CourseMate into feat/Card-w…
GuY8528 Dec 15, 2024
13239bb
Merge branch 'feat/Card-with-interests' of github.com:ut-code/CourseM…
GuY8528 Dec 15, 2024
0f75874
Merge branch 'main' of github.com:ut-code/CourseMate into feat/Card-w…
GuY8528 Dec 15, 2024
83248dc
Card表面をV2に対応
GuY8528 Dec 15, 2024
0393d6c
Merge branch 'main' of github.com:ut-code/CourseMate into feat/Card-w…
GuY8528 Dec 28, 2024
549bb05
Merge branch 'main' of github.com:ut-code/CourseMate into feat/Card-w…
GuY8528 Dec 28, 2024
69ce1c0
Cardの表示修正
GuY8528 Dec 28, 2024
992133f
カードの表面のデザイン&表示ロジック修正
GuY8528 Dec 28, 2024
0ff56c3
ボタンクリック時の動作修正
GuY8528 Jan 21, 2025
c71cd8d
Merge branch 'main' of github.com:ut-code/CourseMate into fix/Card-de…
GuY8528 Jan 21, 2025
b91c636
フック呼び出し時のオブジェクト生成を防止
nakaterm Jan 23, 2025
affe048
temporary fix
nakaterm Jan 23, 2025
1c0a8ca
不要な console.log を削除
nakaterm Jan 23, 2025
f964855
Merge branch 'main' of github.com:ut-code/CourseMate into fix/Card-de…
nakaterm Jan 23, 2025
00a2e69
詳細情報メニューの表示
nakaterm Jan 23, 2025
82b7cb4
Merge branch 'main' into fix/Card-design
nakaterm Jan 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion server/src/functions/engines/recommendation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { UserID, UserWithCoursesAndSubjects } from "common/types";
import { prisma } from "../../database/client";
import { getCoursesByUserId } from "../../database/courses";
import * as interest from "../../database/interest";
import { getUserByID } from "../../database/users";

export async function recommendedTo(
user: UserID,
Expand All @@ -24,12 +25,15 @@ export async function recommendedTo(
const { overlap: count, ...u } = res;
if (count === null)
throw new Error("count is null: something is wrong");
// TODO: user の情報はここで再度 DB に問い合わせるのではなく、 recommend の sql で取得
const user = await getUserByID(u.id);
if (!user.ok) throw new Error("user not found");
const courses = getCoursesByUserId(u.id);
const subjects = interest.of(u.id);
return {
count: Number(count),
u: {
...u,
...user.value,
courses: await courses,
interestSubjects: await subjects,
},
Expand Down
5 changes: 1 addition & 4 deletions web/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ export function useAll(): Hook<UserWithCoursesAndSubjects[]> {
}
export function useRecommended(): UseHook<UserWithCoursesAndSubjects[]> {
const url = endpoints.recommendedUsers;
return useAuthorizedData<UserWithCoursesAndSubjects[]>(
url,
z.array(UserWithCoursesAndSubjectsSchema),
);
return useAuthorizedData<UserWithCoursesAndSubjects[]>(url, UserListSchema);
}
export function useMatched(): Hook<UserWithCoursesAndSubjects[]> {
return useCustomizedSWR("users::matched", matched, UserListSchema);
Expand Down
181 changes: 100 additions & 81 deletions web/app/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,61 +15,66 @@ import PersonDetailedMenu from "./components/PersonDetailedMenu";
export default function Home() {
const { data, error } = useRecommended();
const controls = useAnimation();
const backCardControls = useAnimation();
const [clickedButton, setClickedButton] = useState<string>("");

const [openDetailedMenu, setOpenDetailedMenu] = useState(false);
const {
state: { data: currentUser },
} = useAboutMe();

const [_, rerender] = useState({});
const [recommended, setRecommended] =
useState<Queue<UserWithCoursesAndSubjects> | null>(null);
const [recommended, setRecommended] = useState<
Queue<UserWithCoursesAndSubjects>
>(() => new Queue([]));

useEffect(() => {
if (data) setRecommended(new Queue(data));
}, [data]);

const displayedUser = recommended?.peek(0);
const nextUser = recommended?.peek(1);
const reject = useCallback(() => {
const current = recommended?.pop();
if (!current) return;
recommended?.push(current);
rerender({});
}, [recommended]);
const accept = useCallback(async () => {
const current = recommended?.pop();
if (!current) return;
request.send(current.id);
rerender({});
}, [recommended]);

const onClickClose = useCallback(() => {
setClickedButton("cross");
controls
.start({
x: [0, -1000],
transition: { duration: 0.5, times: [0, 1], delay: 0.2 },
})
.then(() => {
reject();
setClickedButton("");
controls.set({ x: 0 });
});
}, [controls, reject]);

const onClickHeart = useCallback(() => {
setClickedButton("heart");
controls
.start({
x: [0, 1000],
transition: { duration: 0.5, times: [0, 1], delay: 0.2 },
})
.then(() => {
accept();
setClickedButton("");
controls.set({ x: 0 });
});
}, [controls, accept]);
const displayedUser = recommended.peek(0);
const nextUser = recommended.peek(1);

const handleAction = useCallback(
async (action: "accept" | "reject") => {
const current = recommended.peek(0);
if (!current) return;

setClickedButton(action === "accept" ? "heart" : "cross");

// アニメーション開始前に BackCard の位置をリセット
backCardControls.set({ x: 0, y: 0 });

// 移動アニメーションを実行
await Promise.all([
controls.start({
x: action === "accept" ? 1000 : -1000,
transition: { duration: 0.5, delay: 0.2 },
}),
backCardControls.start({
x: 10,
y: 10,
transition: { duration: 0.5, delay: 0.2 },
}),
]);

// 状態更新
recommended.pop();
if (action === "accept") {
await request.send(current.id);
} else if (action === "reject") {
recommended.push(current);
}
rerender({});

// 位置をリセット
controls.set({ x: 0 });
backCardControls.set({ x: 0, y: 0 });

setClickedButton("");
},
[recommended, controls, backCardControls],
);

if (recommended == null) {
return <FullScreenCircularProgress />;
Expand All @@ -85,40 +90,57 @@ export default function Home() {
return (
<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">
{nextUser && (
<div className="relative h-full w-full">
<div className="-translate-x-4 -translate-y-4 inset-0 z-0 mt-4 transform">
<Card displayedUser={nextUser} currentUser={currentUser} />
</div>
<motion.div
animate={controls}
className="absolute inset-0 z-10 mt-4 flex items-center justify-center"
>
<DraggableCard
displayedUser={displayedUser}
currentUser={currentUser}
onSwipeLeft={reject}
onSwipeRight={accept}
clickedButton={clickedButton}
/>
</motion.div>
</div>
)}
<button
type="button"
onClick={() => setOpenDetailedMenu(!openDetailedMenu)}
>
てすと
</button>
<div className="button-container mt-4 mb-4 flex w-full justify-center space-x-8">
<CloseButton onclick={onClickClose} icon={<CloseIconStyled />} />
<GoodButton
onclick={onClickHeart}
icon={<FavoriteIconStyled />}
/>
<div className="flex h-full flex-col items-center justify-center">
{nextUser && (
<div className="relative grid h-full w-full grid-cols-1 grid-rows-1">
<motion.div
className="z-0 col-start-1 row-start-1 mt-4"
initial={{ x: 0, y: 0 }} // 初期位置を (0, 0) に設定
animate={backCardControls}
>
<Card displayedUser={nextUser} currentUser={currentUser} />
</motion.div>
<motion.div
className="z-10 col-start-1 row-start-1 mt-4 flex items-center justify-center"
animate={controls}
>
<DraggableCard
displayedUser={displayedUser}
currentUser={currentUser}
onSwipeLeft={() => handleAction("reject")}
onSwipeRight={() => handleAction("accept")}
clickedButton={clickedButton}
setOpenDetailedMenu={setOpenDetailedMenu}
/>
</motion.div>
</div>
)}
{nextUser == null && (
<div className="relative grid h-full w-full grid-cols-1 grid-rows-1">
<motion.div
className="z-10 col-start-1 row-start-1 mt-4 flex items-center justify-center"
animate={controls}
>
<DraggableCard
displayedUser={displayedUser}
currentUser={currentUser}
onSwipeLeft={() => handleAction("reject")}
onSwipeRight={() => handleAction("accept")}
clickedButton={clickedButton}
setOpenDetailedMenu={setOpenDetailedMenu}
/>
</motion.div>
</div>
)}
<div className="button-container mt-4 mb-4 flex w-full justify-center space-x-8">
<CloseButton
onclick={() => handleAction("reject")}
icon={<CloseIconStyled />}
/>
<GoodButton
onclick={() => handleAction("accept")}
icon={<FavoriteIconStyled />}
/>
</div>
{openDetailedMenu && (
<PersonDetailedMenu
Expand All @@ -129,7 +151,7 @@ export default function Home() {
currentUser={currentUser}
/>
)}
</>
</div>
)}
</div>
);
Expand Down Expand Up @@ -179,8 +201,5 @@ class Queue<T> {
}
pop(): T | undefined {
return this.store.shift();
// yes, I know what you want to say, it has O(n) time complexity.
// it doesn't really matter if there is only like 100 people in home queue at most.
// if you really care about performance, why don't you go and limit the amount of people to fetch? that probably has significantly more impact to the performance.
}
}
Loading
Loading