Skip to content

channeling8/channeling-FE

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

channeling-frontend

β–Ά 채널링 - Channeling

Coding conventions are documented in Rules.md.

πŸ’‘ Project Overview

유튜브 채널 및 κ°œλ³„ μ˜μƒ 데이터λ₯Ό AI둜 뢄석해, κ°œμ„ μ κ³Ό νŠΈλ Œλ“œ 기반 μ½˜ν…μΈ  아이디어λ₯Ό μ œκ³΅ν•˜λŠ” μ†”λ£¨μ…˜μž…λ‹ˆλ‹€.
초보 μœ νŠœλ²„λΆ€ν„° μ „λ¬Έ 크리에이터, λΈŒλžœλ“œ λ§ˆμΌ€νŒ… νŒ€κΉŒμ§€ λͺ¨λ‘κ°€ ν™œμš©ν•  수 μžˆλŠ” λ§žμΆ€ν˜• 리포트λ₯Ό μžλ™ μƒμ„±ν•©λ‹ˆλ‹€.

λ©‹μžˆλŠ” 채널링 νŽ˜μ΄μ§€

πŸ‘₯ Contributors

κ³°/κΉ€μ†Œμ› ν•˜μΉ˜/μ •μœ€λΉˆ 띡/μž₯λͺ…μ€€ μ •/κΉ€μ„Έμ •

gomx3

drddyn

komascode

sejeong223

πŸ› οΈ Tech Stacks

  • React + TypeScript + Vite: λΉ λ₯Έ 개발 사이클(HMR)κ³Ό νƒ€μž… μ•ˆμ •μ„±μœΌλ‘œ ν’ˆμ§ˆΒ·μƒμ‚°μ„± 확보
  • TailwindCSS: μœ ν‹Έλ¦¬ν‹° 클래슀 기반으둜 μΌκ΄€λœ λ””μžμΈκ³Ό λΉ λ₯Έ μŠ€νƒ€μΌλ§
  • Tanstack Query: μ„œλ²„ μƒνƒœ μΊμ‹œ/동기화, invalidateQueries둜 신선도 μ œμ–΄
  • Zustand: 둜그인 ν”Œλ‘œμš°/λͺ¨λ‹¬ λ“± μ „μ—­ UI μƒνƒœλ₯Ό μ‹¬ν”Œν•˜κ²Œ 관리
  • Vercel: κ°„νŽΈν•œ ν”„λ‘ νŠΈ 배포 및 프리뷰 ν™˜κ²½
  • ESLint/Prettier: νŒ€ μ»¨λ²€μ…˜κ³Ό μžλ™ ν¬λ§·νŒ…μœΌλ‘œ 일관성 μœ μ§€

βš™οΈ Getting Started

  1. Install Plugin at your IDE
  1. Move to the frontend directory
cd frontend
  1. Install project dependencies
pnpm install
  1. Run development server
pnpm run dev

After running this command, you can see the website at localhost:5173.

πŸ“ Project Structure

ν”„λ‘ νŠΈμ—”λ“œλŠ” 라우트(νŽ˜μ΄μ§€) μ€‘μ‹¬μ˜ κΈ°λŠ₯ λ‹¨μœ„ ꡬ쑰 μœ„μ—, μž¬μ‚¬μš© κ°€λŠ₯ν•œ λ ˆμ΄μ–΄(components Β· hooks Β· lib Β· api Β· stores) λ₯Ό 뢄리해 κ΅¬μ„±ν–ˆμŠ΅λ‹ˆλ‹€.

πŸ“¦FE
┣ πŸ“.github                              # GitHub μ„€μ •
┃ ┣ πŸ“ISSUE_TEMPLATE                     # 이슈 ν…œν”Œλ¦Ώ
┃ β”— πŸ“workflows                          # CI/CD μ›Œν¬ν”Œλ‘œμš°
┣ πŸ“frontend                             # ν”„λ‘ νŠΈμ—”λ“œ μ•± 루트
┃ ┣ πŸ“node_modules
┃ ┣ πŸ“public                             # 정적 μžμ‚°
┃ ┃ ┣ πŸ“fonts                            # μ›Ή 폰트
┃ ┃ β”— πŸ“icons                            # 퍼블릭 μ•„μ΄μ½˜/이미지
┃ ┣ πŸ“src
┃ ┃ ┣ πŸ“api                              # API ν΄λΌμ΄μ–ΈνŠΈ
┃ ┃ ┣ πŸ“assets                           # λ‚΄λΆ€ 에셋
┃ ┃ ┃ ┣ πŸ“ellipses                       # κ·Έλž˜ν”½
┃ ┃ ┃ ┣ πŸ“icons                          # UI μ•„μ΄μ½˜
┃ ┃ ┃ ┃ ┣ πŸ“chart
┃ ┃ ┃ β”— πŸ“loading
┃ ┃ ┣ πŸ“components                       # μž¬μ‚¬μš© μ»΄ν¬λ„ŒνŠΈ
┃ ┃ ┃ ┣ πŸ“chart                          # 차트 μ»΄ν¬λ„ŒνŠΈ/ν”ŒλŸ¬κ·ΈμΈ
┃ ┃ ┃ ┣ πŸ“common                         # 곡톡 UI
┃ ┃ ┃ ┃ β”— πŸ“navbar                       # λͺ¨λ°”일/νƒœλΈ”λ¦Ώ/λ°μŠ€ν¬ν†± Navbar
┃ ┃ ┣ πŸ“constants                        # μƒμˆ˜
┃ ┃ ┃ β”— πŸ“œkey.ts                         # ν‚€/μƒμˆ˜ λͺ¨μŒ
┃ ┃ ┣ πŸ“hooks                            # μ»€μŠ€ν…€ ν›…
┃ ┃ ┃ ┣ πŸ“channel
┃ ┃ ┃ ┣ πŸ“library
┃ ┃ ┃ ┣ πŸ“main
┃ ┃ ┃ ┣ πŸ“my
┃ ┃ ┃ β”— πŸ“report
┃ ┃ ┣ πŸ“layouts                          # 루트/곡톡 λ ˆμ΄μ•„μ›ƒ
┃ ┃ ┃ β”— πŸ“_components
┃ ┃ ┣ πŸ“lib                              # μœ ν‹Έ/맀퍼/검증
┃ ┃ ┃ ┣ πŸ“mappers                        # API λ§€ν•‘
┃ ┃ ┃ β”— πŸ“validation
┃ ┃ ┣ πŸ“pages                            # λΌμš°νŒ… νŽ˜μ΄μ§€
┃ ┃ ┃ ┣ πŸ“auth                           # 인증(λ¦¬λ‹€μ΄λ ‰νŠΈ/λͺ¨λ‹¬)
┃ ┃ ┃ ┃ β”— πŸ“_components
┃ ┃ ┃ ┣ πŸ“library                        # 라이브러리
┃ ┃ ┃ ┃ β”— πŸ“_components
┃ ┃ ┃ ┣ πŸ“main                           # 메인
┃ ┃ ┃ ┃ β”— πŸ“_components
┃ ┃ ┃ ┣ πŸ“my                             # λ§ˆμ΄νŽ˜μ΄μ§€
┃ ┃ ┃ ┃ β”— πŸ“_components
┃ ┃ ┃ ┣ πŸ“report                         # 리포트 상세 νŽ˜μ΄μ§€
┃ ┃ ┃ ┃ ┣ πŸ“_components
┃ ┃ ┃ ┃ ┃ ┣ πŸ“analysis
┃ ┃ ┃ ┃ ┃ ┣ πŸ“idea
┃ ┃ ┃ ┃ ┃ β”— πŸ“overview
┃ ┃ ┃ ┣ πŸ“setting                        # μ„€μ •(ν”„λ‘œν•„/λ™μ˜/νƒˆν‡΄)
┃ ┃ ┃ ┃ β”— πŸ“_components
┃ ┃ ┣ πŸ“router                           # λΌμš°ν„° μ„€μ •
┃ ┃ ┣ πŸ“stores                           # Zustand μ „μ—­ μƒνƒœ
┃ ┃ ┣ πŸ“styles                           # μ „μ—­/μœ ν‹Έ CSS
┃ ┃ ┃ β”— πŸ“œglobal.css
┃ ┃ ┣ πŸ“types                            # νƒ€μž… μ„ μ–Έ
┃ ┃ ┣ πŸ“utils                            # 곡톡 μœ ν‹Έ
┃ ┃ ┃ β”— πŸ“œformat.ts
┃ ┃ ┣ πŸ“œApp.tsx
┃ ┃ ┣ πŸ“œmain.tsx
┃ ┃ β”— πŸ“œvite-env.d.ts
┃ ┣ πŸ“œ.env
┃ ┣ πŸ“œ.gitignore
┃ ┣ πŸ“œ.svg.d.ts
┃ ┣ πŸ“œeslint.config.js
┃ ┣ πŸ“œindex.html
┃ ┣ πŸ“œpackage.json
┃ ┣ πŸ“œpnpm-lock.yaml
┃ ┣ πŸ“œREADME.md
┃ ┣ πŸ“œtsconfig.app.json
┃ ┣ πŸ“œtsconfig.json
┃ ┣ πŸ“œtsconfig.node.json
┃ ┣ πŸ“œvercel.json
┃ β”— πŸ“œvite.config.ts
┣ πŸ“scripts                             # 슀크립트(λΉŒλ“œ/μœ ν‹Έ)
┣ πŸ“œ.gitattributes
┣ πŸ“œ.gitignore
┣ πŸ“œ.prettierignore
┣ πŸ“œ.prettierrc
┣ πŸ“œREADME.md                           # 루트 README
β”— πŸ“œRules.md                            # μ»¨λ²€μ…˜ λ¬Έμ„œ

🐾 Frontend Architecture Flow

ν”„λ‘ νŠΈ μ•„ν‚€ν…μ²˜

✍️ Typography System Guide

ν”„λ‘œμ νŠΈ μ „μ—­μ—μ„œ μΌκ΄€λœ ν…μŠ€νŠΈ μŠ€νƒ€μΌμ„ μ μš©ν•˜κΈ° μœ„ν•΄ νƒ€μ΄ν¬κ·Έλž˜ν”Ό μ‹œμŠ€ν…œμ„ μ •μ˜ν–ˆμŠ΅λ‹ˆλ‹€.
λͺ¨λ“  νŒ€μ›μ€ typo.css에 μ •μ˜λœ 클래슀λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•˜λ©°, 상세 κ·œμΉ™κ³Ό 클래슀 λ ˆνΌλŸ°μŠ€λŠ” Typography.mdμ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

πŸ”« Challenges & Solutions

νŽ˜μ΄μ§€λ„€μ΄μ…˜μ˜ μˆ«μžκ°€ 음수둜 λ‚˜νƒ€λ‚˜λŠ” 문제
  • 원인 뢄석 νŽ˜μ΄μ§€ λ²„νŠΌμ€ β€œ5개 λ‹¨μœ„ μ°½(window)β€λ‘œ λ³΄μ—¬μ£ΌλŠ”λ°, currentPageκ°€ 이 창의 λ²”μœ„λ₯Ό 벗어났을 λ•Œ startPageλ₯Ό μž¬μ‘°μ •ν•΄μ£ΌλŠ” 둜직이 μ—†μœΌλ©΄, 쒌우 ν™”μ‚΄ν‘œ 클릭 μ‹œ μ°½ κΈ°μ€€μœΌλ‘œ κ³„μ‚°λœ κ°’(예: startPage - 1)이 κ·ΈλŒ€λ‘œ onChangePage둜 μ „λ‹¬λ©λ‹ˆλ‹€. 특히 데이터 μ‚­μ œ λ“±μœΌλ‘œ totalItemsκ°€ 쀄어듀어 totalPageCountκ°€ κΈ‰κ²©νžˆ μž‘μ•„μ§ˆ λ•Œ, 이전에 보던 큰 νŽ˜μ΄μ§€ λ²ˆν˜Έκ°€ 남아 currentPage > totalPageCount μƒνƒœκ°€ λ©λ‹ˆλ‹€. 이 μƒνƒœμ—μ„œ 쒌츑 이동을 λ°˜λ³΅ν•˜κ±°λ‚˜, μœˆλ„μš° μ•žμͺ½μœΌλ‘œ μˆœκ°„ μ΄λ™ν•˜λ©΄ startPage와 currentPage의 λΆˆμΌμΉ˜κ°€ 컀지고, κ²°κ΅­ 0μ΄λ‚˜ 음수 νŽ˜μ΄μ§€κ°€ κ³„μ‚°λ˜μ–΄ 전달될 수 μžˆμŠ΅λ‹ˆλ‹€. μš”μ•½ν•˜λ©΄, (1) μœˆλ„μš° 이동 동기화 λΆ€μž¬ + (2) νŽ˜μ΄μ§€ 경계값(1~totalPageCount) ν΄λž¨ν•‘ 미흑이 κ²°ν•©ν•΄ λ°œμƒν•œ λ²„κ·Έμž…λ‹ˆλ‹€.

  • ν•΄κ²° 방법 currentPageκ°€ λ³΄μ΄λŠ” 창의 경계λ₯Ό λ²—μ–΄λ‚  λ•Œ μžλ™μœΌλ‘œ startPageλ₯Ό μž¬μ‘°μ •ν•˜μ—¬, 항상 ν˜„μž¬ νŽ˜μ΄μ§€κ°€ 5개짜리 μ°½ μ•ˆμ— λ“€μ–΄μ˜€κ²Œ ν–ˆμŠ΅λ‹ˆλ‹€. (μ•„λž˜ 둜직이 핡심)

    useEffect(() => {
        if (currentPage >= startPage + 5 && !noNext) {
            // 였λ₯Έμͺ½ 경계 초과 β†’ μ°½ μ‹œμž‘μ μ„ ν˜„μž¬ νŽ˜μ΄μ§€λ‘œ 이동
            setStartPage(currentPage)
        } else if (currentPage <= startPage - 1 && !noPrev) {
            // μ™Όμͺ½ 경계 λ°– β†’ ν˜„μž¬ νŽ˜μ΄μ§€κ°€ 창의 맨 μ•žμ— μ˜€λ„λ‘ 이동
            setStartPage(currentPage - 4)
        }
    }, [noPrev, noNext, startPage, currentPage, setStartPage])

    이 λ‘œμ§μ„ λ„£μœΌλ©΄, 쒌우 ν™”μ‚΄ν‘œ/번호 λ²„νŠΌμœΌλ‘œ λΉ λ₯΄κ²Œ μ΄λ™ν•˜κ±°λ‚˜, μ•„μ΄ν…œμ΄ 쀄어 총 νŽ˜μ΄μ§€ μˆ˜κ°€ μ€„μ–΄λ“œλŠ” μƒν™©μ—μ„œλ„ μœˆλ„μš°μ™€ ν˜„μž¬ νŽ˜μ΄μ§€κ°€ 항상 λ™κΈ°ν™”λ˜μ–΄, startPage - 1 같은 계산이 0 μ΄ν•˜λ‘œ λ–¨μ–΄μ§€λŠ” κ²½λ‘œκ°€ μ°¨λ‹¨λ©λ‹ˆλ‹€. μž¬ν˜„ 및 확인 κ³Όμ •: 1. 데이터가 μ—¬λŸ¬ νŽ˜μ΄μ§€(예: 18개, 6개/νŽ˜μ΄μ§€ β†’ 총 3νŽ˜μ΄μ§€)일 λ•Œ 3νŽ˜μ΄μ§€λ‘œ 이동. 2. 일뢀 μ•„μ΄ν…œ μ‚­μ œλ‘œ totalItemsλ₯Ό 7λΆ€ν„° 12개 μˆ˜μ€€μœΌλ‘œ 쀄여 총 νŽ˜μ΄μ§€ 수λ₯Ό 2둜 μΆ•μ†Œ. 3. 쒌츑 ν™”μ‚΄ν‘œ/νŽ˜μ΄μ§€ λΉ λ₯Έ 클릭 β†’ (μˆ˜μ • μ „) μ°½/ν˜„μž¬νŽ˜μ΄μ§€ 뢈일치둜 0 λ˜λŠ” 음수 νŽ˜μ΄μ§€κ°€ μ°νžˆλŠ” 둜그 확인. 4. μœ„ useEffect μΆ”κ°€ ν›„ 동일 μ‹œλ‚˜λ¦¬μ˜€ μž¬μ‹€ν–‰ β†’ 음수 νŽ˜μ΄μ§€ λ°œμƒν•˜μ§€ μ•ŠμŒ, λ³΄μ΄λŠ” λ²„νŠΌλ„ 항상 1λΆ€ν„° 유효 λ²”μœ„λ₯Ό μœ μ§€.

ν…μŠ€νŠΈ μ˜μ—­ κΈ€μž 수 μ œν•œμ΄ μ μš©λ˜μ§€ μ•ŠλŠ” 문제
  • 원인 뢄석 Textarea μ»΄ν¬λ„ŒνŠΈκ°€ maxLengthλ₯Ό μ„ νƒκ°’μœΌλ‘œ 받도둝 λ˜μ–΄ μžˆλŠ”λ°, 일뢀 ν™”λ©΄μ—μ„œ 이 값을 μ „λ‹¬ν•˜μ§€ μ•Šμ•„ μ‹€μ œ <textarea>에 maxlength 속성이 μž‘νžˆμ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. μžλ™ 높이 쑰절 λ•Œλ¬Έμ— 슀크둀만 생겨 μ œν•œμ΄ μžˆλŠ” κ²ƒμ²˜λŸΌ λ³΄μ˜€μ§€λ§Œ, μ‹€μ œλ‘œλŠ” λ¬΄μ œν•œ μž…λ ₯이 κ°€λŠ₯ν–ˆμŠ΅λ‹ˆλ‹€.

  • ν•΄κ²° 방법

    import { useEffect, useRef, useState, type PropsWithChildren } from 'react'
    
    interface TextareaProps {
        id: string // textarea μš”μ†Œμ˜ 고유 id
        value: string // textarea의 κ°’
        onChange: (value: string) => void // μ‚¬μš©μžκ°€ μž…λ ₯ν•œ ν…μŠ€νŠΈκ°€ 변경될 λ•Œ ν˜ΈμΆœλ˜λŠ” ν•¨μˆ˜
        placeholder?: string
        initialRows?: number // row 개수둜 textarea λ°•μŠ€μ˜ 초기 높이λ₯Ό μ§€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ””ν΄νŠΈλŠ” 1
        disabled?: boolean
        className?: string
        maxLength?: number
    }
    
    const Textarea = ({
        id,
        value,
        onChange,
        placeholder,
        initialRows = 1,
        children,
        disabled = false,
        maxLength,
        className,
    }: PropsWithChildren<TextareaProps>) => {
        const [isFocused, setIsFocused] = useState(false)
        const textareaRef = useRef<HTMLTextAreaElement>(null)
    
        // Desktop, Tablet: 5μ€„κΉŒμ§€ textareaκ°€ λŠ˜μ–΄λ‚©λ‹ˆλ‹€. 6쀄 λΆ€ν„°λŠ” μŠ€ν¬λ‘€ν•΄μ„œ ν™•μΈν•©λ‹ˆλ‹€.
        // Mobile: 3μ€„κΉŒμ§€ textareaκ°€ λŠ˜μ–΄λ‚©λ‹ˆλ‹€. 4쀄 λΆ€ν„°λŠ” μŠ€ν¬λ‘€ν•΄μ„œ ν™•μΈν•©λ‹ˆλ‹€.
        useEffect(() => {
            const textarea = textareaRef.current
            if (!textarea) return
    
            const handleResize = () => {
                textarea.style.height = 'auto'
    
                const isMobile = window.innerWidth <= 768
                const maxLines = isMobile ? 3 : 5
                const maxHeight = 32 * maxLines
                textarea.style.height = Math.min(textarea.scrollHeight, maxHeight) + 'px'
            }
            handleResize()
    
            window.addEventListener('resize', handleResize)
            return () => window.removeEventListener('resize', handleResize)
        }, [value])
    
        return (
            <divclassName={`
                    flex flex-col w/full min-w-[240px] tablet:min-w-[540px] desktop:min-w-[744px] p-4 space-y-6
                    border placeholder-gray-600 bg-neutral-white-opacity10 rounded-2xl
                    transition duration-300 ${isFocused ? 'border-gray-400' : 'border-transparent'} ${className ?? ''}
                `}
            >
                <textarearef={textareaRef}
                    id={id}
                    value={value}
                    disabled={disabled}
                    onChange={(e) => onChange(e.target.value)}
                    onFocus={() => setIsFocused(true)}
                    onBlur={() => setIsFocused(false)}
                    rows={initialRows}
                    placeholder={placeholder}
                    maxLength={maxLength}
                    className="
                        w-full h-fit max-h-[120px] px-2 outline-none resize-none focus:placeholder-transparent
                        text-[14px] leading-[150%] tracking-[-0.35px] tablet:text-[16px] tablet:tracking-[-0.4px]
                    "
                />
    
                {children && <>{children}</>}
            </div>
        )
    }
    
    export default Textarea

    λͺ¨λ‹¬λ³„ μƒμˆ˜κ°’(μ½˜μ…‰νŠΈ 500자, νƒ€κ²Ÿ 100자)을 ν˜ΈμΆœλΆ€μ—μ„œ maxLength둜 λ°˜λ“œμ‹œ μ „λ‹¬ν•˜μ—¬ μ œν•œμ„ ν™•μ‹€νžˆ μ μš©ν–ˆμŠ΅λ‹ˆλ‹€.

νŽ˜μ΄μ§€ 전체가 μŠ€μΌˆλ ˆν†€ μ²˜λ¦¬λ˜μ–΄ μ‚¬μš©μžκ°€ 닡닡함을 느끼게 λ˜λŠ” 문제
  • 원인 뢄석 λ‚΄ 채널 νŽ˜μ΄μ§€μ—μ„œ 데이터가 pending일 경우λ₯Ό ν•œ λ²ˆμ— κ΄€λ¦¬ν•˜μ—¬ μ‚¬μš©μžμ—κ²Œ λ‹΅λ‹΅ν•œ λŠλ‚Œμ„ μ£ΌλŠ” λ¬Έμ œκ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

  • ν•΄κ²° 방법 μŠ€μΌˆλ ˆν†€ μ»΄ν¬λ„ŒνŠΈλ₯Ό Skeletonκ³Ό VideoSkeleton으둜 λΆ„λ¦¬ν•˜κ³ , 채널 λŒ€μ‹œλ³΄λ“œ 정보와 λΉ„λ””μ˜€λ¦¬μŠ€νŠΈμ˜ pending μƒνƒœκ°€ 각각 κ΄€λ¦¬λ˜κ²Œ ν–ˆμŠ΅λ‹ˆλ‹€.

    if (isMePending)
        return (
            <div>
                <Skeleton />
            </div>
        )
    if (isVideoPending || isShortsPending) return <VideoSkeleton />
ν”„λ‘œν•„ 이미지 λ³€κ²½ ν›„ μ‚¬μ΄λ“œλ°”μ— μ¦‰μ‹œ λ°˜μ˜λ˜μ§€ μ•Šκ³ , μƒˆλ‘œκ³ μΉ¨ μ‹œ μ„€μ • 이미지가 μ‚¬λΌμ§€λŠ” 문제
  • 원인 뢄석 ν”„λ‘œν•„ λ³€κ²½ 직후에도 μ‚¬μ΄λ“œλ°”κ°€ κ°±μ‹  μ „ μ‚¬μš©μž 정보(μΊμ‹œ) λ₯Ό 계속 μ°Έμ‘°ν–ˆκ³ , staleTime: Infinity둜 μžλ™ μž¬μš”μ²­μ΄ μ—†μ–΄ μ΅œμ‹  이미지가 λ°˜μ˜λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. λ˜ν•œ μΊμ‹œ λ¬΄νš¨ν™”κ°€ μ—†μ–΄μ„œ μƒˆλ‘œκ³ μΉ¨ μ‹œ ν™”λ©΄λ³„λ‘œ μ„œλ‘œ λ‹€λ₯Έ μ†ŒμŠ€κ°€ λ’€μ„žμ΄λ©° 이전 이미지가 사라진 κ²ƒμ²˜λŸΌ λ³΄μ˜€μŠ΅λ‹ˆλ‹€.

  • ν•΄κ²° 방법

    import { useQuery } from '@tanstack/react-query'
    import type { User } from '../../types/channel'
    import { fetchMyProfile as fetchMyProfileAPI } from '../../api/user'
    export const useFetchMyProfile = (enabled = true) =>
        useQuery<User, Error, User, [string]>({
            queryKey: ['my-profile'],
            queryFn: async () => (await fetchMyProfileAPI()).result,
            staleTime: Infinity,
            retry: false,
            enabled,
        })
    // settings/ProfileImageUploader.tsx (ν”„λ‘œν•„ 이미지 λ³€κ²½ 성곡 μ‹œ)
    import { useQueryClient } from '@tanstack/react-query'
    const queryClient = useQueryClient()
    
    const onSuccessUpdate = async () => {
    // 이미지 μ—…λ‘œλ“œ/μˆ˜μ • 성곡 ν›„ μ΅œμ‹  μ •λ³΄λ‘œ 동기화
    await queryClient.invalidateQueries({ queryKey: ['my-profile'] })
    }
    
    // components/Sidebar.tsx (μ‚¬μ΄λ“œλ°”μ™€ μ„€μ • λͺ¨λ‘ 같은 훅을 ꡬ독)
    import { useFetchMyProfile } from '../hooks/queries/useFetchMyProfile'
    const { data: me } = useFetchMyProfile()
    <img src={me?.profileImage ?? '/images/default.png'} alt="profile" />

    ν•΄λ‹Ή 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ hooks/queries 에 fetchMyProfile.tsx νŒŒμΌμ„ λ§Œλ“€μ–΄ ν”„λ‘œν•„μ„ λ³€κ²½ν•˜λ©΄ invalidateQueries({queryKey: ['my-profile']})을 톡해 μΊμ‹œλ₯Ό λ¬΄νš¨ν™”ν•œ ν›„ fetchMyProfile.tsxκ°€ APIλ₯Ό λ‹€μ‹œ λΆˆλŸ¬μ™€ μ΅œμ‹  ν”„λ‘œν•„ 데이터λ₯Ό κ°€μ Έμ˜€λ„λ‘ μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€

πŸ€– Gemini AI PR Review Automation Pipeline

저희 μ±„λ„λ§μ—μ„œλŠ” 보닀 λ‚˜μ€ μ½”λ“œλ₯Ό μœ„ν•΄ Gemini AIλ₯Ό PR 리뷰에 μžλ™ν™”μ‹œμΌœ λ°±μ—”λ“œ, ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

ν”„λ‘ νŠΈ μž¬λ―Έλ‚˜μ΄


default.mp4

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 7

Languages