Skip to content
Merged
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
23 changes: 20 additions & 3 deletions apps/web/src/app/login/LoginContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useRouter, useSearchParams } from "next/navigation";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { usePostEmailAuth } from "@/apis/Auth";
import { IconSolidConnectionFullBlackLogo } from "@/public/svgs";
import { IconArrowBackFilled, IconSolidConnectionFullBlackLogo } from "@/public/svgs";
import { IconAppleLogo, IconEmailIcon, IconKakaoLogo } from "@/public/svgs/auth";
import {
AUTH_REDIRECT_PARAM,
Expand Down Expand Up @@ -54,10 +54,27 @@ const LoginContent = () => {
}
};

const handleBack = () => {
if (window.history.length > 1) {
router.back();
return;
}

router.push("/");
};

return (
<div>
<div className="mt-[-56px] h-[77px] border-b border-bg-200 py-[21px] pl-5">
<Link href="/">
<div className="-mx-5 mt-[-56px] flex h-[77px] items-center gap-3 border-b border-bg-200 px-5">
<button
type="button"
onClick={handleBack}
className="flex h-6 w-6 shrink-0 items-center justify-center"
aria-label="뒤로가기"
>
<IconArrowBackFilled />
</button>
<Link href="/" aria-label="홈으로 이동">
<IconSolidConnectionFullBlackLogo />
</Link>
</div>
Expand Down
14 changes: 12 additions & 2 deletions apps/web/src/app/my/_ui/MyProfileContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
IconUniversity,
} from "@/public/svgs/my";
import { UserRole } from "@/types/mentor";
import { openKakaoOpenChat } from "@/utils/openKakaoOpenChat";

const NEXT_PUBLIC_CONTACT_LINK = process.env.NEXT_PUBLIC_CONTACT_LINK;

Expand All @@ -37,8 +38,17 @@ const MyProfileContent = () => {
const favoriteLocation =
profileData.role === UserRole.MENTEE ? profileData.interestedCountries.slice(0, 3).join(", ") || "없음" : "없음";

const handleContactClick = () => {
if (!NEXT_PUBLIC_CONTACT_LINK) {
showIconToast("logo", "고객센터 링크를 불러오지 못했습니다. 잠시 후 다시 시도해주세요.");
return;
}

openKakaoOpenChat(NEXT_PUBLIC_CONTACT_LINK);
};

return (
<div className="px-5 py-2">
<div className="py-2">
<div className="mb-4 text-start text-k-700 typo-sb-5">
<p>{nickname}님은</p>
<p>
Expand Down Expand Up @@ -155,7 +165,7 @@ const MyProfileContent = () => {
<div className="mt-5 flex flex-col gap-0.5">
<LinkedTextWithIcon href="/terms" text="서비스 이용약관" />

<LinkedTextWithIcon isBilink href={NEXT_PUBLIC_CONTACT_LINK} text="고객센터 문의" />
<LinkedTextWithIcon onClick={handleContactClick} text="고객센터 문의" />

<LinkedTextWithIcon
onClick={() => {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/ui/LinkedTextWithIcon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const LinkedTextWithIcon = ({
}

return (
<button onClick={onClick} className="w-full cursor-pointer">
<button type="button" onClick={onClick} className="w-full cursor-pointer">
{<Content icon={icon} text={text} subText={subText} textColor={textColor} />}
</button>
);
Expand Down
73 changes: 73 additions & 0 deletions apps/web/src/utils/openKakaoOpenChat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const KAKAO_OPEN_CHAT_HOST = "open.kakao.com";
const KAKAO_OPEN_CHAT_PATH_PATTERN = /^\/o\/([^/?#]+)/;
const KAKAO_OPEN_CHAT_REFERER = "EW";
const OPEN_CHAT_FALLBACK_DELAY_MS = 1200;

export const getKakaoOpenChatJoinScheme = (openChatUrl: string): string | null => {
try {
const url = new URL(openChatUrl);

if (url.hostname !== KAKAO_OPEN_CHAT_HOST) {
return null;
}

const linkId = url.pathname.match(KAKAO_OPEN_CHAT_PATH_PATTERN)?.[1];

if (!linkId) {
return null;
}

return `kakaoopen://join?l=${encodeURIComponent(linkId)}&r=${KAKAO_OPEN_CHAT_REFERER}`;
} catch {
return null;
}
};

export const openExternalUrl = (url: string) => {
const openedWindow = window.open(url, "_blank", "noopener,noreferrer");

if (!openedWindow) {
window.location.href = url;
Comment on lines +27 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid treating noopener windows as blocked

When this helper runs from a user click, such as a contact URL that does not match open.kakao.com, passing noopener,noreferrer makes a successful window.open return null, so this branch also navigates the current tab/WebView to the same URL. That regresses the previous _blank link behavior by leaving My Page or opening the contact page twice even though the popup succeeded; use a popup-blocking check that does not rely on the handle returned when noopener is set.

Useful? React with 👍 / 👎.

}
};

export const openKakaoOpenChat = (openChatUrl: string) => {
const joinScheme = getKakaoOpenChatJoinScheme(openChatUrl);

if (!joinScheme) {
openExternalUrl(openChatUrl);
return;
}

let fallbackTimer: number | undefined;

const cleanup = () => {
if (fallbackTimer) {
window.clearTimeout(fallbackTimer);
}

window.removeEventListener("pagehide", cleanup);
document.removeEventListener("visibilitychange", handleVisibilityChange);
};

const handleVisibilityChange = () => {
if (document.hidden) {
cleanup();
}
};

fallbackTimer = window.setTimeout(() => {
cleanup();
openExternalUrl(openChatUrl);
}, OPEN_CHAT_FALLBACK_DELAY_MS);

window.addEventListener("pagehide", cleanup);
document.addEventListener("visibilitychange", handleVisibilityChange);

try {
window.location.href = joinScheme;
} catch {
cleanup();
openExternalUrl(openChatUrl);
}
};
Loading