Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@tanstack/react-query": "^5.84.1",
"@tanstack/react-query-devtools": "^5.84.1",
"@tanstack/react-virtual": "^3.13.12",
"@vercel/speed-insights": "^1.3.1",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸก Minor

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

#!/bin/bash
# Search for `@vercel/speed-insights` usage in the codebase
echo "=== Searching for `@vercel/speed-insights` imports ==="
rg "@vercel/speed-insights" --type-list | head -20
rg "speed-insights|SpeedInsights" -i --type ts --type tsx --type js --type jsx 2>/dev/null || echo "No matches found in ts/tsx/js/jsx"

# Check the actual apps/web structure
echo ""
echo "=== Checking apps/web structure ==="
find apps/web -maxdepth 2 -type f -name "*.ts" -o -name "*.tsx" | head -20

Repository: solid-connection/solid-connect-web

Length of output: 827


๐Ÿ Script executed:

#!/bin/bash
# Check for imports in the main app files
echo "=== Searching in apps/web directory ==="
rg -i "vercel|speed.insights|speed-insights" apps/web --type ts --type tsx -A 2 2>/dev/null | head -50

# Also check if there's any configuration or setup
echo ""
echo "=== Checking package.json context ==="
cat apps/web/package.json | grep -A 10 -B 10 "@vercel/speed-insights" || echo "Not in grep output"

Repository: solid-connection/solid-connect-web

Length of output: 890


๐Ÿ Script executed:

#!/bin/bash
# Get file listing to understand the structure
echo "=== Apps/web main files ==="
ls -la apps/web/*.json 2>/dev/null | head -20
ls -la apps/web/src/ 2>/dev/null | head -20

# Search more broadly for any vercel imports
echo ""
echo "=== All vercel related imports ==="
rg "@vercel" apps/web --type ts --type tsx --type json 2>/dev/null

Repository: solid-connection/solid-connect-web

Length of output: 1128


@vercel/speed-insights ์˜์กด์„ฑ์ด ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉ๋˜์ง€ ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์ฝ”๋“œ์—์„œ ์ž„ํฌํŠธ/์‚ฌ์šฉ ์—†์Œ: ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ, @vercel/speed-insights๋Š” package.json์—๋งŒ ์ถ”๊ฐ€๋˜์–ด ์žˆ๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์–ด๋–ค ํŒŒ์ผ์—์„œ๋„ ์‹ค์ œ๋กœ ์ž„ํฌํŠธ๋˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  2. PR ๋ฒ”์œ„ ์™ธ ๋ณ€๊ฒฝ: ์ด PR์˜ ๋ชฉ์ ์ธ #405โ€“๋ฉ˜ํ‹ฐ ์‹ ์ฒญํ•˜๊ธฐ -> ๋ฉ˜ํ† ๋ง ์‹ ์ฒญํ•˜๊ธฐย #409 ๋ฒ„๊ทธ ์ˆ˜์ •๊ณผ๋Š” ๋ฌด๊ด€ํ•œ ์˜์กด์„ฑ ์ถ”๊ฐ€์ž…๋‹ˆ๋‹ค.

๋ถˆํ•„์š”ํ•œ ์˜์กด์„ฑ์€ ์ œ๊ฑฐํ•˜๊ฑฐ๋‚˜, ์‹ค์ œ ์‚ฌ์šฉ ๊ณ„ํš์ด ์žˆ๋‹ค๋ฉด ์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€๋‚˜ PR ์„ค๋ช…์— ์ด์œ ๋ฅผ ๋ช…์‹œํ•ด์ฃผ์„ธ์š”.

๐Ÿค– Prompt for AI Agents
In `@apps/web/package.json` at line 29, The package.json currently includes an
unused dependency "@vercel/speed-insights"; remove the "@vercel/speed-insights":
"^1.3.1" entry from apps/web package.json if there is no planned usage in this
PR, or alternatively add a brief note in the PR/commit message explaining why
"@vercel/speed-insights" is being introduced and where it will be used; update
package.json accordingly and run dependency install/lockfile update to keep
package-lock/yarn.lock consistent.

"axios": "^1.6.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand Down
45 changes: 41 additions & 4 deletions apps/web/src/apis/community/deletePost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,64 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import type { AxiosError, AxiosResponse } from "axios";
import { useRouter } from "next/navigation";

import useAuthStore from "@/lib/zustand/useAuthStore";
import { toast } from "@/lib/zustand/useToastStore";
import { CommunityQueryKeys, communityApi, type DeletePostResponse } from "./api";

interface DeletePostVariables {
postId: number;
boardCode?: string;
}

/**
* @description ISR ํŽ˜์ด์ง€๋ฅผ revalidateํ•˜๋Š” ํ•จ์ˆ˜
* @param boardCode - ๊ฒŒ์‹œํŒ ์ฝ”๋“œ
* @param accessToken - ์‚ฌ์šฉ์ž ์ธ์ฆ ํ† ํฐ
*/
const revalidateCommunityPage = async (boardCode: string, accessToken: string) => {
try {
if (!accessToken) {
console.warn("Revalidation skipped: No access token available");
return;
}

await fetch("/api/revalidate", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ boardCode }),
});
Comment on lines +20 to +34
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸ  Major

ISR revalidate๊ฐ€ โ€˜์กฐ์šฉํžˆ ์‹คํŒจโ€™ํ•  ์ˆ˜ ์žˆ์–ด์š”.
fetch๋Š” 4xx/5xx์—์„œ๋„ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€์ง€ ์•Š์•„์„œ, ํ˜„์žฌ๋Š” ์‹คํŒจ๊ฐ€ ๋กœ๊ทธ ์—†์ด ํ†ต๊ณผ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‘๋‹ต ok ์ฒดํฌ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ์„ธ์š”.

๐Ÿ”ง ์ˆ˜์ • ์ œ์•ˆ
-    await fetch("/api/revalidate", {
+    const response = await fetch("/api/revalidate", {
       method: "POST",
       headers: {
         "Content-Type": "application/json",
         Authorization: `Bearer ${accessToken}`,
       },
       body: JSON.stringify({ boardCode }),
     });
+    if (!response.ok) {
+      throw new Error(`Revalidate failed: ${response.status}`);
+    }
๐Ÿค– Prompt for AI Agents
In `@apps/web/src/apis/community/deletePost.ts` around lines 20 - 34, The
revalidation call in revalidateCommunityPage currently awaits fetch but doesn't
check HTTP status, so 4xx/5xx responses silently succeed; update
revalidateCommunityPage to capture the fetch response, check response.ok, and if
false log or throw an error including response.status and response.statusText
(or response.text()) so failed ISR revalidations are visible; ensure this
handling occurs after the fetch and before returning from
revalidateCommunityPage.

} catch (error) {
console.error("Revalidate failed:", error);
}
};

/**
* @description ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ๋ฅผ ์œ„ํ•œ useMutation ์ปค์Šคํ…€ ํ›…
*/
const useDeletePost = () => {
const router = useRouter();
const queryClient = useQueryClient();
const { accessToken } = useAuthStore();

return useMutation<AxiosResponse<DeletePostResponse>, AxiosError, number>({
mutationFn: communityApi.deletePost,
onSuccess: () => {
return useMutation<AxiosResponse<DeletePostResponse>, AxiosError, DeletePostVariables>({
mutationFn: ({ postId }) => communityApi.deletePost(postId),
onSuccess: async (_result, variables) => {
// 'posts' ์ฟผ๋ฆฌ ํ‚ค๋ฅผ ๊ฐ€์ง„ ๋ชจ๋“  ์ฟผ๋ฆฌ๋ฅผ ๋ฌดํšจํ™”ํ•˜์—ฌ
// ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์„ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ค๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] });

// ISR ํŽ˜์ด์ง€ revalidate
if (variables.boardCode && accessToken) {
await revalidateCommunityPage(variables.boardCode, accessToken);
}

toast.success("๊ฒŒ์‹œ๊ธ€์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");

// ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ํŽ˜์ด์ง€ ์ด๋™
router.replace("/community/FREE");
router.replace(`/community/${variables.boardCode || "FREE"}`);
},
onError: (error) => {
console.error("๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ์‹คํŒจ:", error);
Expand Down
36 changes: 35 additions & 1 deletion apps/web/src/apis/community/patchUpdatePost.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,60 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import type { AxiosError } from "axios";

import useAuthStore from "@/lib/zustand/useAuthStore";
import { toast } from "@/lib/zustand/useToastStore";
import { CommunityQueryKeys, communityApi, type PostIdResponse, type PostUpdateRequest } from "./api";

interface UpdatePostVariables {
postId: number;
data: PostUpdateRequest;
boardCode?: string;
}

/**
* @description ISR ํŽ˜์ด์ง€๋ฅผ revalidateํ•˜๋Š” ํ•จ์ˆ˜
* @param boardCode - ๊ฒŒ์‹œํŒ ์ฝ”๋“œ
* @param accessToken - ์‚ฌ์šฉ์ž ์ธ์ฆ ํ† ํฐ
*/
const revalidateCommunityPage = async (boardCode: string, accessToken: string) => {
try {
if (!accessToken) {
console.warn("Revalidation skipped: No access token available");
return;
}

await fetch("/api/revalidate", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ boardCode }),
});
Comment on lines +19 to +33
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸ  Major

์—ฌ๊ธฐ๋„ revalidate ์‹คํŒจ๊ฐ€ ์ˆจ๊ฒจ์งˆ ์ˆ˜ ์žˆ์–ด์š”.
fetch๋Š” 4xx/5xx์—์„œ๋„ ์„ฑ๊ณต์œผ๋กœ resolve๋˜๋ฏ€๋กœ, ์‘๋‹ต ok ์ฒดํฌ๋กœ ์‹คํŒจ๋ฅผ ๋ช…์‹œํ•ด ์ฃผ์„ธ์š”.

๐Ÿ”ง ์ˆ˜์ • ์ œ์•ˆ
-    await fetch("/api/revalidate", {
+    const response = await fetch("/api/revalidate", {
       method: "POST",
       headers: {
         "Content-Type": "application/json",
         Authorization: `Bearer ${accessToken}`,
       },
       body: JSON.stringify({ boardCode }),
     });
+    if (!response.ok) {
+      throw new Error(`Revalidate failed: ${response.status}`);
+    }
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const revalidateCommunityPage = async (boardCode: string, accessToken: string) => {
try {
if (!accessToken) {
console.warn("Revalidation skipped: No access token available");
return;
}
await fetch("/api/revalidate", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ boardCode }),
});
const revalidateCommunityPage = async (boardCode: string, accessToken: string) => {
try {
if (!accessToken) {
console.warn("Revalidation skipped: No access token available");
return;
}
const response = await fetch("/api/revalidate", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ boardCode }),
});
if (!response.ok) {
throw new Error(`Revalidate failed: ${response.status}`);
}
๐Ÿค– Prompt for AI Agents
In `@apps/web/src/apis/community/patchUpdatePost.ts` around lines 19 - 33, The
fetch in revalidateCommunityPage currently treats all HTTP responses as success;
update the function to check the Response.ok after the await fetch (in
revalidateCommunityPage) and handle non-ok responses by throwing or logging a
descriptive error (include response.status and response text) so the existing
try/catch can surface failures; ensure the Authorization/header/body logic
remains unchanged and that failures end up logged or rethrown for callers to
observe.

} catch (error) {
console.error("Revalidate failed:", error);
}
};

/**
* @description ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •์„ ์œ„ํ•œ useMutation ์ปค์Šคํ…€ ํ›…
*/
const useUpdatePost = () => {
const queryClient = useQueryClient();
const { accessToken } = useAuthStore();

return useMutation<PostIdResponse, AxiosError, UpdatePostVariables>({
mutationFn: ({ postId, data }) => communityApi.updatePost(postId, data),
onSuccess: (_result, variables) => {
onSuccess: async (_result, variables) => {
// ํ•ด๋‹น ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ์ฟผ๋ฆฌ์™€ ๋ชฉ๋ก ์ฟผ๋ฆฌ๋ฅผ ๋ฌดํšจํ™”
queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts, variables.postId] });
queryClient.invalidateQueries({ queryKey: [CommunityQueryKeys.posts] });

// ISR ํŽ˜์ด์ง€ revalidate
if (variables.boardCode && accessToken) {
await revalidateCommunityPage(variables.boardCode, accessToken);
}

toast.success("๊ฒŒ์‹œ๊ธ€์ด ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
},
onError: (error) => {
Expand Down
13 changes: 8 additions & 5 deletions apps/web/src/app/community/[boardCode]/[postId]/KebabMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
"use client";

import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { type RefObject, useEffect, useRef, useState } from "react";
import { useDeletePost } from "@/apis/community";
import ReportPanel from "@/components/ui/ReportPanel";
import { toast } from "@/lib/zustand/useToastStore";
import { IconSetting } from "@/public/svgs/mentor";

const useClickOutside = (ref, handler) => {
const useClickOutside = (ref: RefObject<HTMLDivElement | null>, handler: (event: MouseEvent | TouchEvent) => void) => {
useEffect(() => {
const listener = (event) => {
if (!ref.current || ref.current.contains(event.target)) return;
const listener = (event: MouseEvent | TouchEvent) => {
const current = ref.current;
if (!current) return;
if (!(event.target instanceof Node)) return;
if (current.contains(event.target)) return;
handler(event);
};
document.addEventListener("mousedown", listener);
Expand Down Expand Up @@ -113,7 +116,7 @@ const KebabMenu = ({ postId, boardCode, isOwner = false }: KebabMenuProps) => {
<button
onClick={() => {
if (confirm("์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?")) {
deletePost(postId);
deletePost({ postId, boardCode });
}
}}
className={`flex w-full items-center gap-3 rounded-md px-3 py-2.5 text-gray-700 typo-regular-2 hover:bg-k-50`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";

import { useUpdatePost } from "@/apis/community";
import { toast } from "@/lib/zustand/useToastStore";
import { IconArrowBackFilled, IconImage, IconPostCheckboxFilled, IconPostCheckboxOutlined } from "@/public/svgs";

type PostModifyFormProps = {
Expand All @@ -25,6 +26,7 @@ const PostModifyForm = ({
}: PostModifyFormProps) => {
const [title, setTitle] = useState<string>(defaultTitle);
const [content, setContent] = useState<string>(defaultContent);
const [isQuestion, setIsQuestion] = useState<boolean>(defaultIsQuestion);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const titleRef = useRef<HTMLDivElement>(null);
const imageUploadRef = useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -60,12 +62,23 @@ const PostModifyForm = ({
}, []);

const submitPost = async () => {
if (!title.trim()) {
toast.error("์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.");
return;
}

if (!content.trim()) {
toast.error("๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.");
return;
}

updatePostMutation.mutate(
{
postId,
boardCode,
data: {
postUpdateRequest: {
postCategory: defaultPostCategory,
postCategory: isQuestion ? "์งˆ๋ฌธ" : "์ž์œ ",
title,
content,
},
Expand Down Expand Up @@ -108,8 +121,8 @@ const PostModifyForm = ({
</div>
<div className="flex h-[42px] items-center justify-between border-b border-b-gray-c-100 px-5 py-2.5">
<div className="text-gray-250/87 flex items-center gap-1 typo-regular-2">
<button type="button">
{defaultIsQuestion ? <IconPostCheckboxFilled /> : <IconPostCheckboxOutlined />}
<button onClick={() => setIsQuestion(!isQuestion)} type="button">
{isQuestion ? <IconPostCheckboxFilled /> : <IconPostCheckboxOutlined />}
</button>
์งˆ๋ฌธ์œผ๋กœ ์—…๋กœ๋“œ ํ•˜๊ธฐ
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const useModifyHookForm = (myMentorProfile: MentorCardPreview | null): UseModify
reset({
channels,
introduction: myMentorProfile.introduction,
passTip: "",
passTip: myMentorProfile.passTip ?? "",
});
} else {
// myMentorProfile์ด ์—†์„ ๋•Œ๋„ 4๊ฐœ์˜ ๋นˆ ์ฑ„๋„ ์Šฌ๋กฏ ์ œ๊ณต
Expand All @@ -35,6 +35,6 @@ const useModifyHookForm = (myMentorProfile: MentorCardPreview | null): UseModify
});
}
}, [myMentorProfile, reset]);
return { ...method };
return method;
};
export default useModifyHookForm;
6 changes: 4 additions & 2 deletions apps/web/src/app/mentor/modify/_ui/ModifyContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ const ModifyContent = () => {
<h2 className="mt-10 text-primary-1 typo-sb-5">๋ฉ˜ํ†  ํ•œ๋งˆ๋””</h2>
<textarea
{...register("introduction")}
className="mt-2.5 h-30 w-full rounded-lg bg-k-50 p-5 text-k-300 typo-regular-2"
className="mt-2.5 h-30 w-full rounded-lg bg-k-50 p-5 text-k-900 placeholder:text-k-300 typo-regular-2"
placeholder="์ตœ๋Œ€ 200์ž ์ด๋‚ด"
maxLength={200}
/>
{errors.introduction && (
<p className="mt-1 text-red-500 typo-regular-2">
Expand All @@ -74,8 +75,9 @@ const ModifyContent = () => {
<h2 className="mt-10 text-primary-1 typo-sb-5">ํ•ฉ๊ฒฉ ๋ ˆ์‹œํ”ผ</h2>
<textarea
{...register("passTip")}
className="mt-2.5 h-30 w-full rounded-lg bg-k-50 p-5 text-k-300 typo-regular-2"
className="mt-2.5 h-30 w-full rounded-lg bg-k-50 p-5 text-k-900 placeholder:text-k-300 typo-regular-2"
placeholder="์ตœ๋Œ€ 200์ž ์ด๋‚ด"
maxLength={200}
/>
{errors.passTip && (
<p className="mt-1 text-red-500 typo-regular-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const WaitingContent = () => {
<div className="flex items-center">
<h2 className="mr-2 text-k-900 typo-sb-5">๋Œ€๊ธฐ ์ค‘์ธ ๋ฉ˜ํ† ๋ง</h2>
{totalLength > DEFAULT_VISIBLE_ITEMS && (
<span className="rounded-2xl bg-primary-1 px-2 text-k-0">{totalLength - 2}+</span>
<span className="rounded-2xl bg-primary-1 px-2 text-k-0">{totalLength - DEFAULT_VISIBLE_ITEMS}+</span>
)}
</div>
</div>
Expand All @@ -42,7 +42,7 @@ const WaitingContent = () => {
mentoringId={item.mentoringId}
profileImageUrl={item.profileImageUrl}
nickname={item.nickname}
message={`๋‹˜์ด ๋ฉ˜ํ‹ฐ ์‹ ์ฒญ์„ ์ˆ˜๋ฝํ–ˆ์–ด์š”.`}
message="๋ฉ˜ํ† ๊ฐ€ ๋ฉ˜ํ‹ฐ ์‹ ์ฒญ์„ ์ˆ˜๋ฝํ–ˆ์–ด์š”."
date={item.createdAt}
/>
))
Expand All @@ -56,7 +56,7 @@ const WaitingContent = () => {
{({ isExpanded }) => (
<div className="space-y-2">
<h3 className="mt-3 px-5 text-k-900 typo-sb-5">์ˆ˜๋ฝ ๋Œ€๊ธฐ์ค‘</h3>
<div className="space-y-2 p-4">
<div className="space-y-2">
{pendingList.length === 0 ? (
<div className="px-4 py-3">
<EmptySdwBCards message="๋ฉ˜ํ†  ์‹ ์ฒญ ๋‚ด์—ญ์ด ์—†์Šต๋‹ˆ๋‹ค." />
Expand All @@ -67,7 +67,7 @@ const WaitingContent = () => {
key={item.mentoringId}
profileImageUrl={item.profileImageUrl}
nickname={item.nickname}
description={`๋‹˜์—๊ฒŒ ๋ฉ˜ํ‹ฐ ์‹ ์ฒญ์„ ๋ณด๋ƒˆ์–ด์š”.`}
description="๋ฉ˜ํ† ์—๊ฒŒ ๋ฉ˜ํ‹ฐ ์‹ ์ฒญ์„ ๋ณด๋ƒˆ์–ด์š”."
/>
))
)}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/app/university/application/ScoreSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useState } from "react";
import { IconExpandMoreFilled } from "@/public/svgs/community";
import type { ScoreSheet } from "@/types/application";
import type { ScoreSheet as ScoreSheetType } from "@/types/application";
import { languageTestMapping } from "@/types/score";

const ScoreSheet = ({ scoreSheet }: { scoreSheet: ScoreSheet }) => {
const ScoreSheet = ({ scoreSheet }: { scoreSheet: ScoreSheetType }) => {
const [tableOpened, setTableOpened] = useState(false);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,23 @@ const useArticleSchema = ({

const handleFormSubmit = (data: ArticleFormData) => {
if (isEdit) {
putModifyArticle({ body: data, articleId });
putModifyArticle(
{ body: data, articleId },
{
onSuccess: () => {
handleClose();
reset();
},
},
);
} else {
postAddArticle(data);
postAddArticle(data, {
onSuccess: () => {
handleClose();
reset();
},
});
}
handleClose();
reset();
};

const handleModalClose = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { z } from "zod";
export const articleSchema = z.object({
title: z.string().min(1, "์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”").max(20, "์ œ๋ชฉ์€ 20์ž ์ดํ•˜๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"),
description: z.string().min(1, "๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”").max(300, "๋‚ด์šฉ์€ 300์ž ์ดํ•˜๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"),
url: z.string().url("์˜ฌ๋ฐ”๋ฅธ ๋งํฌ ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”").optional().or(z.literal("")),
url: z.union([z.string().url("์˜ฌ๋ฐ”๋ฅธ ๋งํฌ ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"), z.literal("")]).optional(),
resetToDefaultImage: z.boolean().optional(),
// file์€ optional๋กœ ์„ค์ • (์—…๋กœ๋“œํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์œผ๋ฏ€๋กœ)
file: z.instanceof(File).optional(),
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/mentor/MentorCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const MentorCard = ({ mentor, observeRef, isMine = false }: MentorCardProps) =>
onClick={() => id && handlePostApplyMentor(id)}
className="flex h-10 w-1/2 flex-shrink-0 items-center justify-center gap-3 rounded-[20px] bg-primary px-5 py-2.5 text-white typo-medium-2"
>
๋ฉ˜ํ‹ฐ ์‹ ์ฒญํ•˜๊ธฐ
๋ฉ˜ํ† ๋ง ์‹ ์ฒญํ•˜๊ธฐ
</button>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";

const useFloatingUpHandler = (scrollYThreshold: number = 400) => {
const [isVisible, setIsVisible] = useState(false);

const handleScroll = () => {
const handleScroll = useCallback(() => {
if (window.scrollY > scrollYThreshold) {
setIsVisible(true);
} else {
setIsVisible(false);
}
};
}, [scrollYThreshold]);

const handleClick = () => {
window.scrollTo({
Expand Down
6 changes: 1 addition & 5 deletions apps/web/src/components/ui/UniverSityCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,7 @@ const UniversityCard = ({ university, showCapacity = true }: UniversityCardProps
</div>

<div className="flex flex-col">
<span
id={`university-name-${university.id}`}
className="truncate text-k-700 typo-bold-4"
aria-label={convertedKoreanName}
>
<span id={`university-name-${university.id}`} className="truncate text-k-700 typo-bold-4">
{convertedKoreanName}
</span>
<div className="flex items-center gap-2.5">
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/types/mentor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface Mentor {
isApplied: boolean;
}
/** ๋ฆฌ์ŠคํŠธ(๋ฏธ๋ฆฌ๋ณด๊ธฐ) ์šฉ โ€“ passTip / isApplied ์—†์ด ์‚ฌ์šฉ */
export type MentorCardPreview = (MentorCardBase & { term: string }) | null; // ํ•™์—… ํ•™๊ธฐ (์˜ˆ: "2026-1")
export type MentorCardPreview = (MentorCardBase & { term: string; passTip?: string }) | null; // ํ•™์—… ํ•™๊ธฐ (์˜ˆ: "2026-1")

/** ์ƒ์„ธ ๋ทฐ ์šฉ โ€“ ์ถ”๊ฐ€ ์ •๋ณด ํฌํ•จ */
export interface MentorCardDetail extends MentorCardBase {
Expand Down
Loading