시작하며...
이번 프로젝트에서 직접 사용자들의 피드백을 받는 베타 테스트를 진행하기로 했다.
유저들의 피드백을 구글폼이나 디스코드로 받으려고 했는데 일단 접근성이 낮고 유저 입장에서는 귀찮을 거 같다는 우려가 있었다.
좀 더 간편한 방법으로 유저들이 피드백을 줄 수 있는 방법이 없을까? 하고 여러 사이트를 돌아다니면서 본 결과 "채널톡" 을 운영하는 것을 보게 되었다.
그래서 "채널톡" 을 프로젝트에 구현하게 되었고 이를 적용한 방법에 대해 글을 작성하게 되었다.
채널톡 적용하기
공식문서
채널톡 공식문서에 적용하는 방법이 자세히 적혀있다.
https://developers.channel.io/reference/web-quickstart-kr#single-page-application
1. Service 추가하기
우선 나는 컴포넌트 폴더에 channel-talk 이라는 폴더를 만들고 하위에 파일을 추가하였다.
(컨벤션을 케밥 케이스로 설정함)
채널톡 공식문서에 있는 코드를 그대로 복붙하였다.
공식문서와 다른 점은 eslint 오류가 나서 위에 주석을 추가한 것 밖에 없다.
/* eslint-disable import/no-anonymous-default-export */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable prefer-rest-params */
/* eslint-disable consistent-return */
/* eslint-disable func-names */
/* eslint-disable class-methods-use-this */
declare global {
interface Window {
ChannelIO?: IChannelIO;
ChannelIOInitialized?: boolean;
}
}
interface IChannelIO {
c?: (...args: any) => void;
q?: [methodName: string, ...args: any[]][];
(...args: any): void;
}
interface BootOption {
appearance?: string;
customLauncherSelector?: string;
hideChannelButtonOnBoot?: boolean;
hidePopup?: boolean;
language?: string;
memberHash?: string;
memberId?: string;
pluginKey: string;
profile?: Profile;
trackDefaultEvent?: boolean;
trackUtmSource?: boolean;
unsubscribe?: boolean;
unsubscribeEmail?: boolean;
unsubscribeTexting?: boolean;
zIndex?: number;
}
interface Callback {
(error: Error | null, user: CallbackUser | null): void;
}
interface CallbackUser {
alert: number;
avatarUrl: string;
id: string;
language: string;
memberId: string;
name?: string;
profile?: Profile | null;
tags?: string[] | null;
unsubscribeEmail: boolean;
unsubscribeTexting: boolean;
}
interface UpdateUserInfo {
language?: string;
profile?: Profile | null;
profileOnce?: Profile;
tags?: string[] | null;
unsubscribeEmail?: boolean;
unsubscribeTexting?: boolean;
}
interface Profile {
[key: string]: string | number | boolean | null | undefined;
}
interface FollowUpProfile {
name?: string | null;
mobileNumber?: string | null;
email?: string | null;
}
interface EventProperty {
[key: string]: string | number | boolean | null | undefined;
}
type Appearance = "light" | "dark" | "system" | null;
class ChannelService {
loadScript() {
(function () {
const w = window;
if (w.ChannelIO) {
return w.console.error("ChannelIO script included twice.");
}
const ch: IChannelIO = function () {
ch.c?.(arguments);
};
ch.q = [];
ch.c = function (args) {
ch.q?.push(args);
};
w.ChannelIO = ch;
function l() {
if (w.ChannelIOInitialized) {
return;
}
w.ChannelIOInitialized = true;
const s = document.createElement("script");
s.type = "text/javascript";
s.async = true;
s.src = "https://cdn.channel.io/plugin/ch-plugin-web.js";
const x = document.getElementsByTagName("script")[0];
if (x.parentNode) {
x.parentNode.insertBefore(s, x);
}
}
if (document.readyState === "complete") {
l();
} else {
w.addEventListener("DOMContentLoaded", l);
w.addEventListener("load", l);
}
})();
}
boot(option: BootOption, callback?: Callback) {
window.ChannelIO?.("boot", option, callback);
}
shutdown() {
window.ChannelIO?.("shutdown");
}
showMessenger() {
window.ChannelIO?.("showMessenger");
}
hideMessenger() {
window.ChannelIO?.("hideMessenger");
}
openChat(chatId?: string | number, message?: string) {
window.ChannelIO?.("openChat", chatId, message);
}
track(eventName: string, eventProperty?: EventProperty) {
window.ChannelIO?.("track", eventName, eventProperty);
}
onShowMessenger(callback: () => void) {
window.ChannelIO?.("onShowMessenger", callback);
}
onHideMessenger(callback: () => void) {
window.ChannelIO?.("onHideMessenger", callback);
}
onBadgeChanged(callback: (unread: number, alert: number) => void) {
window.ChannelIO?.("onBadgeChanged", callback);
}
onChatCreated(callback: () => void) {
window.ChannelIO?.("onChatCreated", callback);
}
onFollowUpChanged(callback: (profile: FollowUpProfile) => void) {
window.ChannelIO?.("onFollowUpChanged", callback);
}
onUrlClicked(callback: (url: string) => void) {
window.ChannelIO?.("onUrlClicked", callback);
}
clearCallbacks() {
window.ChannelIO?.("clearCallbacks");
}
updateUser(userInfo: UpdateUserInfo, callback?: Callback) {
window.ChannelIO?.("updateUser", userInfo, callback);
}
addTags(tags: string[], callback?: Callback) {
window.ChannelIO?.("addTags", tags, callback);
}
removeTags(tags: string[], callback?: Callback) {
window.ChannelIO?.("removeTags", tags, callback);
}
setPage(page: string) {
window.ChannelIO?.("setPage", page);
}
resetPage() {
window.ChannelIO?.("resetPage");
}
showChannelButton() {
window.ChannelIO?.("showChannelButton");
}
hideChannelButton() {
window.ChannelIO?.("hideChannelButton");
}
setAppearance(appearance: Appearance) {
window.ChannelIO?.("setAppearance", appearance);
}
}
export default new ChannelService();
2. 설치 및 부트하기
프로젝트에 TanStack Query 를 적용하기 위해 AppProvider 라는 것을 만들었는데, 나는 여기에 "채널톡" 을 설치하고 부트하였다.
채널톡 Service 를 import 하고 useEffect 를 사용하여 생성해 준다.
useEffect 를 사용한 이유는 초기 렌더링 시에만 한 번만 실행돼야 하기 때문이다.
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { PropsWithChildren, useEffect, useState } from "react";
import ChannelService from "../common/channel-talk";
const AppProvider = ({ children }: PropsWithChildren) => {
const [client] = useState(
new QueryClient({
// 생략
}),
);
// 채널톡 적용!!
useEffect(() => {
ChannelService.loadScript();
ChannelService.boot({
pluginKey: "플러그인 키" || "",
});
}, []);
return (
<QueryClientProvider client={client}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
};
export default AppProvider;
AppPovider 는 루트 레이아웃 파일에 배치하였다.
const RootLayout: React.FC<Readonly<{ children: React.ReactNode }>> = ({
children,
}) => (
<html lang="ko" className="dark">
<body className="생략">
<AppProvider>
<ToastContainer />
<NavBar />
<main className="px-16 ">{children}</main>
</AppProvider>
</body>
</html>
);
+) 플러그인 키
플러그인 키를 받는 방법은 "채널톡" 새로운 채널을 생성하고 좌측 하단에 있는 "채널 설정" 버튼을 누른다.
그리고 일반 설정 -> 버튼 설치 및 설정 에서 플러그인 키를 발급받을 수 있다.
+) 채널톡 꾸미기
자유롭게 "채널톡" 의 내용과 버튼을 꾸밀 수 있다.
3. 적용 결과
"채널톡" 이 프로젝트에 잘 적용된 것을 확인할 수 있다.
그리고 실제로 베타테스트를 할 때 구글폼과 디스코드 DM 중에서 "채널톡" 이 가장 활발하였고, 유저들의 피드백과 칭찬(?) 을 들을 수 있었다!
마치며...
전에 다른 프로젝트에 채널톡이 구현되어 있는 것을 보고 이건 어떻게 했지? 생각했었는데 실제로 구현해 보니 어렵지 않아서 좋았고 해보고 싶은 것을 구현해 보아서 더욱더 좋았다.
특히 "채널톡" 이 사용자 입장에서 접근성이 제일 좋고 간편하기 때문에 사용자의 피드백을 받거나 문의사항을 쉽게 받을 수 있어 매우 유용한 서비스인 것 같다 😊
'💜 리액트 > Next.js' 카테고리의 다른 글
[Next Auth.js] 구글 로그인 배포 시, Server error (feat. AWS Amplfiy) (0) | 2024.11.10 |
---|---|
[Next.js / TanStack Query] Server Side Rendering 하기 (feat. app router) (0) | 2024.08.27 |
[Next.js] Server Sider Rendering 특정 사용자 접근 제한 (feat. App Router) (0) | 2024.08.11 |
[Next.js] 메타태그, 오픈그래프 컴포넌트 SEO 향상 (feat. Page Router) (0) | 2024.07.12 |
[Next.js] Framer Motion 화면 전환 애니메이션 적용 (feat. Page Router) (2) | 2024.07.10 |
FE 개발자가 되고 싶은 짱잼이
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!