Skip to content

Commit

Permalink
[FE] feat: GA 이벤트 추가 (#721)
Browse files Browse the repository at this point in the history
* feat: 상품 정렬 버튼 클릭 GA 이벤트 추가

* feat: 꿀조합 작성, 정렬 버튼 클릭 GA 이벤트 추가

* feat: 상품 목록 페이지 카테고리 버튼 클릭 GA 이벤트 추가

* feat: 상품 리뷰 정렬, 작성 버튼 클릭 GA 이벤트 추가

* feat: 꿀조합 등록하기 버튼 GA 이벤트 추가

* feat: 상품 리뷰 등록하기 버튼 클릭 GA 이벤트 추가

* feat: 검색 페이지에서 검색 GA 이벤트 추가

* style: 등록하기 버튼 클릭 -> 등록

* refactor: 리뷰, 꿀조합 등록 이벤트 삭제

* feat: 랭킹 링크 GA 이벤트 추가

* feat: 홈 배너 및 카테고리 링크 GA 이벤트 추가
  • Loading branch information
Leejin-Yang authored Oct 6, 2023
1 parent 2a3e96b commit 86e9430
Show file tree
Hide file tree
Showing 16 changed files with 151 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,33 @@ import styled from 'styled-components';
import CategoryItem from '../CategoryItem/CategoryItem';

import { CATEGORY_TYPE } from '@/constants';
import { useGA } from '@/hooks/common';
import { useCategoryFoodQuery } from '@/hooks/queries/product';

const category = CATEGORY_TYPE.FOOD;

const CategoryFoodList = () => {
const { data: categories } = useCategoryFoodQuery(category);
const { gaEvent } = useGA();

const handleHomeCategoryLinkClick = (categoryName: string) => {
gaEvent({
category: 'link',
action: `${categoryName} 카테고리 링크 클릭`,
label: '카테고리',
});
};

return (
<div>
<CategoryFoodListWrapper>
{categories.map((menu) => (
<Link key={menu.id} as={RouterLink} to={`products/food?category=${menu.id}`}>
<Link
key={menu.id}
as={RouterLink}
to={`products/food?category=${menu.id}`}
onClick={() => handleHomeCategoryLinkClick(menu.name)}
>
<CategoryItem name={menu.name} image={menu.image} />
</Link>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { useLocation } from 'react-router-dom';
import styled from 'styled-components';

import { CATEGORY_TYPE } from '@/constants';
import { useGA } from '@/hooks/common';
import { useCategoryActionContext, useCategoryValueContext } from '@/hooks/context';
import { useCategoryFoodQuery } from '@/hooks/queries/product/useCategoryQuery';
import { getTargetCategoryName } from '@/utils/category';

const category = CATEGORY_TYPE.FOOD;

Expand All @@ -20,12 +22,23 @@ const CategoryFoodTab = () => {
const queryParams = new URLSearchParams(location.search);
const categoryIdFromURL = queryParams.get('category');

const { gaEvent } = useGA();

useEffect(() => {
if (categoryIdFromURL) {
selectCategory(category, parseInt(categoryIdFromURL));
}
}, [category]);

const handleCategoryButtonClick = (menuId: number) => {
selectCategory(category, menuId);
gaEvent({
category: 'button',
action: `${getTargetCategoryName(categories, menuId)} 카테고리 버튼 클릭`,
label: '카테고리',
});
};

return (
<CategoryMenuContainer>
{categories.map((menu) => {
Expand All @@ -40,7 +53,7 @@ const CategoryFoodTab = () => {
weight="bold"
variant={isSelected ? 'filled' : 'outlined'}
isSelected={isSelected}
onClick={() => selectCategory(category, menu.id)}
onClick={() => handleCategoryButtonClick(menu.id)}
aria-pressed={isSelected}
>
{menu.name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,33 @@ import styled from 'styled-components';
import CategoryItem from '../CategoryItem/CategoryItem';

import { CATEGORY_TYPE } from '@/constants';
import { useGA } from '@/hooks/common';
import { useCategoryStoreQuery } from '@/hooks/queries/product';

const category = CATEGORY_TYPE.STORE;

const CategoryStoreList = () => {
const { data: categories } = useCategoryStoreQuery(category);
const { gaEvent } = useGA();

const handleHomeCategoryLinkClick = (categoryName: string) => {
gaEvent({
category: 'link',
action: `${categoryName} 카테고리 링크 클릭`,
label: '카테고리',
});
};

return (
<div>
<CategoryStoreListWrapper>
{categories.map((menu) => (
<Link key={menu.id} as={RouterLink} to={`products/store?category=${menu.id}`}>
<Link
key={menu.id}
as={RouterLink}
to={`products/store?category=${menu.id}`}
onClick={() => handleHomeCategoryLinkClick(menu.name)}
>
<CategoryItem name={menu.name} image={menu.image} />
</Link>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { useLocation } from 'react-router-dom';
import styled from 'styled-components';

import { CATEGORY_TYPE } from '@/constants';
import { useGA } from '@/hooks/common';
import { useCategoryActionContext, useCategoryValueContext } from '@/hooks/context';
import { useCategoryStoreQuery } from '@/hooks/queries/product/useCategoryQuery';
import { getTargetCategoryName } from '@/utils/category';

const category = CATEGORY_TYPE.STORE;

Expand All @@ -20,12 +22,23 @@ const CategoryStoreTab = () => {
const queryParams = new URLSearchParams(location.search);
const categoryIdFromURL = queryParams.get('category');

const { gaEvent } = useGA();

useEffect(() => {
if (categoryIdFromURL) {
selectCategory(category, parseInt(categoryIdFromURL));
}
}, [category]);

const handleCategoryButtonClick = (menuId: number) => {
selectCategory(category, menuId);
gaEvent({
category: 'button',
action: `${getTargetCategoryName(categories, menuId)} 카테고리 버튼 클릭`,
label: '카테고리',
});
};

return (
<CategoryMenuContainer>
{categories.map((menu) => {
Expand All @@ -40,7 +53,7 @@ const CategoryStoreTab = () => {
weight="bold"
variant={isSelected ? 'filled' : 'outlined'}
isSelected={isSelected}
onClick={() => selectCategory(category, menu.id)}
onClick={() => handleCategoryButtonClick(menu.id)}
aria-pressed={isSelected}
>
{menu.name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Link as RouterLink } from 'react-router-dom';

import { ProductOverviewItem } from '@/components/Product';
import { PATH } from '@/constants/path';
import { useGA } from '@/hooks/common';
import { useProductRankingQuery } from '@/hooks/queries/rank';
import displaySlice from '@/utils/displaySlice';

Expand All @@ -12,13 +13,22 @@ interface ProductRankingListProps {

const ProductRankingList = ({ isHomePage = false }: ProductRankingListProps) => {
const { data: productRankings } = useProductRankingQuery();
const { gaEvent } = useGA();
const productsToDisplay = displaySlice(isHomePage, productRankings.products, 3);

const handleProductRankingLinkClick = () => {
gaEvent({ category: 'link', action: '상품 랭킹 링크 클릭', label: '랭킹' });
};

return (
<ul>
{productsToDisplay.map(({ id, name, image, categoryType }, index) => (
<li key={id}>
<Link as={RouterLink} to={`${PATH.PRODUCT_LIST}/${categoryType}/${id}`}>
<Link
as={RouterLink}
to={`${PATH.PRODUCT_LIST}/${categoryType}/${id}`}
onClick={handleProductRankingLinkClick}
>
<ProductOverviewItem rank={index + 1} name={name} image={image} />
</Link>
<Spacing size={16} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@ import RecipeRankingItem from '../RecipeRankingItem/RecipeRankingItem';

import { Carousel } from '@/components/Common';
import { PATH } from '@/constants/path';
import { useGA } from '@/hooks/common';
import { useRecipeRankingQuery } from '@/hooks/queries/rank';

const RecipeRankingList = () => {
const { data: recipeResponse } = useRecipeRankingQuery();
const { gaEvent } = useGA();

if (recipeResponse.recipes.length === 0) return <Text size="lg">아직 랭킹이 없어요!</Text>;

const handleRecipeRankingLinkClick = () => {
gaEvent({ category: 'link', action: '꿀조합 랭킹 링크 클릭', label: '랭킹' });
};

const carouselList = recipeResponse.recipes.map((recipe, index) => ({
id: index,
children: (
<Link as={RouterLink} to={`${PATH.RECIPE}/${recipe.id}`}>
<Link as={RouterLink} to={`${PATH.RECIPE}/${recipe.id}`} onClick={handleRecipeRankingLinkClick}>
<RecipeRankingItem rank={index + 1} recipe={recipe} />
</Link>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import styled from 'styled-components';
import ReviewRankingItem from '../ReviewRankingItem/ReviewRankingItem';

import { PATH } from '@/constants/path';
import { useGA } from '@/hooks/common';
import { useReviewRankingQuery } from '@/hooks/queries/rank';
import useDisplaySlice from '@/utils/displaySlice';

Expand All @@ -14,15 +15,21 @@ interface ReviewRankingListProps {

const ReviewRankingList = ({ isHomePage = false }: ReviewRankingListProps) => {
const { data: reviewRankings } = useReviewRankingQuery();
const { gaEvent } = useGA();
const reviewsToDisplay = useDisplaySlice(isHomePage, reviewRankings.reviews);

const handleReviewRankingLinkClick = () => {
gaEvent({ category: 'link', action: '리뷰 랭킹 링크 클릭', label: '랭킹' });
};

return (
<ReviewRankingListContainer>
{reviewsToDisplay.map((reviewRanking) => (
<li key={reviewRanking.reviewId}>
<Link
as={RouterLink}
to={`${PATH.PRODUCT_LIST}/${reviewRanking.categoryType}/${reviewRanking.productId}`}
onClick={handleReviewRankingLinkClick}
block
>
<ReviewRankingItem reviewRanking={reviewRanking} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const RecipeRegisterForm = ({ closeRecipeDialog }: RecipeRegisterFormProps) => {
</Text>
<Spacing size={10} />
<FormButton customWidth="100%" customHeight="60px" size="xl" weight="bold" disabled={!isValid || isLoading}>
레시피 등록하기
꿀조합 등록하기
</FormButton>
<Spacing size={50} />
</form>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { default as useTimeout } from './useTimeout';
export { default as useRouteChangeTracker } from './useRouteChangeTracker';
export { default as useTabMenu } from './useTabMenu';
export { default as useScrollRestoration } from './useScrollRestoration';
export { default as useGA } from './useGA';
20 changes: 20 additions & 0 deletions frontend/src/hooks/common/useGA.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useCallback } from 'react';
import ReactGA from 'react-ga4';

interface GAEventProps {
category: string;
action: string;
label?: string;
}

const useGA = () => {
// TODO: navigate event tracking

const gaEvent = useCallback((eventProps: GAEventProps) => {
ReactGA.event(eventProps);
}, []);

return { gaEvent };
};

export default useGA;
5 changes: 5 additions & 0 deletions frontend/src/hooks/search/useSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { ChangeEventHandler, FormEventHandler, MouseEventHandler } from 're
import { useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom';

import { useGA } from '../common';

const useSearch = () => {
const inputRef = useRef<HTMLInputElement>(null);

Expand All @@ -12,6 +14,8 @@ const useSearch = () => {
const [isSubmitted, setIsSubmitted] = useState(!!currentSearchQuery);
const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(searchQuery.length > 0);

const { gaEvent } = useGA();

const focusInput = () => {
if (inputRef.current) {
inputRef.current.focus();
Expand All @@ -26,6 +30,7 @@ const useSearch = () => {

const handleSearch: FormEventHandler<HTMLFormElement> = (event) => {
event.preventDefault();
gaEvent({ category: 'submit', action: '검색 페이지에서 검색', label: '검색' });

const trimmedSearchQuery = searchQuery.trim();

Expand Down
12 changes: 11 additions & 1 deletion frontend/src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,31 @@ import styled from 'styled-components';
import { Loading, ErrorBoundary, ErrorComponent, CategoryFoodList, CategoryStoreList } from '@/components/Common';
import { ProductRankingList, ReviewRankingList, RecipeRankingList } from '@/components/Rank';
import { IMAGE_URL } from '@/constants';
import { useGA } from '@/hooks/common';
import channelTalk from '@/service/channelTalk';

export const HomePage = () => {
const { reset } = useQueryErrorResetBoundary();
const { gaEvent } = useGA();

channelTalk.loadScript();

channelTalk.boot({
pluginKey: process.env.CHANNEL_TALK_KEY ?? '',
});

const handleBannerClick = () => {
gaEvent({ category: 'link', action: '이벤트 배너 클릭', label: '배너' });
};

return (
<>
<section>
<Link href="https://www.instagram.com/p/CxmlqAQSK-w/?igshid=MzRlODBiNWFlZA==" isExternal>
<Link
href="https://www.instagram.com/p/CxmlqAQSK-w/?igshid=MzRlODBiNWFlZA=="
onClick={handleBannerClick}
isExternal
>
<Banner src={`${IMAGE_URL}banner.png`} width={600} height={360} alt="이벤트 배너" />
</Link>
</section>
Expand Down
Loading

0 comments on commit 86e9430

Please sign in to comment.