From 9282de16af060ab68b7a69afd9a251377b4ca4d7 Mon Sep 17 00:00:00 2001 From: gwansikk Date: Sun, 1 Jun 2025 00:53:12 +0900 Subject: [PATCH 01/24] docs: improve documentation --- docs/src/content/en/installation.mdx | 14 +- docs/src/content/ko/_meta.ts | 47 ++-- .../ko/{quick-start.mdx => basic-example.mdx} | 0 docs/src/content/ko/create-tracker.mdx | 133 +++++----- docs/src/content/ko/index.mdx | 229 +++++------------- docs/src/content/ko/installation.mdx | 18 +- .../content/ko/{hook.mdx => useTracker.mdx} | 7 +- docs/src/content/ko/why-event-tracker.mdx | 186 ++++++++++++++ 8 files changed, 349 insertions(+), 285 deletions(-) rename docs/src/content/ko/{quick-start.mdx => basic-example.mdx} (100%) rename docs/src/content/ko/{hook.mdx => useTracker.mdx} (94%) create mode 100644 docs/src/content/ko/why-event-tracker.mdx diff --git a/docs/src/content/en/installation.mdx b/docs/src/content/en/installation.mdx index d2c9a98..4d9af8e 100644 --- a/docs/src/content/en/installation.mdx +++ b/docs/src/content/en/installation.mdx @@ -2,18 +2,6 @@ Using npm: -```bash +```shell npm2yarn npm install @offlegacy/event-tracker ``` - -Using yarn: - -```bash -yarn add @offlegacy/event-tracker -``` - -Using pnpm: - -```bash -pnpm add @offlegacy/event-tracker -``` diff --git a/docs/src/content/ko/_meta.ts b/docs/src/content/ko/_meta.ts index 2a81597..df5ee23 100644 --- a/docs/src/content/ko/_meta.ts +++ b/docs/src/content/ko/_meta.ts @@ -1,6 +1,6 @@ -import { MetaRecord } from "nextra"; +import type { MetaRecord } from "nextra"; -const meta: MetaRecord = { +export default { "getting-started-separator": { type: "separator", title: "시작하기", @@ -12,22 +12,22 @@ const meta: MetaRecord = { layout: "default", }, }, - installation: { - title: "설치", + "why-event-tracker": { + title: "왜 Event Tracker인가요?", theme: { toc: true, layout: "default", }, }, - "quick-start": { - title: "가이드", + installation: { + title: "설치하기", theme: { toc: true, layout: "default", }, }, - "api-separator": { - title: "API", + "api-reference-separator": { + title: "API 레퍼런스", type: "separator", }, "create-tracker": { @@ -38,21 +38,22 @@ const meta: MetaRecord = { }, }, components: { - title: "컴포넌트", + title: "Components", theme: { toc: true, layout: "default", + collapsed: true, }, }, - hook: { - title: "hook", + useTracker: { + title: "useTracker", theme: { toc: true, layout: "default", }, }, "advanced-separator": { - title: "고급", + title: "가이드", type: "separator", }, batching: { @@ -62,6 +63,22 @@ const meta: MetaRecord = { layout: "default", }, }, -}; - -export default meta; + "data-type-validation": { + title: "데이터 타입 검증", + theme: { + toc: true, + layout: "default", + }, + }, + "example-separator": { + title: "예제", + type: "separator", + }, + "basic-example": { + title: "기본", + theme: { + toc: true, + layout: "default", + }, + }, +} satisfies MetaRecord; diff --git a/docs/src/content/ko/quick-start.mdx b/docs/src/content/ko/basic-example.mdx similarity index 100% rename from docs/src/content/ko/quick-start.mdx rename to docs/src/content/ko/basic-example.mdx diff --git a/docs/src/content/ko/create-tracker.mdx b/docs/src/content/ko/create-tracker.mdx index 5b6cbed..8f0dbfe 100644 --- a/docs/src/content/ko/create-tracker.mdx +++ b/docs/src/content/ko/create-tracker.mdx @@ -1,6 +1,6 @@ -# createTracker(config) +# createTracker -원하는 구성으로 트래커 인스턴스를 생성하는 메인 함수입니다. +`createTracker`는 사용자 정의 설정으로 트래커 인스턴스를 생성하는 함수입니다. 이 함수의 파라미터를 통해 이벤트 트래킹을 정의하고, 반환된 튜플을 통해 트래커 인스턴스를 사용할 수 있습니다. ```tsx const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTracker] = createTracker({ @@ -19,95 +19,92 @@ const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTrack }); ``` -### 구성 옵션 +## Reference -#### init +### Parameters -- Type: `(initialContext: Context, setContext: SetContext) => void | Promise` -- 옵셔널 -- 모든 이벤트가 발생하기 전에 실행되는 함수 -- Promise를 반환하면 이벤트가 Promise가 해결될 때까지 지연됩니다. +`createTracker`는 하나의 설정 객체를 인자로 받습니다. -#### send +| 옵션 | 타입 | 설명 | +| ------------ | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | +| `init` | `(initialContext: Context, setContext: SetContext) => void \| Promise` | 모든 이벤트 발생 전에 실행되는 초기화 함수. Promise 지원. | +| `send` | `(params: EventParams \| (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` | 이벤트 전송 함수. Promise 지원. | +| `DOMEvents` | [`DOMEvents`](https://developer.mozilla.org/docs/Web/API/Event) | React DOM 이벤트 핸들러 모음 (`onClick`, `onMouseEnter` 등) | +| `impression` | `ImpressionOptions` | 노출 이벤트 트래킹 설정 | +| `pageView` | `PageViewOptions` | 페이지 뷰 트래킹 설정 | +| `batch` | `BatchConfig` | 이벤트 배칭 설정 | +| `schemas` | `SchemaConfig` | 이벤트 스키마 검증 설정 | -- Type: `(params: EventParams | (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` -- 옵셔널 -- 이벤트를 보내는 표준 함수 -- Promise를 반환하면 이벤트가 Promise가 해결될 때까지 지연됩니다. +--- -#### DOMEvents +#### `init` -- Type: `DOMEvents` -- 옵셔널 -- 표준 React DOM 이벤트 (`onClick`, `onMouseEnter`, 등)의 모음 -- 각 핸들러는 ``에서 해당 이벤트가 발생할 때 실행됩니다. -- 핸들러가 Promise를 반환하면 후속 이벤트 콜백이 해결될 때까지 지연됩니다. +- 타입: `(initialContext: Context, setContext: SetContext) => void | Promise` +- 설명: 모든 이벤트가 발생하기 전, 초기 컨텍스트를 설정하는 함수입니다. 비동기 함수 지원. -#### impression +#### `send` -노출 이벤트를 트래킹하기 위한 설정입니다. +- 타입: `(params: EventParams | (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` +- 설명: 이벤트를 외부로 전송하는 함수입니다. 비동기 처리 가능. -- onImpression +#### `DOMEvents` - - Type: `(params: EventParams | (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` - - Optional - - Executed when impression occurs on a `` child - - Promise return value will delay subsequent callbacks +- 타입: `DOMEvents` +- 설명: 표준 React DOM 이벤트 핸들러 객체입니다. `` 컴포넌트에서 사용됩니다. -- options - - Type: `ImpressionOptions` - - 옵셔널 - - 노출 이벤트 트래킹 구성 옵션: - - `threshold`: 노출 필요 비율 (기본값: 0.2) - - `freezeOnceVisible`: 노출 후 교차 상태 고정 (기본값: true) - - `initialIsIntersecting`: 초기 교차 상태 (기본값: false) +#### `impression` -#### pageView +- `impression.onImpression` -페이지 뷰 이벤트를 트래킹하기 위한 설정입니다. + - 타입: `(params: EventParams | (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` + - 설명: `` 발생 시 실행되는 콜백 -##### onPageView +- `impression.options` -- Type: `(params: EventParams | (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` -- 옵셔널 -- ``가 마운트될 때 실행됩니다. -- Promise를 반환하면 후속 이벤트가 해결될 때까지 지연됩니다. + - 타입: `ImpressionOptions` + - 속성: -#### batch + - `threshold`: 노출 비율 기준 (기본값: 0.2) + - `freezeOnceVisible`: 노출 후 상태 고정 여부 (기본값: true) + - `initialIsIntersecting`: 초기 교차 여부 (기본값: false) -이벤트 배칭을 위한 설정입니다. +#### `pageView` -- Type: `BatchConfig` -- 옵셔널 -- Properties: - - `enable`: 배칭 활성화 (기본값: false) - - `interval`: 버퍼링 간격 (ms) (기본값: 3000) - - `thresholdSize`: 최대 배칭 크기 (기본값: 25) - - `onFlush`: 배치 비우기 처리 함수 (활성화된 경우 필요) - - `onError`: 오류 처리 함수 (옵셔널) +- `pageView.onPageView` -#### schemas + - 타입: `(params: EventParams | (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` + - 설명: `` 마운트 시 실행됩니다. -데이터 타입 검증을 위한 설정입니다. +#### `batch` -- Type: `SchemaConfig` -- 옵셔널 -- Properties: - - `schemas`: [Zod](https://zod.dev/) 스키마의 레코드 - - `onSchemaError`?: 스키마 검증 오류 처리 함수 - - `abortOnError`?: 스키마 검증 오류가 발생한 경우 이벤트 추적 중단 여부 (기본값: false) +- 타입: `BatchConfig` +- 속성: -### Return Value + - `batch.enable`: 배치 활성화 여부 (기본값: false) + - `batch.interval`: 전송 간격(ms, 기본값: 3000) + - `batch.thresholdSize`: 최대 배치 크기 (기본값: 25) + - `batch.onFlush`: 이벤트 전송 처리 함수 + - `batch.onError`: 오류 발생 시 처리 함수 (옵셔널) -`createTracker` 함수는 다음 튜플을 반환합니다: +#### `schemas` -1. 트래킹 컴포넌트를 포함하는 객체: +- 타입: `SchemaConfig` +- 속성: - - [`Provider`](/docs/components) - - [`DOMEvent`](/docs/components/dom-event) - - [`Click`](/docs/components/click) - - [`Impression`](/docs/components/impression) - - [`PageView`](/docs/components/page-view) - - [`SetContext`](/docs/components/set-context) + - `schemas.schema`: [Zod](https://zod.dev/) 기반의 스키마 정의 + - `schemas.onSchemaError`: 스키마 오류 발생 시 처리 함수 + - `schemas.abortOnError`: 오류 시 이벤트 중단 여부 (기본값: false) -2. [커스텀 hook](/docs/hook) +### Returns + +`createTracker`는 다음 두 값을 포함한 튜플을 반환합니다. + +```tsx +const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTracker] = createTracker({...}) +``` + +1. 이벤트 컴포넌트 (`Tracker`) + + - `Provider`, `DOMEvent`, `Click`, `Impression`, `PageView`, `SetContext` + +2. 커스텀 훅 (`useTracker`) diff --git a/docs/src/content/ko/index.mdx b/docs/src/content/ko/index.mdx index 97aed6d..aa96cf6 100644 --- a/docs/src/content/ko/index.mdx +++ b/docs/src/content/ko/index.mdx @@ -1,202 +1,87 @@ -# 소개 - -## `event-tracker`가 필요한 이유 - -이벤트 트래킹은 많은 보일러플레이트 코드와 복잡성을 수반하는 작업입니다. -다음 예시를 살펴보세요. - -```tsx {14,15, 23-30} -// 전통적인 이벤트 트래킹 방식 - -const Page = () => { - const { user, userId } = useUser(); - - return ( -
-

User: {user.name}

- -
- ); -}; - -const Counter = ({ userId }: { userId: string }) => { - // 이벤트 트래킹만을 위해서 'userId'를 prop으로 전달받음 - - const [count, setCount] = useState(0); - const { track } = useTrackEvent(); - - const handleIncrement = () => { - setCount(count + 1); - - track({ - event: "click", - params: { - type: "count", - value: count + 1, - userId, - }, - }); - }; - - return ( -
-

Count: {count}

- -
- ); -}; -``` +import { Steps } from "nextra/components"; -위 코드로 알아본 이벤트 트래킹으로 인한 두 가지 주요 불편사항은 다음과 같습니다: +# 소개 -1. **Prop Drilling**: `userId`가 `` 컴포넌트에서 `` 컴포넌트로 prop으로 전달됩니다. `` 컴포넌트가 컴포넌트 트리 깊숙이 중첩되어 있다면, prop drilling이 더 심각해져 코드의 가독성과 유지보수성이 저하될 수 있습니다. -2. **이벤트 트래킹 로직과 비즈니스 로직의 강결합**: `handleIncrement` 함수는 카운트 증가 로직과 이벤트 트래킹 로직을 모두 포함하고 있습니다. 이벤트 트래킹 로직을 분리하면 코드를 더 깔끔하고 유지보수하기 쉽게 만들 수 있습니다. +Event Tracker 문서에 오신 것을 환영합니다. -## `event-tracker`가 제시하는 새로운 패러다임 +## Event Tracker가 무엇인가요? -`event-tracker`는 이벤트 트래킹을 위한 새로운 패러다임을 소개합니다. -`event-tracker`가 제시하는 선언적 방식은 전통적으로 이벤트 트래킹과 관련된 복잡성을 단순화하여, -모든 개발자들이 쉽게 접근할 수 있도록 합니다. +Event Tracker는 복잡한 이벤트 트래킹 구현 과정을 단순화하고, 개발자가 비즈니스 로직에 더 집중할 수 있도록 돕는 선언적 방식의 React 라이브러리입니다. 모든 규모의 애플리케이션에서 이벤트 트래킹을 쉽고 효율적으로 관리할 수 있도록 설계되었습니다. -### 선언적 이벤트 트래킹 +```tsx +import { createTracker } from "@offlegacy/event-tracker"; -```tsx {5, 10, 24, 26} -const Page = () => { - const { user, userId } = useUser(); +// 트래커 인스턴스 생성 +const [Track, useTracker] = createTracker({ + DOMEvents: { + onClick: (params, context) => { + log("Click event:", params, context); + }, + }, +}); +// 앱에서 사용하기 +function App() { return ( - -
-

User: {user.name}

- -
+ + + + ); -}; - -const Counter = () => { - const [count, setCount] = useState(0); +} +``` - const handleIncrement = () => { - setCount(count + 1); - }; +### 주요 기능 - return ( -
-

Count: {count}

- - - -
- ); -}; -``` +Event Tracker는 개발자 경험과 애플리케이션 성능을 모두 고려한 다양한 기능을 제공합니다. -`event-tracker`를 사용하면 선언적 이벤트 트래킹이 가능해져 코드 가독성이 향상되고 복잡성이 감소합니다. 이는 개발자들이 이벤트 트래킹을 더 쉽게 이해하고 사용할 수 있도록 돕습니다. -이제 `handleIncrement` 함수는 카운트 증가에만 책임이 있고, 이벤트 트래킹은 `` 컴포넌트가 처리합니다. -**이러한 선언적 접근 방식은 개발자가 '어떻게 트래킹할지'가 아닌 '무엇을 트래킹할지'에 집중하도록 합니다.** -어떻게 트래킹할지는 React 앱 외부에서 정의되어야 합니다. +| Feature | Description | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 타입 안정성을 갖춘 선언적 API | [TypeScript](https://www.typescriptlang.org/)를 완벽하게 지원하여 개발 과정에서의 오류를 줄이고, 자동 완성을 통해 생산성을 높입니다. | +| 강력한 데이터 타입 검증 | [Zod](https://zod.dev/)를 활용한 스키마 기반 검증으로 데이터의 신뢰성을 확보합니다. | +| 최적화된 성능 | 배칭이나 디바운스, 스로틀링 기능을 통해 네트워크 요청을 최소화하고 애플리케이션 성능에 미치는 영향을 줄입니다. | +| 실행 순서 보장 | 비동기적으로 발생하는 이벤트들에 대해서도 의도한 순서대로 처리되도록 보장합니다. | +| 애널리틱스 도구 독립성 | 특정 애널리틱스 서비스에 종속되지 않고, 원하는 모든 도구([Google Analytics](https://analytics.google.com/), [Amplitude](https://amplitude.com/) 등)와 유연하게 통합할 수 있습니다. | +| 명확한 관심사 분리 | 트래킹 로직과 비즈니스 로직을 효과적으로 분리하여 코드의 유지보수성과 확장성을 극대화합니다. | -### 애플리케이션 외부에서 이벤트 트래킹 응집도 개선 +## 핵심 개념 -```tsx -const [Track] = createTracker({ - DOMEvents: { - onClick: (params, context) => { - log({ - event: "click", - params: { - ...params, - userId: context.userId, - }, - }); - }, - }, - onImpression: (params, context) => { - log({ - event: "impression", - params: { - ...params, - userId: context.userId, - }, - }); - }, -}); -``` +Event Tracker를 효과적으로 사용하기 위해 알아야 할 몇 가지 핵심 개념이 있습니다. -이제 **'어떻게 추적할지'**에 대한 코드가 비즈니스 로직과 분리되었습니다. -애플리케이션 외부에 위치하므로 비즈니스 로직을 변경하지 않고도 이벤트 트래킹 로직을 수정할 수 있습니다. + -### 데이터 타입 검증 +### 인스턴스 (`createTracker`) -`event-tracker`는 [Zod](https://zod.dev/)를 사용하여 스키마 기반으로 데이터 유효성 검증을 제공합니다. +라이브러리의 가장 기본적인 출발점입니다. `createTracker` 함수를 사용하여 트래커 인스턴스(`Track` 컴포넌트 컬렉션과 `useTracker` 훅)를 생성합니다. 이때, DOM 이벤트 핸들러, 노출(Impression) 이벤트 핸들러, 스키마 등을 설정하여 이벤트 트래킹을 정의합니다. -```tsx -import { z } from "zod"; -import { createTracker } from "@offlegacy/event-tracker"; +### 프로바이더 (`Track.Provider`) -interface Context { - // ... -} +React의 Context API를 기반으로 구현되었습니다. 애플리케이션 또는 특정 컴포넌트 트리의 최상단에서 `Track.Provider`로 감싸 하위 컴포넌트들에 트래킹에 필요한 공통 데이터(컨텍스트)를 제공합니다. 예를 들어, `userId`, `pageName` 등의 정보를 컨텍스트로 전달하면, 각 이벤트 트래킹 시 이 정보를 활용할 수 있습니다. -interface Params { - // ... -} +### 이벤트 컴포넌트 (`Track.Click`, `Track.PageView` 등) -// 스키마 정의 -const schemas = { - page_view: z.object({ - title: z.string(), - }), - click_button: z.object({ - target: z.string(), - }), -}; - -// 트래커 설정 -const [Track] = createTracker({ - // 기타 설정... - - schema: { - schemas: { - page_view, - click_button, - }, - onSchemaError: (error) => { - console.error("Schema validation error:", error); - }, - abortOnError: true, - }, -}); +선언적으로 이벤트를 트래킹할 수 있도록 제공되는 특수 컴포넌트들입니다. `createTracker` 리턴 배열의 첫 번째 요소입니다. -// 스키마 사용하기 -; -; -``` +- `Track.Click`: 자식 요소에서 클릭 이벤트가 발생했을 때 트래킹합니다. +- `Track.Impression`: 자식 요소가 화면에 노출되었을 때 트래킹합니다. +- `Track.PageView`: 컴포넌트가 마운트될 때 페이지 뷰 이벤트를 트래킹합니다. -## 주요 기능 +이 외에도 다양한 사용자 인터랙션 및 생명주기 이벤트에 대응하는 컴포넌트를 제공하거나 커스텀하여 사용할 수 있습니다. 각 컴포넌트는 `context`, `params` prop을 통해 해당 이벤트와 관련된 특정 데이터를 전달받고 활용할 수 있습니다. -- 🎯 **타입 안정성을 갖춘 API**: 타입 안전성을 갖춘 선언적 이벤트 트래킹 제공 -- 🛡️ **데이터 타입 검증**: 스키마를 사용한 데이터 타입 안전성과 유효성 보장 -- ⚡️ **최적화된 성능**: 이벤트 배칭을 통한 향상된 성능 -- 🔄 **순서 보장**: 비동기 작업에 대한 실행 순서 보장 -- 🔌 **애널리틱스 도구와의 독립성**: 선택한 모든 애널리틱스 도구와 함께 작동 -- 🧩 **관심사의 분리**: 트래킹 로직과 비즈니스 로직의 분리 유지 -- 📦 **작은 번들 사이즈**: 애플리케이션에 미치는 번들 크기 영향 최소화 +### 커스텀 훅 (`useTracker`) -## 핵심 개념 +컴포넌트의 생명주기나 DOM 이벤트와 직접적으로 관련되지 않은, 보다 복잡하거나 조건부적인 이벤트 트래킹이 필요할 때 사용합니다. `useTracker` 훅을 사용하면 `Track.Provider`로부터 컨텍스트 정보를 가져오고, 정의된 트래킹 로직을 명령형으로 실행할 수 있습니다. -`event-tracker`는 몇 가지 핵심 개념을 기반으로 구축되었습니다: + -1. **Tracker 생성**: `createTracker`를 사용하여 원하는 이벤트 트래킹 지침을 사용하여 트래커 인스턴스를 생성합니다. -2. **Provider**: `Track.Provider`를 사용하여 앱을 래핑하여 컨텍스트를 제공합니다. -3. **이벤트 컴포넌트**: `Track.Click` 또는 `Track.Impression`과 같은 이벤트 컴포넌트를 사용하여 이벤트를 트래킹합니다. -4. **커스텀 Hook**: `useTracker` 훅을 사용하여 명령적으로 트래킹 할 수 있습니다. +## 다음 단계 -다른 섹션에서 각 기능에 대한 자세한 문서를 확인할 수 있습니다: +이러한 핵심 개념들은 서로 유기적으로 작동하여 Event Tracker의 강력하고 유연한 이벤트 트래킹 환경을 구성합니다. 더 자세한 사용법과 각 기능에 대한 심층적인 내용은 아래 문서들에서 확인하실 수 있습니다. -- [createTracker](/docs/create-tracker) - `createTracker`에 대한 자세한 API 문서 -- [components](/docs/components) - 사용 가능한 트래킹 컴포넌트 -- [hook](/docs/hook) - 훅을 사용하여 트래킹 -- [Batching](/docs/batching) - 이벤트 배칭을 통한 성능 최적화 -- [Data Type Validation](/docs/data-type-validation) - 스키마를 사용하여 데이터 타입 안전성과 유효성 보장 +- [왜 Event Tracker인가요?](/docs/why-event-tracker): Event Tracker의 필요성과 주요 기능 소개 +- [`createTracker`](/docs/create-tracker): 트래커 인스턴스 생성 및 상세 설정 가이드 +- [Components](/docs/components): 사용 가능한 모든 트래킹 컴포넌트와 사용 예시 +- [`useTracker`](/docs/hook): 커스텀 훅을 활용한 사용자 지정 트래킹 기법 +- [Batching](/docs/batching): 이벤트 배칭을 통한 성능 최적화 전략 +- [Data Type Validation](/docs/data-type-validation): Zod 스키마를 활용한 데이터 유효성 검증 가이드 diff --git a/docs/src/content/ko/installation.mdx b/docs/src/content/ko/installation.mdx index 87fba1a..fa7b78a 100644 --- a/docs/src/content/ko/installation.mdx +++ b/docs/src/content/ko/installation.mdx @@ -1,19 +1,9 @@ # 설치 -npm을 사용하여 설치하기: +Event Tracker는 React 애플리케이션에서 이벤트 트래킹을 쉽게 구현할 수 있도록 설계된 라이브러리입니다. `React 18.0.0` 이상의 버전에서 사용할 수 있으며, TypeScript를 완벽하게 지원합니다. -```bash -npm install @offlegacy/event-tracker -``` +최신 안정 버전을 설치하려면 아래 명령어를 실행하세요. -yarn을 사용하여 설치하기: - -```bash -yarn add @offlegacy/event-tracker -``` - -pnpm을 사용하여 설치하기: - -```bash -pnpm add @offlegacy/event-tracker +```shell npm2yarn +npm install @offlegacy/event-tracker ``` diff --git a/docs/src/content/ko/hook.mdx b/docs/src/content/ko/useTracker.mdx similarity index 94% rename from docs/src/content/ko/hook.mdx rename to docs/src/content/ko/useTracker.mdx index c775e8f..81385cb 100644 --- a/docs/src/content/ko/hook.mdx +++ b/docs/src/content/ko/useTracker.mdx @@ -1,7 +1,8 @@ -# hook +# useTracker -[`createTracker`](/docs/create-tracker)에서 두 번째 배열 항목으로 반환되는 커스텀 React hook입니다. -이 훅은 컴포넌트 내에서 이벤트 트래킹 기능과 컨텍스트 관리에 접근할 수 있게 합니다. +[`createTracker`](/docs/create-tracker)에서 두 번째 배열 항목으로 반환되는 커스텀 React hook입니다. 이 훅은 컴포넌트 내에서 이벤트 트래킹 기능과 컨텍스트 관리에 접근할 수 있게 합니다. + +예를 들어, 특정 비동기 작업이 완료된 후 또는 사용자의 특정 입력 값에 따라 이벤트를 발생시켜야 할 때 유용합니다. ```tsx import { createTracker } from "@offlegacy/event-tracker"; diff --git a/docs/src/content/ko/why-event-tracker.mdx b/docs/src/content/ko/why-event-tracker.mdx new file mode 100644 index 0000000..83331c5 --- /dev/null +++ b/docs/src/content/ko/why-event-tracker.mdx @@ -0,0 +1,186 @@ +import { Steps } from "nextra/components"; + +# 왜 Event Tracker인가요? + +현대 웹 애플리케이션은 사용자의 행동을 분석하여 서비스 품질을 지속적으로 개선해야 합니다. 그러나 기존의 이벤트 트래킹 방식은 여러 문제점이 나타납니다. + +## Event Tracker가 필요한 이유 + +다음은 전통적인 이벤트 트래킹 방식의 문제점을 보여주는 예시입니다. + +- **Prop Drilling의 고통**: 이벤트 트래킹에 필요한 데이터를 하위 컴포넌트까지 전달하기 위해 수많은 계층을 거쳐 prop을 내려보내야 하는 경우가 많습니다. 이는 코드의 가독성을 해치고 유지보수를 어렵게 만듭니다. +- **로직의 강한 결합**: 비즈니스 로직과 이벤트 트래킹 로직이 한데 섞여 코드의 복잡도를 높이고, 각 로직의 독립적인 테스트와 수정을 어렵게 만듭니다. +- **보일러플레이트 코드 증가**: 반복적인 트래킹 코드 작성은 개발 생산성을 저해하는 요인이 됩니다. + +```tsx {8,15, 24-31} +function Page() { + const { user, userId } = useUser(); // 사용자 정보와 ID를 가져옵니다. + + return ( +
+

User: {user.name}

+ {/* Counter 컴포넌트에 이벤트 트래킹을 위해 userId를 전달합니다. */} + +
+ ); +} + +// 오직 이벤트 트래킹만을 위해서 'userId'를 prop으로 전달받습니다. +// 만약 Counter가 더 깊은 곳에 있다면, prop drilling은 더욱 심해집니다. +function Counter({ userId }: { userId: string }) { + const [count, setCount] = useState(0); + const { track } = useTrackEvent(); // 가상의 트래킹 훅 + + const handleIncrement = () => { + const newCount = count + 1; + setCount(newCount); + + // 비즈니스 로직 (카운트 증가)과 트래킹 로직이 혼재합니다. + track({ + event: "click_increment", + params: { + type: "count", + value: newCount, + userId, // 상위로부터 전달받은 userId 사용 + }, + }); + }; + + return ( +
+

Count: {count}

+ +
+ ); +} +``` + +## Event Tracker가 제시하는 새로운 패러다임 + +Event Tracker는 이벤트 트래킹을 위한 새로운 패러다임을 소개합니다. +Event Tracker가 제시하는 선언적 방식은 전통적으로 이벤트 트래킹과 관련된 복잡성을 단순화하여, 모든 개발자들이 쉽게 접근할 수 있도록 합니다. + + + +### 선언적 이벤트 트래킹 + +```tsx {7, 10, 12, 32, 34} +function Page() { + const { user, userId } = useUser(); + + // Track.Provider를 통해 하위 컴포넌트에 트래킹 컨텍스트(userId)를 제공합니다. + // 더 이상 prop drilling이 필요 없습니다. + return ( + +
+

User: {user.name}

+ {/* userId를 prop으로 전달할 필요가 없습니다. */} +
+
+ ); +} + +function Counter() { + const [count, setCount] = useState(0); + + const handleIncrement = () => { + // 이제 handleIncrement 함수는 순수하게 카운트 증가 로직만 담당합니다. + setCount(count + 1); + }; + + return ( +
+

Count: {count}

+ {/* + Track.Click 컴포넌트가 클릭 이벤트를 감싸고, + 클릭 발생 시 정의된 파라미터와 함께 이벤트를 트래킹합니다. + 컨텍스트로 제공된 userId는 자동으로 트래킹 데이터에 포함됩니다. + */} + + + +
+ ); +} +``` + +Event Tracker를 사용하면 선언적 이벤트 트래킹이 가능해져 코드 가독성이 향상되고 복잡성이 감소합니다. 이는 개발자들이 이벤트 트래킹을 더 쉽게 이해하고 사용할 수 있도록 돕습니다. + +이제 `handleIncrement` 함수는 카운트 증가에만 책임이 있고, 이벤트 트래킹은 `` 컴포넌트가 처리합니다. +**이러한 선언적 접근 방식은 개발자가 '어떻게 트래킹할지'가 아닌 '무엇을 트래킹할지'에 집중하도록 합니다.** +어떻게 트래킹할지는 React 앱 외부에서 정의되어야 합니다. + +### 이벤트 트래킹 응집도 개선 + +```tsx {4-10, 14-20} +const [Track, useTracker] = createTracker({ + // DOM 이벤트 발생 시 실행될 콜백 함수 + DOMEvents: { + onClick: (params, context) => { + // 실제 트래킹 라이브러리(Google Analytics, Amplitude 등) 호출 + logEvent("click_event", { + ...params, // { value: ..., type: "count" } + userId: context.userId, // Provider로부터 받은 userId + }); + }, + // 필요에 따라 onMouseOver, onFocus 등 다양한 DOM 이벤트 핸들러 정의 가능 + }, + // 화면 노출(Impression) 이벤트 발생 시 실행될 콜백 함수 + onImpression: (params, context) => { + logEvent("impression_event", { + ...params, + userId: context.userId, + pagePath: window.location.pathname, + }); + }, +}); +``` + +이제 '어떻게 추적할지'에 대한 코드가 비즈니스 로직과 분리되었습니다. 애플리케이션 외부에 위치하므로 비즈니스 로직을 변경하지 않고도 이벤트 트래킹 로직을 수정할 수 있습니다. + +### 데이터 타입 검증 + +```tsx {13-20, 24-33} +import { z } from "zod"; +import { createTracker } from "@offlegacy/Event Tracker"; + +interface Context { + /* ... */ +} + +interface Params { + /* ... */ +} + +// 스키마 정의 +const schemas = { + page_view: z.object({ + title: z.string(), + }), + click_button: z.object({ + target: z.string(), + }), +}; + +// 트래커 설정 +const [Track] = createTracker({ + schema: { + schemas: { + page_view, + click_button, + }, + onSchemaError: (error) => { + console.error("Schema validation error:", error); + }, + abortOnError: true, + }, +}); + +// 스키마 사용하기 +; +; +``` + +Event Tracker는 선택적으로 [Zod](https://zod.dev/) 라이브러리와 통합하여 스키마 기반의 강력한 데이터 타입 검증 기능을 제공합니다. 이를 통해 개발 단계에서부터 데이터 오류를 방지하고, 트래킹 데이터의 신뢰성을 높일 수 있습니다. + +
From e8b2262e9de17b6a5d743a5020e1939829920c47 Mon Sep 17 00:00:00 2001 From: gwansikk Date: Wed, 4 Jun 2025 04:27:09 +0900 Subject: [PATCH 02/24] docs: update --- docs/src/content/ko/_meta.ts | 2 +- docs/src/content/ko/components/dom-event.mdx | 69 +++++++++++++++---- docs/src/content/ko/components/impression.mdx | 2 +- docs/src/content/ko/components/index.mdx | 17 ++++- docs/src/content/ko/create-tracker.mdx | 39 ++++++++--- docs/src/content/ko/index.mdx | 2 + docs/src/content/ko/installation.mdx | 2 +- .../ko/{useTracker.mdx => use-tracker.mdx} | 0 8 files changed, 104 insertions(+), 29 deletions(-) rename docs/src/content/ko/{useTracker.mdx => use-tracker.mdx} (100%) diff --git a/docs/src/content/ko/_meta.ts b/docs/src/content/ko/_meta.ts index df5ee23..7705f64 100644 --- a/docs/src/content/ko/_meta.ts +++ b/docs/src/content/ko/_meta.ts @@ -45,7 +45,7 @@ export default { collapsed: true, }, }, - useTracker: { + "use-tracker": { title: "useTracker", theme: { toc: true, diff --git a/docs/src/content/ko/components/dom-event.mdx b/docs/src/content/ko/components/dom-event.mdx index fdc0149..1623333 100644 --- a/docs/src/content/ko/components/dom-event.mdx +++ b/docs/src/content/ko/components/dom-event.mdx @@ -1,6 +1,8 @@ +import { Callout } from "nextra/components"; + # DOMEvent -DOM 이벤트를 추적하는 데 사용됩니다. 자식 컴포넌트를 감싸고 지정된 이벤트 핸들러를 실행합니다. +DOM 이벤트를 추적하는 데 사용됩니다. 자식 컴포넌트를 감싸고 지정된 이벤트 핸들러를 실행합니다. `createTracker`의 리턴 배열의 첫 번쨰 요소인 이벤트 컴포넌트 중 하나입니다. ```tsx import { createTracker } from "@offlegacy/event-tracker"; @@ -24,21 +26,64 @@ function App() { } ``` +## Reference + ### Props -- `type: DOMEventNames` - 이벤트 이름 (예: onClick, onFocus) -- 스키마와 함께 사용하는 경우 - - `params: SchemaParams | (context: Context) => SchemaParams` - 스키마 기반 매개변수 - - `schema: string` - 이벤트 매개변수 검증을 위한 스키마 이름 -- 스키마와 함께 사용하지 않는 경우 - - `params: EventParams | (context: Context) => EventParams` - 이벤트 매개변수 -- `enabled?: boolean | ((context: Context, params: EventParams) => boolean)` - 이벤트 추적을 조건부로 활성화/비활성화 (기본값: `true`) -- `debounce?: DebounceConfig` - 연속적인 이벤트 발생을 방지하는 디바운스 설정 -- `throttle?: ThrottleConfig` - 이벤트 발생 빈도를 제한하는 스로틀 설정 +| Props | 타입 | 설명 | 필수 | +| ------------- | ---------------- | ----------------------------------------------------- | ---- | +| DOMEventNames | DOMEventNames | 이벤트 이름 (예: `"onClick"`, `"onFocus"`) | O | +| `enabled` | `boolean | ((context: Context, params: EventParams) => boolean)` | - | +| `debounce` | `DebounceConfig` | 연속적인 이벤트 발생을 방지하는 디바운스 설정 | - | +| `throttle` | `ThrottleConfig` | 이벤트 발생 빈도를 제한하는 스로틀 설정 | - | + +참고: `debounce`와 `throttle`은 상호 배타적이며 함께 사용할 수 없습니다. + +스키마와 함께 사용하는 경우와 아닐 경우 타입이 달라지는 속성이 있습니다. + +| Props | 스키마 여부 | 타입 | 설명 | 필수 | +| -------- | ----------- | ---------------------------------------------------- | --------------------------------------- | ---- | +| `params` | O | `SchemaParams \| (context: Context) => SchemaParams` | 스키마 기반 매개변수 | O | +| `schema` | O | `string` | 이벤트 매개변수 검증을 위한 스키마 이름 | O | +| `params` | - | `EventParams \| (context: Context) => EventParams` | 이벤트 매개변수 | O | + +--- + +#### `params` (필수) + +스키마와 함께 사용하는 경우: + +- 타입: `SchemaParams | (context: Context) => SchemaParams` +- 설명: 스키마 기반 매개변수 + +스키마와 함께 사용하지 않는 경우: + +- 타입: `EventParams | (context: Context) => EventParams` +- 설명: 이벤트 매개변수 + +#### `schema` (필수) + +스키마와 함께 사용하는 경우: + +- 타입: `string` +- 설명: 이벤트 매개변수 검증을 위한 스키마 이름 + +#### `enabled` + +- 타입: `boolean | ((context: Context, params: EventParams) => boolean)` +- 설명: 이벤트 추적을 조건부로 활성화/비활성화 (기본값: `true`) + +#### `debounce` + +- 타입: `DebounceConfig` +- 설명: 연속적인 이벤트 발생을 방지하는 디바운스 설정 + +#### `throttle` -**참고:** `debounce`와 `throttle`은 상호 배타적이며 함께 사용할 수 없습니다. +- 타입: `ThrottleConfig` +- 설명: 이벤트 발생 빈도를 제한하는 스로틀 설정 -### 추적 옵션 예제 +### Examples #### 조건부 추적 diff --git a/docs/src/content/ko/components/impression.mdx b/docs/src/content/ko/components/impression.mdx index d664b5c..7bfbb58 100644 --- a/docs/src/content/ko/components/impression.mdx +++ b/docs/src/content/ko/components/impression.mdx @@ -1,6 +1,6 @@ # Impression -Intersection Observer API를 사용하여 노출 이벤트를 추적합니다. +Intersection Observer API를 사용하여 노출 이벤트를 추적합니다. `createTracker`의 리턴 배열의 첫 번쨰 요소인 이벤트 컴포넌트 중 하나입니다. ```tsx import { createTracker } from "@offlegacy/event-tracker"; diff --git a/docs/src/content/ko/components/index.mdx b/docs/src/content/ko/components/index.mdx index 2189f78..a565b32 100644 --- a/docs/src/content/ko/components/index.mdx +++ b/docs/src/content/ko/components/index.mdx @@ -1,6 +1,6 @@ # Provider -`Provider` 컴포넌트는 애플리케이션에 초기 컨텍스트를 연결하고 제공합니다. +`Provider` 컴포넌트는 애플리케이션에 초기 컨텍스트를 연결하고 제공합니다. `createTracker`의 리턴 배열의 첫 번쨰 요소인 이벤트 컴포넌트 중 하나입니다. ```tsx import { createTracker } from '@offlegacy/event-tracker' @@ -10,12 +10,23 @@ const [Track] = createTracker({...}) function App() { return ( - {/* 애플리케이션 컨텐츠 */} + {/* 애플리케이션 */} ) } ``` +## Reference + ### Props -- `initialContext?: Context` - 초기 컨텍스트 값 +| Props | 타입 | 설명 | 예제 | +| ---------------- | --------- | ---------------- | ------------------- | +| `initialContext` | `Context` | 초기 컨텍스트 값 | `{ userId: '123' }` | + +--- + +#### `initialContext` (필수) + +- 타입: `Context` +- 설명: 초기 컨텍스트 값 diff --git a/docs/src/content/ko/create-tracker.mdx b/docs/src/content/ko/create-tracker.mdx index 8f0dbfe..dea5919 100644 --- a/docs/src/content/ko/create-tracker.mdx +++ b/docs/src/content/ko/create-tracker.mdx @@ -25,7 +25,7 @@ const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTrack `createTracker`는 하나의 설정 객체를 인자로 받습니다. -| 옵션 | 타입 | 설명 | +| 설정 | 타입 | 설명 | | ------------ | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | | `init` | `(initialContext: Context, setContext: SetContext) => void \| Promise` | 모든 이벤트 발생 전에 실행되는 초기화 함수. Promise 지원. | | `send` | `(params: EventParams \| (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` | 이벤트 전송 함수. Promise 지원. | @@ -64,9 +64,9 @@ const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTrack - 타입: `ImpressionOptions` - 속성: - - `threshold`: 노출 비율 기준 (기본값: 0.2) - - `freezeOnceVisible`: 노출 후 상태 고정 여부 (기본값: true) - - `initialIsIntersecting`: 초기 교차 여부 (기본값: false) + - `threshold`: 노출 비율 기준 (기본값: `0.2`) + - `freezeOnceVisible`: 노출 후 상태 고정 여부 (기본값: `true`) + - `initialIsIntersecting`: 초기 교차 여부 (기본값: `false`) #### `pageView` @@ -80,9 +80,9 @@ const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTrack - 타입: `BatchConfig` - 속성: - - `batch.enable`: 배치 활성화 여부 (기본값: false) - - `batch.interval`: 전송 간격(ms, 기본값: 3000) - - `batch.thresholdSize`: 최대 배치 크기 (기본값: 25) + - `batch.enable`: 배치 활성화 여부 (기본값: `false`) + - `batch.interval`: 전송 간격(ms, 기본값: `3000`) + - `batch.thresholdSize`: 최대 배치 크기 (기본값: `25`) - `batch.onFlush`: 이벤트 전송 처리 함수 - `batch.onError`: 오류 발생 시 처리 함수 (옵셔널) @@ -93,7 +93,7 @@ const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTrack - `schemas.schema`: [Zod](https://zod.dev/) 기반의 스키마 정의 - `schemas.onSchemaError`: 스키마 오류 발생 시 처리 함수 - - `schemas.abortOnError`: 오류 시 이벤트 중단 여부 (기본값: false) + - `schemas.abortOnError`: 오류 시 이벤트 중단 여부 (기본값: `false`) ### Returns @@ -103,8 +103,25 @@ const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTrack const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTracker] = createTracker({...}) ``` -1. 이벤트 컴포넌트 (`Tracker`) +#### [Components](./components) - - `Provider`, `DOMEvent`, `Click`, `Impression`, `PageView`, `SetContext` +리턴 배열에서 첫 요소인 이벤트 컴포넌트는 여러 가지 이벤트 컴포넌트를 포함하고 있습니다. -2. 커스텀 훅 (`useTracker`) +```tsx +const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }] = createTracker(config); +``` + +- `Provider` +- `DOMEvent` +- `Click` +- `Impression` +- `PageView` +- `SetContext` + +#### [useTracker](./use-tracker) + +```tsx +const [, useTracker] = createTracker(config); +``` + +리턴 배열에서 두번째 요소인 커스텀 React hook입니다. 이 훅은 컴포넌트 내에서 이벤트 트래킹 기능과 컨텍스트 관리에 접근할 수 있게 합니다. diff --git a/docs/src/content/ko/index.mdx b/docs/src/content/ko/index.mdx index aa96cf6..03c0269 100644 --- a/docs/src/content/ko/index.mdx +++ b/docs/src/content/ko/index.mdx @@ -55,6 +55,8 @@ Event Tracker를 효과적으로 사용하기 위해 알아야 할 몇 가지 라이브러리의 가장 기본적인 출발점입니다. `createTracker` 함수를 사용하여 트래커 인스턴스(`Track` 컴포넌트 컬렉션과 `useTracker` 훅)를 생성합니다. 이때, DOM 이벤트 핸들러, 노출(Impression) 이벤트 핸들러, 스키마 등을 설정하여 이벤트 트래킹을 정의합니다. +목적에 따라 구분하여 여러 가지 트래커 인스턴스를 생성할 수 있습니다. (예를 들어, Google Analytics로 보내는 이벤트와 Amplitude로 보내는 이벤트를 구분하여 생성할 수 있습니다.) + ### 프로바이더 (`Track.Provider`) React의 Context API를 기반으로 구현되었습니다. 애플리케이션 또는 특정 컴포넌트 트리의 최상단에서 `Track.Provider`로 감싸 하위 컴포넌트들에 트래킹에 필요한 공통 데이터(컨텍스트)를 제공합니다. 예를 들어, `userId`, `pageName` 등의 정보를 컨텍스트로 전달하면, 각 이벤트 트래킹 시 이 정보를 활용할 수 있습니다. diff --git a/docs/src/content/ko/installation.mdx b/docs/src/content/ko/installation.mdx index fa7b78a..9a3705c 100644 --- a/docs/src/content/ko/installation.mdx +++ b/docs/src/content/ko/installation.mdx @@ -1,6 +1,6 @@ # 설치 -Event Tracker는 React 애플리케이션에서 이벤트 트래킹을 쉽게 구현할 수 있도록 설계된 라이브러리입니다. `React 18.0.0` 이상의 버전에서 사용할 수 있으며, TypeScript를 완벽하게 지원합니다. +Event Tracker는 React 애플리케이션에서 이벤트 트래킹을 쉽게 구현할 수 있도록 설계된 라이브러리입니다. `React 18.0.0` 이상의 버전에서 사용할 수 있습니다. 최신 안정 버전을 설치하려면 아래 명령어를 실행하세요. diff --git a/docs/src/content/ko/useTracker.mdx b/docs/src/content/ko/use-tracker.mdx similarity index 100% rename from docs/src/content/ko/useTracker.mdx rename to docs/src/content/ko/use-tracker.mdx From b8da2691e1aa73b0c0411103386b498ea663ea56 Mon Sep 17 00:00:00 2001 From: gwansikk Date: Thu, 12 Jun 2025 20:20:57 +0900 Subject: [PATCH 03/24] docs: update en --- docs/src/content/en/_meta.ts | 42 +++- .../en/{quick-start.mdx => basic-example.mdx} | 6 +- docs/src/content/en/components.mdx | 189 --------------- docs/src/content/en/components/index.mdx | 17 +- docs/src/content/en/create-tracker.mdx | 136 ++++++----- docs/src/content/en/hook.mdx | 223 ----------------- docs/src/content/en/index.mdx | 229 +++++------------- docs/src/content/en/installation.mdx | 4 +- docs/src/content/en/use-tracker.mdx | 225 +++++++++++++++++ docs/src/content/en/why-event-tracker.mdx | 184 ++++++++++++++ 10 files changed, 591 insertions(+), 664 deletions(-) rename docs/src/content/en/{quick-start.mdx => basic-example.mdx} (83%) delete mode 100644 docs/src/content/en/components.mdx delete mode 100644 docs/src/content/en/hook.mdx create mode 100644 docs/src/content/en/use-tracker.mdx create mode 100644 docs/src/content/en/why-event-tracker.mdx diff --git a/docs/src/content/en/_meta.ts b/docs/src/content/en/_meta.ts index 29d0799..1c36a1e 100644 --- a/docs/src/content/en/_meta.ts +++ b/docs/src/content/en/_meta.ts @@ -1,6 +1,6 @@ import { MetaRecord } from "nextra"; -const meta: MetaRecord = { +export default { "getting-started-separator": { type: "separator", title: "Getting Started", @@ -12,21 +12,21 @@ const meta: MetaRecord = { layout: "default", }, }, - installation: { - title: "Installation", + "why-event-tracker": { + title: "Why Event Tracker?", theme: { toc: true, layout: "default", }, }, - "quick-start": { - title: "Quick Start", + installation: { + title: "Installation", theme: { toc: true, layout: "default", }, }, - "api-separator": { + "api-reference-separator": { title: "API Reference", type: "separator", }, @@ -38,21 +38,21 @@ const meta: MetaRecord = { }, }, components: { - title: "components", + title: "Components", theme: { toc: true, layout: "default", }, }, - hook: { - title: "hook", + "use-tracker": { + title: "useTracker", theme: { toc: true, layout: "default", }, }, "advanced-separator": { - title: "Advanced", + title: "Guides", type: "separator", }, batching: { @@ -62,6 +62,22 @@ const meta: MetaRecord = { layout: "default", }, }, -}; - -export default meta; + "data-type-validation": { + title: "Data Type Validation", + theme: { + toc: true, + layout: "default", + }, + }, + "example-separator": { + title: "Examples", + type: "separator", + }, + "basic-example": { + title: "Basic", + theme: { + toc: true, + layout: "default", + }, + }, +} satisfies MetaRecord; diff --git a/docs/src/content/en/quick-start.mdx b/docs/src/content/en/basic-example.mdx similarity index 83% rename from docs/src/content/en/quick-start.mdx rename to docs/src/content/en/basic-example.mdx index 0a5728b..6c06e3e 100644 --- a/docs/src/content/en/quick-start.mdx +++ b/docs/src/content/en/basic-example.mdx @@ -1,6 +1,6 @@ -# Quick Start +# Guide -Here's a simple example of how to use event-tracker: +Here's a simple example demonstrating how to use `event-tracker`: ```tsx import { createTracker } from "@offlegacy/event-tracker"; @@ -14,7 +14,7 @@ const [Track, useTracker] = createTracker({ }, }); -// Use in your app +// Usage within the app function App() { return ( diff --git a/docs/src/content/en/components.mdx b/docs/src/content/en/components.mdx deleted file mode 100644 index 64113ec..0000000 --- a/docs/src/content/en/components.mdx +++ /dev/null @@ -1,189 +0,0 @@ -# Components - -event-tracker provides several components for tracking different types of events. Each component is designed to be easy to use while maintaining type safety and performance. - -## Provider - -The `Provider` component connects and provides initial context to your application. - -```tsx -import { createTracker } from '@offlegacy/event-tracker' - -const [Track] = createTracker({...}) - -function App() { - return ( - - {/* Your app content */} - - ) -} -``` - -### Props - -- `initialContext: unknown` - Initial context value - -## DOMEvent - -Used for tracking DOM events. Wraps a child component and fires the specified event handler. - -```tsx -import { createTracker } from "@offlegacy/event-tracker"; - -const [Track] = createTracker({ - DOMEvents: { - onFocus: (params, context) => { - // Handle focus event - }, - }, -}); - -function App() { - return ( - - - - - - ); -} -``` - -### Props - -- `type: DOMEventNames` - Event name (e.g., onClick, onFocus) -- (With schema) - - `params: SchemaParams | (context: Context) => SchemaParams` - Event parameters - - `schema?: string` - A name of schema that will be used to validate the event parameters -- (Without schema) - - `params: EventParams | (context: Context) => EventParams` - Event parameters - -## Click - -A specialized version of `DOMEvent` for click events (`type="onClick"`). - -```tsx -import { createTracker } from "@offlegacy/event-tracker"; - -const [Track] = createTracker({ - DOMEvents: { - onClick: (params, context) => { - // Handle click event - }, - }, -}); - -function App() { - return ( - - - - - - ); -} -``` - -### Props - -- With schema - - `params: SchemaParams | (context: Context) => SchemaParams` - Click event parameters - - `schema?: string` - A name of schema that will be used to validate the event parameters -- Without schema - - `params: EventParams | (context: Context) => EventParams` - Click event parameters - -## Impression - -Tracks impression events using the Intersection Observer API. - -```tsx -import { createTracker } from "@offlegacy/event-tracker"; - -const [Track] = createTracker({ - impression: { - onImpression: (params, context) => { - // Handle impression event - }, - options: { - threshold: 0.5, - }, - }, -}); - -function App() { - return ( - - -
Tracked content
-
-
- ); -} -``` - -### Props - -- Without schema - - `params: EventParams | (context: Context) => EventParams` - Impression event parameters - - `options?: ImpressionOptions` - Optional configuration (overrides global options) -- With schema - - `params: SchemaParams | (context: Context) => SchemaParams` - Impression event parameters - - `schema?: string` - A name of schema that will be used to validate the event parameters - - `options?: ImpressionOptions` - Optional configuration (overrides global options) - -## PageView - -Tracks page view events on component mount. - -```tsx -import { createTracker } from "@offlegacy/event-tracker"; - -const [Track] = createTracker({ - pageView: { - onPageView: (params, context) => { - // Handle page view event - }, - }, -}); - -function App() { - return ( - - - - ); -} -``` - -### Props - -- With schema - - `params: SchemaParams | (context: Context) => SchemaParams` - Page view event parameters - - `schema?: string` - A name of schema that will be used to validate the event parameters -- Without schema - - `params: EventParams | (context: Context) => EventParams` - Page view event parameters - -## SetContext - -Sets or updates the tracking context. - -```tsx -import { createTracker } from '@offlegacy/event-tracker' - -const [Track] = createTracker({...}) - -function App() { - return ( - - - - ) -} -``` - -### Props - -- `context: unknown | ((prevContext: unknown) => unknown)` - New context value or update function diff --git a/docs/src/content/en/components/index.mdx b/docs/src/content/en/components/index.mdx index 814a375..13da1ae 100644 --- a/docs/src/content/en/components/index.mdx +++ b/docs/src/content/en/components/index.mdx @@ -1,6 +1,6 @@ # Provider -The `Provider` component connects and provides initial context to your application. +The `Provider` component attaches and provides the initial context to your application. It is one of the event components included as the first element of the tuple returned by `createTracker`. ```tsx import { createTracker } from '@offlegacy/event-tracker' @@ -10,12 +10,23 @@ const [Track] = createTracker({...}) function App() { return ( - {/* Your app content */} + {/* Application */} ) } ``` +## Reference + ### Props -- `initialContext?: Context` - Initial context value +| Props | Type | Description | Example | +| ---------------- | --------- | --------------------- | ------------------- | +| `initialContext` | `Context` | Initial context value | `{ userId: '123' }` | + +--- + +#### `initialContext` (required) + +- Type: `Context` +- Description: The initial context value provided to the application. diff --git a/docs/src/content/en/create-tracker.mdx b/docs/src/content/en/create-tracker.mdx index 229279b..6d143a0 100644 --- a/docs/src/content/en/create-tracker.mdx +++ b/docs/src/content/en/create-tracker.mdx @@ -1,6 +1,6 @@ -# createTracker(config) +# createTracker -The main function to create a tracker instance with your desired configuration. +`createTracker` is a function used to create tracker instances with custom configurations. It allows you to define event tracking behaviors through parameters, returning a tuple containing the tracker instance and a custom hook for usage. ```tsx const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTracker] = createTracker({ @@ -19,95 +19,109 @@ const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTrack }); ``` -### Configuration Options +## Reference -#### init +### Parameters + +`createTracker` accepts a single configuration object as its parameter. + +| Option | Type | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | +| `init` | `(initialContext: Context, setContext: SetContext) => void \| Promise` | Initialization function executed before any event occurs. Supports async operations. | +| `send` | `(params: EventParams \| (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` | Function for sending events externally. Supports async operations. | +| `DOMEvents` | [`DOMEvents`](https://developer.mozilla.org/docs/Web/API/Event) | Collection of React DOM event handlers (`onClick`, `onMouseEnter`, etc.). | +| `impression` | `ImpressionOptions` | Configuration for impression event tracking. | +| `pageView` | `PageViewOptions` | Configuration for page view tracking. | +| `batch` | `BatchConfig` | Configuration for event batching. | +| `schemas` | `SchemaConfig` | Configuration for event schema validation. | + +--- + +#### `init` - Type: `(initialContext: Context, setContext: SetContext) => void | Promise` -- Optional -- Function executed before any events happen -- If it returns a promise, events will be delayed until the promise resolves +- Description: Function to set the initial context before any event occurs. Supports asynchronous functions. -#### send +#### `send` - Type: `(params: EventParams | (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` -- Optional -- Standard function to send events -- If it returns a promise, events will be delayed until the promise resolves +- Description: Function for sending events externally. Supports asynchronous operations. -#### DOMEvents +#### `DOMEvents` - Type: `DOMEvents` -- Optional -- Collection of standard React DOM events (`onClick`, `onMouseEnter`, etc.) -- Each handler is executed when that event occurs on a `` -- If a handler returns a promise, subsequent event callbacks will be delayed until it resolves - -#### impression +- Description: Standard React DOM event handlers. Used by `` components. -Configuration for impression tracking: +#### `impression` -- onImpression +- `impression.onImpression` - Type: `(params: EventParams | (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` - - Optional - - Executed when impression occurs on a `` child - - Promise return value will delay subsequent callbacks + - Description: Callback executed when `` event occurs. -- options - - Type: `ImpressionOptions` - - Optional - - Configuration options for impression tracking: - - `threshold`: Percentage of visibility needed (default: 0.2) - - `freezeOnceVisible`: Freeze intersection state after visibility (default: true) - - `initialIsIntersecting`: Initial intersection state (default: false) +- `impression.options` -#### pageView + - Type: `ImpressionOptions` + - Properties: -Configuration for page view tracking: + - `threshold`: Visibility threshold ratio (default: `0.2`) + - `freezeOnceVisible`: Fix visibility state after impression (default: `true`) + - `initialIsIntersecting`: Initial intersection state (default: `false`) -##### onPageView +#### `pageView` -- Type: `(params: EventParams | (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` -- Optional -- Executed when `` is mounted -- Promise return value will delay subsequent events +- `pageView.onPageView` -#### batch + - Type: `(params: EventParams | (context: Context) => EventParams, context: Context, setContext: SetContext) => TaskReturnType` + - Description: Executed when `` component mounts. -Configuration for event batching: +#### `batch` - Type: `BatchConfig` -- Optional - Properties: - - `enable`: Enable batching (default: false) - - `interval`: Flush interval in ms (default: 3000) - - `thresholdSize`: Max batch size (default: 25) - - `onFlush`: Function to handle batch flush (required if enabled) - - `onError`: Error handler (optional) -#### schemas + - `batch.enable`: Enable batching (default: `false`) + - `batch.interval`: Sending interval in milliseconds (default: `3000`) + - `batch.thresholdSize`: Maximum batch size (default: `25`) + - `batch.onFlush`: Function to handle batched event sending + - `batch.onError`: Optional error handling function -Configuration for data type validation: +#### `schemas` - Type: `SchemaConfig` -- Optional - Properties: - - `schemas`: A record of [Zod](https://zod.dev/) schemas - - `onSchemaError`?: Function to handle schema validation errors - - `abortOnError`?: Whether to abort event tracking if a schema validation error occurs (default: false) -### Return Value + - `schemas.schema`: Schema definitions based on [Zod](https://zod.dev/) + - `schemas.onSchemaError`: Function executed when schema validation errors occur + - `schemas.abortOnError`: Abort events on validation error (default: `false`) -The `createTracker` function returns a tuple containing: +### Returns -1. An object with tracking components: +`createTracker` returns a tuple containing the following two elements: - - [`Provider`](/docs/components) - - [`DOMEvent`](/docs/components/dom-event) - - [`Click`](/docs/components/click) - - [`Impression`](/docs/components/impression) - - [`PageView`](/docs/components/page-view) - - [`SetContext`](/docs/components/set-context) +```tsx +const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }, useTracker] = createTracker({...}) +``` + +#### [Components](./components) + +The first element of the returned tuple contains various event components: + +```tsx +const [{ Provider, DOMEvent, Click, Impression, PageView, SetContext }] = createTracker(config); +``` + +- `Provider` +- `DOMEvent` +- `Click` +- `Impression` +- `PageView` +- `SetContext` + +#### [useTracker](./use-tracker) + +```tsx +const [, useTracker] = createTracker(config); +``` -2. The [custom hook](/docs/hook) +The second element of the returned tuple is a custom React hook. It provides access to event tracking functionalities and context management within components. diff --git a/docs/src/content/en/hook.mdx b/docs/src/content/en/hook.mdx deleted file mode 100644 index 0c8c4e2..0000000 --- a/docs/src/content/en/hook.mdx +++ /dev/null @@ -1,223 +0,0 @@ -# hook - -A custom React hook returned as the second array item from [`createTracker`](/docs/create-tracker). -It provides access to tracking functionality and context management within your components. - -```tsx -import { createTracker } from "@offlegacy/event-tracker"; - -const [Track, useTracker] = createTracker({...}) - -function MyComponent() { - const { setContext, getContext, track, trackWithSchema } = useTracker(); - - return ( - // Your component content - ); -} -``` - -### Return Value - -The hook returns an object with the following properties: - -#### setContext - -- Type: `(context: Context) => void` -- Sets or updates the current tracking context -- Can be used to update user information, session data, etc. - -```tsx -const { setContext } = useTracker(); - -// Set new context -setContext({ userId: "user-123" }); - -// Update context based on previous value -setContext((prev) => ({ - ...prev, - lastActive: new Date(), -})); -``` - -#### getContext - -- Type: `() => Context` -- Returns the current tracking context -- Useful for accessing current tracking state - -```tsx -const { getContext } = useTracker(); - -const currentContext = getContext(); -console.log("Current user:", currentContext.userId); -``` - -#### track - -- Type: `Record void>` -- Object containing all configured event tracking functions -- Keys match the event names defined in your tracker configuration -- Accepts optional `TrackingOptions` for advanced control - -```tsx -const { track } = useTracker(); - -// Track a simple click event -track.onClick({ buttonId: "submit" }); - -// Track with conditional logic -track.onClick( - { buttonId: "premium" }, - { - enabled: (context) => context.user?.isPremium, - }, -); - -// Track with debouncing -track.onClick( - { buttonId: "search" }, - { - debounce: { delay: 300, leading: false, trailing: true }, - }, -); - -// Track with throttling -track.onClick( - { buttonId: "rapid-action" }, - { - throttle: { delay: 1000, leading: true, trailing: false }, - }, -); - -// Track an impression -track.onImpression({ elementId: "hero" }); -``` - -#### trackWithSchema - -- Type: `Record void>` -- Object containing all configured event tracking functions with schema validation -- Keys match the event names defined in your tracker configuration -- Accepts optional `TrackingOptions` for advanced control - -```tsx -const { trackWithSchema } = useTracker(); - -// Track a click event with schema -trackWithSchema.onClick({ schema: "click", params: { buttonId: "submit" } }); - -// Track with conditional logic and schema -trackWithSchema.onClick( - { - schema: "premium_click", - params: { buttonId: "premium", userId: "123" }, - }, - { - enabled: (context, params) => context.user?.id === params.userId, - }, -); - -// Track with throttling and schema -trackWithSchema.onImpression( - { - schema: "impression", - params: { elementId: "hero", userId: "123" }, - }, - { - throttle: { delay: 2000, leading: true, trailing: false }, - }, -); -``` - -### TrackingOptions - -Both `track` and `trackWithSchema` methods accept an optional second parameter with the following options: - -- `enabled?: boolean | ((context: Context, params: EventParams) => boolean)` - Conditionally enable/disable event tracking -- `debounce?: DebounceConfig` - Debounce configuration to prevent rapid successive events -- `throttle?: ThrottleConfig` - Throttle configuration to limit event frequency - -**Note:** `debounce` and `throttle` are mutually exclusive and cannot be used together. - -#### DebounceConfig - -```tsx -interface DebounceConfig { - delay: number; // Delay in milliseconds - leading?: boolean; // Execute on leading edge (default: false) - trailing?: boolean; // Execute on trailing edge (default: true) -} -``` - -#### ThrottleConfig - -```tsx -interface ThrottleConfig { - delay: number; // Delay in milliseconds - leading?: boolean; // Execute on leading edge (default: true) - trailing?: boolean; // Execute on trailing edge (default: false) -} -``` - -### Example Usage - -Here's a complete example showing how to use the custom hook: - -```tsx -import { createTracker } from "@offlegacy/event-tracker"; - -const [Track, useTracker] = createTracker({ - onClick: (params) => { - // Send event to analytics service - analytics.track(params); - }, - pageView: { - onPageView: (params) => { - // Send event to analytics service - analytics.pageView(params); - }, - }, -}); - -function UserProfile({ userId }) { - const { setContext, track, trackWithSchema } = useTracker(); - - useEffect(() => { - // Update context when user ID changes - setContext({ userId }); - - // Track page view - track.onPageView({ page: "profile" }); - }, [userId]); - - const handleSettingsClick = () => { - // Track custom event - trackWithSchema.onClick({ schema: "settings", params: { userId } }); - }; - - return ( -
-

User Profile

- -
- ); -} -``` - -### Best Practices - -1. **Context Updates** - - - Use `setContext` for global state that affects multiple events - - Consider using the function form of `setContext` for updates based on previous state - -2. **Event Tracking** - - - Use the specific event functions from `track` or `trackWithSchema` when possible - -3. **Performance** - - Avoid calling tracking functions in render - - Use callbacks or effects for tracking - - Consider using [batching](/docs/batching) for better performance - - Consider using [data type validation](/docs/data-type-validation) for data type safety diff --git a/docs/src/content/en/index.mdx b/docs/src/content/en/index.mdx index 2a87bb1..c2585a0 100644 --- a/docs/src/content/en/index.mdx +++ b/docs/src/content/en/index.mdx @@ -1,202 +1,89 @@ -# Introduction - -## Motivation - -Event tracking is a complex task that requires a lot of boilerplate code and complexity. -Take a look at this example. - -```tsx {14,15, 23-30} -// Traditional event tracking - -const Page = () => { - const { user, userId } = useUser(); - - return ( -
-

User: {user.name}

- -
- ); -}; - -const Counter = ({ userId }: { userId: string }) => { - // Receives 'userId' as a prop just for event tracking purposes. - - const [count, setCount] = useState(0); - const { track } = useTrackEvent(); - - const handleIncrement = () => { - setCount(count + 1); - - track({ - event: "click", - params: { - type: "count", - value: count + 1, - userId, - }, - }); - }; +import { Steps } from "nextra/components"; - return ( -
-

Count: {count}

- -
- ); -}; -``` - -The two main inconveniences caused by event tracking in the code are: +# Introduction -1. **Prop Drilling**: The `userId` is passed as a prop to the `` component from the `` component. If the `` component is nested deeper within the component tree, prop drilling can become more severe, reducing the readability and maintainability of the code. -2. **Coupling of Event Tracking Logic and Business Logic**: The `handleIncrement` function contains both the logic for incrementing the count and tracking the event. Separating the event tracking logic can make the code cleaner and easier to maintain. +Welcome to the Event Tracker documentation. -## The New Paradigm +## What is Event Tracker? -`event-tracker` introduces a new paradigm for event tracking. -The declarative nature of this paradigm simplifies -the complexity traditionally associated with event tracking, making it accessible to developers of all skill levels. +Event Tracker is a declarative React library designed to simplify the implementation of complex event tracking, allowing developers to focus more on their business logic. It is designed to efficiently manage event tracking in applications of any scale. -### Declarative event tracking +```tsx +import { createTracker } from "@offlegacy/event-tracker"; -```tsx {5, 10, 24, 26} -const Page = () => { - const { user, userId } = useUser(); +// Creating a tracker instance +const [Track, useTracker] = createTracker({ + DOMEvents: { + onClick: (params, context) => { + log("Click event:", params, context); + }, + }, +}); +// Usage within an app +function App() { return ( - -
-

User: {user.name}

- -
+ + + + ); -}; +} +``` -const Counter = () => { - const [count, setCount] = useState(0); +### Key Features - const handleIncrement = () => { - setCount(count + 1); - }; +Event Tracker provides various features prioritizing both developer experience and application performance. - return ( -
-

Count: {count}

- - - -
- ); -}; -``` +| Feature | Description | +| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Declarative API with Type Safety | Fully supports [TypeScript](https://www.typescriptlang.org/), reducing errors during development and increasing productivity through auto-completion. | +| Powerful Data Type Validation | Ensures data reliability through schema-based validation using [Zod](https://zod.dev/). | +| Optimized Performance | Minimizes network requests through batching, debouncing, or throttling, reducing the impact on application performance. | +| Guaranteed Execution Order | Ensures asynchronous events are processed in the intended sequence. | +| Analytics Tool Independence | Flexible integration with any analytics tool (e.g., [Google Analytics](https://analytics.google.com/), [Amplitude](https://amplitude.com/)), without being tied to any specific provider. | +| Clear Separation of Concerns | Effectively separates tracking logic from business logic, maximizing code maintainability and scalability. | -Using `event-tracker` allows for declarative event tracking, which improves code readability and reduces complexity. This helps developers understand and use event tracking more easily. -Your `handleIncrement` function is now only responsible for incrementing the count, and the event tracking is handled by the `` component. -**This declarative approach makes the developer focus on _'what to track'_ and not _'how to track'._** -How to track should be defined outside of the React app. +## Core Concepts -### Improve code cohesion outside your application. +There are several key concepts you need to understand to use Event Tracker effectively. -```tsx -const [Track] = createTracker({ - DOMEvents: { - onClick: (params, context) => { - log({ - event: "click", - params: { - ...params, - userId: context.userId, - }, - }); - }, - }, - onImpression: (params, context) => { - log({ - event: "impression", - params: { - ...params, - userId: context.userId, - }, - }); - }, -}); -``` + -Your instructions for **'how to track'** is now separated from your business logic. -It's located outside your application, so you can change your event tracking provider without having to change your application code. +### Instance (`createTracker`) -### Data Type Validation +The foundational starting point of the library. Use the `createTracker` function to generate a tracker instance (a collection of `Track` components and the `useTracker` hook). Define event tracking by configuring DOM event handlers, impression event handlers, schemas, and more. -`event-tracker` provides built-in schema validation using [Zod](https://zod.dev/), a TypeScript-first schema validation library. +Multiple tracker instances can be created according to specific purposes (e.g., separate instances for sending events to Google Analytics and Amplitude). -```tsx -import { z } from "zod"; -import { createTracker } from "@offlegacy/event-tracker"; +### Provider (`Track.Provider`) -interface Context { - // ... -} +Implemented using React's Context API. Wrap your application or component tree at the top with `Track.Provider` to provide common tracking data (context) to child components. For example, providing information such as `userId` and `pageName` through context allows utilizing this information during each event tracking. -interface Params { - // ... -} +### Event Components (`Track.Click`, `Track.PageView`, etc.) -// Define Schemas -const schemas = { - page_view: z.object({ - title: z.string(), - }), - click_button: z.object({ - target: z.string(), - }), -}; - -// Configure Tracker -const [Track] = createTracker({ - // other configurations... - - schema: { - schemas: { - page_view, - click_button, - }, - onSchemaError: (error) => { - console.error("Schema validation error:", error); - }, - abortOnError: true, - }, -}); +Special components provided to track events declaratively. These are available from the first element of the array returned by `createTracker`. -// Use the schemas -; -; -``` +- `Track.Click`: Tracks click events occurring on child elements. +- `Track.Impression`: Tracks when child elements become visible on the screen. +- `Track.PageView`: Tracks page view events upon component mount. -## Key Features +In addition, you can customize or utilize various provided components that respond to different user interactions and lifecycle events. Each component can receive and utilize specific event-related data through the `context` and `params` props. -- 🎯 **Type-safe APIs**: Declarative event tracking with complete type safety -- 🛡️ **Data Type Validation**: Ensures data type safety and consistency using schemas -- ⚡️ **Optimized Performance**: Enhanced performance through event batching -- 🔄 **Guaranteed Order**: Guaranteed execution order for async operations -- 🔌 **Analytics Agnostic**: Works with any analytics provider of your choice -- 🧩 **Clean Separation**: Keeps tracking logic separate from business logic -- 📦 **Lightweight**: Minimal bundle size impact on your application +### Custom Hook (`useTracker`) -## Basic Concepts +Used for more complex or conditional event tracking unrelated directly to component lifecycles or DOM events. The `useTracker` hook lets you access context information from `Track.Provider` and execute defined tracking logic imperatively. -`event-tracker` is built around a few core concepts: + -1. **Tracker Creation**: Use `createTracker` to create a tracker instance with your desired event tracking instructions. -2. **Provider**: Wrap your app with `Track.Provider` to provide context. -3. **Event Components**: Use components like `Track.Click` or `Track.Impression` to track events. -4. **Hooks**: Use the `useTracker` hook to access tracking functionality in your components imperatively. +## Next Steps -Check out the other sections for detailed documentation on each feature: +These core concepts work synergistically to provide Event Tracker's powerful and flexible event-tracking environment. For detailed usage and in-depth information about each feature, refer to the documents below: -- [createTracker](/docs/create-tracker) - Detailed API documentation on `createTracker` -- [components](/docs/components) - Available tracking components -- [hook](/docs/hook) - Track using the hook -- [Batching](/docs/batching) - Optimizing performance with event batching -- [Data Type Validation](/docs/data-type-validation) - Ensuring data type safety and consistency using schemas +- [Why Event Tracker?](/docs/why-event-tracker): Introduction to the necessity and core features of Event Tracker. +- [`createTracker`](/docs/create-tracker): Guide to creating tracker instances and detailed configurations. +- [Components](/docs/components): Examples and details of all available tracking components. +- [`useTracker`](/docs/hook): Custom tracking techniques using the custom hook. +- [Batching](/docs/batching): Strategies for optimizing performance through event batching. +- [Data Type Validation](/docs/data-type-validation): Guide to data validation using Zod schemas. diff --git a/docs/src/content/en/installation.mdx b/docs/src/content/en/installation.mdx index 4d9af8e..2cba716 100644 --- a/docs/src/content/en/installation.mdx +++ b/docs/src/content/en/installation.mdx @@ -1,6 +1,8 @@ # Installation -Using npm: +Event Tracker is a library designed to simplify event tracking in React applications. It supports versions `React 18.0.0` and higher. + +To install the latest stable version, run the following command: ```shell npm2yarn npm install @offlegacy/event-tracker diff --git a/docs/src/content/en/use-tracker.mdx b/docs/src/content/en/use-tracker.mdx new file mode 100644 index 0000000..81385cb --- /dev/null +++ b/docs/src/content/en/use-tracker.mdx @@ -0,0 +1,225 @@ +# useTracker + +[`createTracker`](/docs/create-tracker)에서 두 번째 배열 항목으로 반환되는 커스텀 React hook입니다. 이 훅은 컴포넌트 내에서 이벤트 트래킹 기능과 컨텍스트 관리에 접근할 수 있게 합니다. + +예를 들어, 특정 비동기 작업이 완료된 후 또는 사용자의 특정 입력 값에 따라 이벤트를 발생시켜야 할 때 유용합니다. + +```tsx +import { createTracker } from "@offlegacy/event-tracker"; + +const [Track, useTracker] = createTracker({...}) + +function MyComponent() { + const { setContext, getContext, track, trackWithSchema } = useTracker(); + + return ( + // 컴포넌트 내용 + ); +} +``` + +### 반환 값 + +이 hook은 다음 속성을 포함하는 객체를 반환합니다: + +#### setContext + +- Type: `(context: Context) => void` +- 현재 트래킹 컨텍스트를 설정하거나 업데이트합니다. +- 사용자 정보, 세션 데이터 등을 업데이트하는 데 사용할 수 있습니다. + +```tsx +const { setContext } = useTracker(); + +// 새로운 컨텍스트 설정 +setContext({ userId: "user-123" }); + +// 이전 값에 기반한 컨텍스트 업데이트 +setContext((prev) => ({ + ...prev, + lastActive: new Date(), +})); +``` + +#### getContext + +- Type: `() => Context` +- 현재 트래킹 컨텍스트를 반환합니다. +- 현재 트래킹 상태에 접근하는 데 유용합니다. + +```tsx +const { getContext } = useTracker(); + +const currentContext = getContext(); +console.log("Current user:", currentContext.userId); +``` + +#### track + +- Type: `Record void>` +- 모든 구성된 이벤트 트래킹 함수를 포함하는 객체 +- key는 트래커 구성에 정의된 이벤트 이름과 일치합니다. +- 고급 제어를 위한 선택적 `TrackingOptions`를 허용합니다. + +```tsx +const { track } = useTracker(); + +// 간단한 클릭 이벤트 추적 +track.onClick({ buttonId: "submit" }); + +// 조건부 로직과 함께 추적 +track.onClick( + { buttonId: "premium" }, + { + enabled: (context) => context.user?.isPremium, + }, +); + +// 디바운싱과 함께 추적 +track.onClick( + { buttonId: "search" }, + { + debounce: { delay: 300, leading: false, trailing: true }, + }, +); + +// 스로틀링과 함께 추적 +track.onClick( + { buttonId: "rapid-action" }, + { + throttle: { delay: 1000, leading: true, trailing: false }, + }, +); + +// 노출 이벤트 추적 +track.onImpression({ elementId: "hero" }); +``` + +#### trackWithSchema + +- Type: `Record void>` +- 스키마 검증이 포함된 모든 구성된 이벤트 트래킹 함수를 포함하는 객체 +- key는 트래커 구성에 정의된 이벤트 이름과 일치합니다. +- 고급 제어를 위한 선택적 `TrackingOptions`를 허용합니다. + +```tsx +const { trackWithSchema } = useTracker(); + +// 스키마와 함께 클릭 이벤트 추적 +trackWithSchema.onClick({ schema: "click", params: { buttonId: "submit" } }); + +// 조건부 로직과 스키마와 함께 추적 +trackWithSchema.onClick( + { + schema: "premium_click", + params: { buttonId: "premium", userId: "123" }, + }, + { + enabled: (context, params) => context.user?.id === params.userId, + }, +); + +// 스로틀링과 스키마와 함께 추적 +trackWithSchema.onImpression( + { + schema: "impression", + params: { elementId: "hero", userId: "123" }, + }, + { + throttle: { delay: 2000, leading: true, trailing: false }, + }, +); +``` + +### TrackingOptions + +`track`과 `trackWithSchema` 메서드 모두 다음 옵션을 포함하는 선택적 두 번째 매개변수를 허용합니다: + +- `enabled?: boolean | ((context: Context, params: EventParams) => boolean)` - 이벤트 추적을 조건부로 활성화/비활성화 +- `debounce?: DebounceConfig` - 연속적인 이벤트 발생을 방지하는 디바운스 설정 +- `throttle?: ThrottleConfig` - 이벤트 발생 빈도를 제한하는 스로틀 설정 + +**참고:** `debounce`와 `throttle`은 상호 배타적이며 함께 사용할 수 없습니다. + +#### DebounceConfig + +```tsx +interface DebounceConfig { + delay: number; // 밀리초 단위의 지연 시간 + leading?: boolean; // 선행 에지에서 실행 (기본값: false) + trailing?: boolean; // 후행 에지에서 실행 (기본값: true) +} +``` + +#### ThrottleConfig + +```tsx +interface ThrottleConfig { + delay: number; // 밀리초 단위의 지연 시간 + leading?: boolean; // 선행 에지에서 실행 (기본값: true) + trailing?: boolean; // 후행 에지에서 실행 (기본값: false) +} +``` + +### 사용 예제 + +다음은 이 hook을 사용하는 예제입니다: + +```tsx +import { createTracker } from "@offlegacy/event-tracker"; + +const [Track, useTracker] = createTracker({ + onClick: (params) => { + // 이벤트를 애널리틱스 서비스로 전송 + analytics.track(params); + }, + pageView: { + onPageView: (params) => { + // Send event to analytics service + analytics.pageView(params); + }, + }, +}); + +function UserProfile({ userId }) { + const { setContext, track, trackWithSchema } = useTracker(); + + useEffect(() => { + // 사용자 ID가 변경될 때 컨텍스트 업데이트 + setContext({ userId }); + + // 페이지 뷰 이벤트 트래킹 + track.onPageView({ page: "profile" }); + }, [userId]); + + const handleSettingsClick = () => { + // 사용자 설정 이벤트 트래킹 + trackWithSchema.onClick({ schema: "settings", params: { userId } }); + }; + + return ( +
+

User Profile

+ +
+ ); +} +``` + +### Best Practices + +1. **컨텍스트 업데이트** + + - 여러 이벤트에 영향을 주는 전역 상태를 업데이트하기 위해 `setContext`를 사용하세요. + - 이전 상태에 기반한 업데이트를 위해 `setContext`의 함수 형태를 고려하세요. + +2. **이벤트 트래킹** + + - 가능한 경우 `track` 또는 `trackWithSchema`에서 이벤트 함수를 사용하세요. + +3. **성능** + + - 렌더링 중에 이벤트 트래킹 함수를 호출하지 마세요. + - 콜백 또는 효과를 사용하세요. + - [배칭](/docs/batching)을 사용하여 성능을 향상시키세요. + - [데이터 타입 검증](/docs/data-type-validation)을 사용하여 데이터 타입 안전성을 확인하세요. diff --git a/docs/src/content/en/why-event-tracker.mdx b/docs/src/content/en/why-event-tracker.mdx new file mode 100644 index 0000000..96f9050 --- /dev/null +++ b/docs/src/content/en/why-event-tracker.mdx @@ -0,0 +1,184 @@ +import { Steps } from "nextra/components"; + +# Why Event Tracker? + +Modern web applications need to continuously analyze user behavior to improve service quality. However, traditional event tracking approaches have various issues. + +## Why is Event Tracker Necessary? + +The following example illustrates common problems with traditional event tracking methods: + +- **Pain of Prop Drilling**: Often, tracking data must be passed through multiple layers of components, negatively impacting readability and maintainability. +- **Strong Coupling of Logic**: Mixing business logic and tracking logic increases complexity and makes it harder to independently test and modify each. +- **Increased Boilerplate Code**: Repetitive tracking code significantly hampers developer productivity. + +```tsx {8,15,24-31} +function Page() { + const { user, userId } = useUser(); // Retrieves user information and ID. + + return ( +
+

User: {user.name}

+ {/* Passes userId to Counter component solely for event tracking purposes */} + +
+ ); +} + +// Receives 'userId' as prop exclusively for event tracking. +// Prop drilling intensifies if Counter is placed even deeper in the tree. +function Counter({ userId }: { userId: string }) { + const [count, setCount] = useState(0); + const { track } = useTrackEvent(); // Hypothetical tracking hook + + const handleIncrement = () => { + const newCount = count + 1; + setCount(newCount); + + // Mixing business logic (incrementing count) with tracking logic. + track({ + event: "click_increment", + params: { + type: "count", + value: newCount, + userId, // userId received from the parent component + }, + }); + }; + + return ( +
+

Count: {count}

+ +
+ ); +} +``` + +## New Paradigm Offered by Event Tracker + +Event Tracker introduces a new paradigm for event tracking. Its declarative approach simplifies traditional complexities, making event tracking accessible for all developers. + + + +### Declarative Event Tracking + +```tsx {7,10,12,32,34} +function Page() { + const { user, userId } = useUser(); + + // Provides tracking context (userId) to child components via Track.Provider. + // Eliminates the need for prop drilling. + return ( + +
+

User: {user.name}

+ {/* No need to pass userId as prop */} +
+
+ ); +} + +function Counter() { + const [count, setCount] = useState(0); + + const handleIncrement = () => { + // handleIncrement now focuses solely on increment logic. + setCount(count + 1); + }; + + return ( +
+

Count: {count}

+ {/* + Track.Click component wraps the click event and automatically tracks the event + with the provided parameters. The userId provided in the context is automatically included. + */} + + + +
+ ); +} +``` + +With Event Tracker, declarative event tracking significantly improves code readability and reduces complexity, helping developers more easily understand and implement tracking. + +Now the `handleIncrement` function is only responsible for increasing the count, while the `` component handles tracking. +**This declarative approach lets developers focus on 'what to track' rather than 'how to track.'** +The actual tracking logic is defined externally from the React app. + +### Improved Cohesion in Event Tracking + +```tsx {4-10,14-20} +const [Track, useTracker] = createTracker({ + // Callback executed on DOM events + DOMEvents: { + onClick: (params, context) => { + // Call the actual tracking library (e.g., Google Analytics, Amplitude) + logEvent("click_event", { + ...params, // { value: ..., type: "count" } + userId: context.userId, // userId from Track.Provider + }); + }, + // Define additional DOM event handlers (onMouseOver, onFocus, etc.) as needed + }, + // Callback executed on Impression events + onImpression: (params, context) => { + logEvent("impression_event", { + ...params, + userId: context.userId, + pagePath: window.location.pathname, + }); + }, +}); +``` + +Now the code for **'how to track'** is separated from business logic. Located externally, it can be modified without impacting business logic. + +### Data Type Validation + +```tsx {13-20,24-33} +import { z } from "zod"; +import { createTracker } from "@offlegacy/event-tracker"; + +interface Context { + /* ... */ +} + +interface Params { + /* ... */ +} + +// Schema definition +const schemas = { + page_view: z.object({ + title: z.string(), + }), + click_button: z.object({ + target: z.string(), + }), +}; + +// Tracker setup +const [Track] = createTracker({ + schema: { + schemas: { + page_view, + click_button, + }, + onSchemaError: (error) => { + console.error("Schema validation error:", error); + }, + abortOnError: true, + }, +}); + +// Using schemas +; +; +``` + +Event Tracker optionally integrates with [Zod](https://zod.dev/) to offer robust schema-based data validation. This ensures data correctness from the development stage, enhancing tracking data reliability. + +
From 355861d2782b330de004d6f3a340f1bec9f104d5 Mon Sep 17 00:00:00 2001 From: gwansikk Date: Wed, 25 Jun 2025 20:51:48 +0900 Subject: [PATCH 04/24] feat(docs): new landing page --- docs/next.config.ts | 8 ++ docs/package.json | 4 +- docs/pnpm-lock.yaml | 48 ++++++++ docs/src/app/[lang]/layout.tsx | 4 +- docs/src/app/[lang]/page.tsx | 148 +++++++++++++++--------- docs/src/components/demo-button.tsx | 20 ++++ docs/src/components/demo-playground.tsx | 52 +++++++++ 7 files changed, 224 insertions(+), 60 deletions(-) create mode 100644 docs/src/components/demo-button.tsx create mode 100644 docs/src/components/demo-playground.tsx diff --git a/docs/next.config.ts b/docs/next.config.ts index 6a5d444..ce2bbac 100644 --- a/docs/next.config.ts +++ b/docs/next.config.ts @@ -14,6 +14,14 @@ const nextConfig: NextConfig = { locales: ["en", "ko"], defaultLocale: "en", }, + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "contrib.rocks", + }, + ], + }, }; export default withNextra(nextConfig); diff --git a/docs/package.json b/docs/package.json index dd03668..960d0b8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -13,11 +13,13 @@ "dependencies": { "@amplitude/analytics-browser": "^2.11.11", "@offlegacy/event-tracker": "^1.0.3", + "codehike": "^1.0.7", "next": "15.1.6", "nextra": "^4.0.7", "nextra-theme-docs": "^4.0.7", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "sonner": "^2.0.5" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index cf56380..331d05f 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@offlegacy/event-tracker': specifier: ^1.0.3 version: 1.0.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + codehike: + specifier: ^1.0.7 + version: 1.0.7 next: specifier: 15.1.6 version: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -29,6 +32,9 @@ importers: react-dom: specifier: ^19.0.0 version: 19.0.0(react@19.0.0) + sonner: + specifier: ^2.0.5 + version: 2.0.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) devDependencies: '@eslint/eslintrc': specifier: ^3.2.0 @@ -121,6 +127,9 @@ packages: '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@code-hike/lighter@1.0.1': + resolution: {integrity: sha512-mccvcsk5UTScRrE02oBz1/qzckyhD8YE3VQlQv++2bSVVZgNuCUX8MpokSCi5OmfRAAxbj6kmNiqq1Um8eXPrw==} + '@emnapi/runtime@1.3.1': resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} @@ -856,6 +865,9 @@ packages: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} + ansi-sequence-parser@1.1.1: + resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1037,6 +1049,9 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + codehike@1.0.7: + resolution: {integrity: sha512-m4YOQv1l06qoGBLCgXRNT45MKAudXjir9v+WTs7Xb0gfze6LN3ZiUp08+WRA1N/QGT1SHbzvck8txvPq+C2EPg==} + collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} @@ -1322,6 +1337,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -2856,6 +2875,12 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} + sonner@2.0.5: + resolution: {integrity: sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3314,6 +3339,10 @@ snapshots: '@chevrotain/utils@11.0.3': {} + '@code-hike/lighter@1.0.1': + dependencies: + ansi-sequence-parser: 1.1.1 + '@emnapi/runtime@1.3.1': dependencies: tslib: 2.8.1 @@ -4083,6 +4112,8 @@ snapshots: ansi-regex@6.1.0: {} + ansi-sequence-parser@1.1.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -4286,6 +4317,16 @@ snapshots: clsx@2.1.1: {} + codehike@1.0.7: + dependencies: + '@code-hike/lighter': 1.0.1 + diff: 5.2.0 + estree-util-visit: 2.0.0 + mdast-util-mdx-jsx: 3.2.0 + unist-util-visit: 5.0.0 + transitivePeerDependencies: + - supports-color + collapse-white-space@2.1.0: {} color-convert@2.0.1: @@ -4587,6 +4628,8 @@ snapshots: didyoumean@1.2.2: {} + diff@5.2.0: {} + dlv@1.1.3: {} doctrine@2.1.0: @@ -6867,6 +6910,11 @@ snapshots: slash@5.1.0: {} + sonner@2.0.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + source-map-js@1.2.1: {} source-map@0.7.4: {} diff --git a/docs/src/app/[lang]/layout.tsx b/docs/src/app/[lang]/layout.tsx index a3f739e..7782110 100644 --- a/docs/src/app/[lang]/layout.tsx +++ b/docs/src/app/[lang]/layout.tsx @@ -7,8 +7,8 @@ import "nextra-theme-docs/style-prefixed.css"; import "../globals.css"; import { TrackClick, TrackImpression, TrackProvider } from "@/tracker"; import { Logo } from "@/logo"; -import { Lang } from "@/lib/types/lang"; -import { type Metadata } from "next"; +import type { Lang } from "@/lib/types/lang"; +import type { Metadata } from "next"; export const metadata: Metadata = { title: { diff --git a/docs/src/app/[lang]/page.tsx b/docs/src/app/[lang]/page.tsx index 61cc581..a0ee08e 100644 --- a/docs/src/app/[lang]/page.tsx +++ b/docs/src/app/[lang]/page.tsx @@ -1,10 +1,11 @@ import Link from "next/link"; -import { Image } from "nextra/components"; import { Logo } from "@/logo"; import { TrackClick, TrackPageView } from "@/tracker"; import { importPage } from "nextra/pages"; -import { Lang } from "@/lib/types/lang"; +import type { Lang } from "@/lib/types/lang"; +import { DemoCode } from "@/components/demo-playground"; +import Image from "next/image"; export const metadata = {}; @@ -17,65 +18,76 @@ const TEXT_MAP = { ko: "시작하기", en: "Get Started", }, + featureCardsTitle: { + ko: "Event Tracker가 제시하는 새로운 패러다임", + en: "New Paradigm Offered by Event Tracker", + }, featureCards: [ { ko: { - title: "타입 안전성이 보장된 API", - description: "타입 안전성을 갖춘 선언적 이벤트 트래킹 API를 제공합니다.", + title: "타입 안정성을 갖춘 선언적 API", + description: + "TypeScript를 완벽하게 지원하여 개발 과정에서의 오류를 줄이고, 자동 완성을 통해 생산성을 높입니다.", }, en: { - title: "Type-safe APIs", - description: "Declarative event tracking with complete type safety", + title: "Declarative API with Type Safety", + description: + "Fully supports TypeScript, reducing errors during development and increasing productivity through auto-completion.", }, }, { ko: { - title: "최적화된 성능", - description: "이벤트 배칭을 통한 향상된 성능을 제공합니다.", + title: "강력한 데이터 타입 검증", + description: "Zod를 활용한 스키마 기반 검증으로 데이터의 신뢰성을 확보합니다.", }, en: { - title: "Optimized Performance", - description: "Enhanced performance through event batching", + title: "Powerful Data Type Validation", + description: "Ensures data reliability through schema-based validation using Zod.", }, }, { ko: { - title: "순서 보장", - description: "비동기 작업의 실행 순서가 보장됩니다.", + title: "최적화된 성능", + description: + "배칭이나 디바운스, 스로틀링 기능을 통해 네트워크 요청을 최소화하고 애플리케이션 성능에 미치는 영향을 줄입니다.", }, en: { - title: "Guaranteed Order", - description: "Guaranteed execution order for async operations", + title: "Optimized Performance ", + description: + "Minimizes network requests through batching, debouncing, or throttling, reducing the impact on application performance.", }, }, { ko: { - title: "애널리틱스 도구에 구애받지 않음", - description: "모든 애널리틱스 도구와 호환 가능합니다.", + title: "실행 순서 보장", + description: "비동기적으로 발생하는 이벤트들에 대해서도 의도한 순서대로 처리되도록 보장합니다.", }, en: { - title: "Analytics Integration", - description: "Works with any analytics provider of your choice", + title: "Guaranteed Execution Order", + description: "Ensures asynchronous events are processed in the intended sequence.", }, }, { ko: { - title: "관심사의 분리", - description: "비즈니스 로직과 이벤트 트래킹 로직의 명확한 분리를 제공합니다.", + title: "애널리틱스 도구 독립성 ", + description: + "특정 애널리틱스 서비스에 종속되지 않고, 원하는 모든 도구(Google Analytics, Amplitude 등)와 유연하게 통합할 수 있습니다.", }, en: { - title: "Clean Separation", - description: "Keeps tracking logic separate from business logic", + title: "Analytics Tool Independence", + description: + "Flexible integration with any analytics tool (e.g., Google Analytics, Amplitude), without being tied to any specific provider.", }, }, { ko: { - title: "작은 번들 사이즈", - description: "애플리케이션 번들 크기에 최소한의 영향을 미칩니다.", + title: "명확한 관심사 분리", + description: "트래킹 로직과 비즈니스 로직을 효과적으로 분리하여 코드의 유지보수성과 확장성을 극대화합니다.", }, en: { - title: "Lightweight", - description: "Minimal bundle size impact on your application", + title: "Clear Separation of Concerns", + description: + "Effectively separates tracking logic from business logic, maximizing code maintainability and scalability.", }, }, ], @@ -86,73 +98,95 @@ export default async function Page(props: { params: Promise<{ mdxPath: string[]; await importPage(mdxPath, lang); return ( - <> -
-
+
+ +
+
-

event-tracker

-

{TEXT_MAP.description[lang]}

-
- - MIT License - - - NPM Version - +
+

Event Tracker

+

{TEXT_MAP.description[lang]}

- - + + + {TEXT_MAP.get_started[lang]} + + + +
- -
+ +
+
+

{TEXT_MAP.featureCardsTitle[lang]}

+
- - +
+

Driven by the Community

+ + Contributors + +
+
); } function FeatureCard({ icon, title, description }: { icon: string; title: string; description: string }) { return ( -
+
{icon}
-

{title}

+

{title}

{description}

); diff --git a/docs/src/components/demo-button.tsx b/docs/src/components/demo-button.tsx new file mode 100644 index 0000000..8069140 --- /dev/null +++ b/docs/src/components/demo-button.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { Toaster, toast } from "sonner"; + +export function DemoButton({ children }: { children: React.ReactNode }) { + return ( + <> + + + + ); +} diff --git a/docs/src/components/demo-playground.tsx b/docs/src/components/demo-playground.tsx new file mode 100644 index 0000000..5959a03 --- /dev/null +++ b/docs/src/components/demo-playground.tsx @@ -0,0 +1,52 @@ +import { highlight, Pre } from "codehike/code"; +import { DemoButton } from "./demo-button"; + +export async function DemoCode() { + const isDark = typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches; + const theme = isDark ? "github-dark" : "github-light"; + + const highlighted = await highlight( + { + value: `import { createTracker } from "@offlegacy/event-tracker"; + +const [Track, useTracker] = createTracker({ + DOMEvents: { + onClick: (params, context) => { + toast.success(\${context.userId}: \${params.buttonId}\`); + }, + }, +}); + +function App() { + return ( + + + + + + ); +}`, + lang: "tsx", + meta: "", + }, + theme, + ); + + return ( +
+
+
+
+ basic-example.tsx +
+
+
+
+        
+
+
+ Click me +
+
+ ); +} From 1802d57c42e0f0f1ce86fd39915c7a5a478eac75 Mon Sep 17 00:00:00 2001 From: gwansikk Date: Wed, 25 Jun 2025 21:18:59 +0900 Subject: [PATCH 05/24] chore: update --- docs/src/components/demo-playground.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/components/demo-playground.tsx b/docs/src/components/demo-playground.tsx index 5959a03..86cd669 100644 --- a/docs/src/components/demo-playground.tsx +++ b/docs/src/components/demo-playground.tsx @@ -12,7 +12,7 @@ export async function DemoCode() { const [Track, useTracker] = createTracker({ DOMEvents: { onClick: (params, context) => { - toast.success(\${context.userId}: \${params.buttonId}\`); + toast.success(\`\${context.userId}: \${params.buttonId}\`); }, }, }); From f12cd23a439db6e2639206485363525f44d151f6 Mon Sep 17 00:00:00 2001 From: gwansikk Date: Thu, 3 Jul 2025 01:42:47 +0900 Subject: [PATCH 06/24] fix(docs): update code example and improve highlighting logic --- docs/src/components/demo-playground.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/src/components/demo-playground.tsx b/docs/src/components/demo-playground.tsx index 86cd669..15132da 100644 --- a/docs/src/components/demo-playground.tsx +++ b/docs/src/components/demo-playground.tsx @@ -1,15 +1,9 @@ import { highlight, Pre } from "codehike/code"; import { DemoButton } from "./demo-button"; -export async function DemoCode() { - const isDark = typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches; - const theme = isDark ? "github-dark" : "github-light"; +const code = `import { createTracker } from "@offlegacy/event-tracker"; - const highlighted = await highlight( - { - value: `import { createTracker } from "@offlegacy/event-tracker"; - -const [Track, useTracker] = createTracker({ +const [Track] = createTracker({ DOMEvents: { onClick: (params, context) => { toast.success(\`\${context.userId}: \${params.buttonId}\`); @@ -25,7 +19,15 @@ function App() { ); -}`, +}`; + +export async function DemoCode() { + const isDark = typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches; + const theme = isDark ? "github-dark" : "github-light"; + + const highlighted = await highlight( + { + value: code, lang: "tsx", meta: "", }, From c29e387167f978955ebb068136b99c9722a6682c Mon Sep 17 00:00:00 2001 From: gwansikk Date: Thu, 3 Jul 2025 01:42:53 +0900 Subject: [PATCH 07/24] feat(docs): integrate event tracking into demo button component --- docs/src/components/demo-button.tsx | 31 ++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/src/components/demo-button.tsx b/docs/src/components/demo-button.tsx index 8069140..a4ce002 100644 --- a/docs/src/components/demo-button.tsx +++ b/docs/src/components/demo-button.tsx @@ -1,20 +1,33 @@ "use client"; +import { createTracker } from "@offlegacy/event-tracker"; import { Toaster, toast } from "sonner"; +const [Track] = createTracker({ + DOMEvents: { + onClick: (params: { buttonId: string }, context: { userId: string }) => { + toast.success(`${context.userId}: ${params.buttonId}`); + }, + }, +}); + export function DemoButton({ children }: { children: React.ReactNode }) { return ( <> - + + + + + ); } From 4fb8eaaafd45265b8ec37dea8a2919076458c10c Mon Sep 17 00:00:00 2001 From: gwansikk Date: Wed, 9 Jul 2025 00:19:33 +0900 Subject: [PATCH 08/24] feat(docs): enhance demo components with animations and improved button interactions --- docs/src/components/demo-button.tsx | 34 +++++++++++--- docs/src/components/demo-playground.tsx | 60 ++++++++++++++++++------- 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/docs/src/components/demo-button.tsx b/docs/src/components/demo-button.tsx index a4ce002..fd69065 100644 --- a/docs/src/components/demo-button.tsx +++ b/docs/src/components/demo-button.tsx @@ -2,6 +2,7 @@ import { createTracker } from "@offlegacy/event-tracker"; import { Toaster, toast } from "sonner"; +import { motion } from "motion/react"; const [Track] = createTracker({ DOMEvents: { @@ -14,18 +15,37 @@ const [Track] = createTracker({ export function DemoButton({ children }: { children: React.ReactNode }) { return ( <> - + - - + diff --git a/docs/src/components/demo-playground.tsx b/docs/src/components/demo-playground.tsx index 15132da..38acffb 100644 --- a/docs/src/components/demo-playground.tsx +++ b/docs/src/components/demo-playground.tsx @@ -1,11 +1,19 @@ -import { highlight, Pre } from "codehike/code"; +"use client"; + +import { highlight, Pre, type HighlightedCode } from "codehike/code"; import { DemoButton } from "./demo-button"; +import { useTheme } from "nextra-theme-docs"; +import { useSystemDarkMode } from "../logo/useSystemDarkMode"; +import { useEffect, useState } from "react"; +import { motion } from "motion/react"; const code = `import { createTracker } from "@offlegacy/event-tracker"; +import { capture } from "you-can-use-analytics"; const [Track] = createTracker({ DOMEvents: { onClick: (params, context) => { + capture("click", { ...params, ...context }); toast.success(\`\${context.userId}: \${params.buttonId}\`); }, }, @@ -14,41 +22,59 @@ const [Track] = createTracker({ function App() { return ( - + ); }`; -export async function DemoCode() { - const isDark = typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches; - const theme = isDark ? "github-dark" : "github-light"; +export function DemoCode() { + const { theme } = useTheme(); + const isSystemDarkMode = useSystemDarkMode(); + const resolvedTheme = theme === "system" ? (isSystemDarkMode ? "dark" : "light") : theme; + const codeTheme = resolvedTheme === "dark" ? "github-dark" : "github-light"; + const [highlighted, setHighlighted] = useState(null); - const highlighted = await highlight( - { - value: code, - lang: "tsx", - meta: "", - }, - theme, - ); + useEffect(() => { + highlight( + { + value: code, + lang: "tsx", + meta: "", + }, + codeTheme, + ).then(setHighlighted); + }, [codeTheme]); + + if (!highlighted) return null; return ( -
+
basic-example.tsx
-
+
         
-
+
Click me
-
+ ); } From cacf35d4137461c90fec3620b551ecff15025e68 Mon Sep 17 00:00:00 2001 From: gwansikk Date: Wed, 9 Jul 2025 00:19:45 +0900 Subject: [PATCH 09/24] feat(docs): add InstallCopyButton component for easy command copying --- docs/src/components/install-copy-button.tsx | 55 +++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/src/components/install-copy-button.tsx diff --git a/docs/src/components/install-copy-button.tsx b/docs/src/components/install-copy-button.tsx new file mode 100644 index 0000000..cd16267 --- /dev/null +++ b/docs/src/components/install-copy-button.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { Copy, Check } from "lucide-react"; +import { motion, AnimatePresence } from "motion/react"; +import { useCopyToClipboard } from "@uidotdev/usehooks"; +import { useState } from "react"; + +const COMMAND = "npm i @offlegacy/event-tracker"; + +export function InstallCopyButton() { + const [, copyToClipboard] = useCopyToClipboard(); + const [showCheck, setShowCheck] = useState(false); + + return ( + + ); +} From b9431dc5ae96c1957fe75578faf563a2dabf84ad Mon Sep 17 00:00:00 2001 From: gwansikk Date: Wed, 9 Jul 2025 00:20:02 +0900 Subject: [PATCH 10/24] feat(docs): refine feature descriptions and improve layout for better clarity --- docs/src/app/[lang]/page.tsx | 94 +++++++++++++++++------------------- 1 file changed, 44 insertions(+), 50 deletions(-) diff --git a/docs/src/app/[lang]/page.tsx b/docs/src/app/[lang]/page.tsx index a0ee08e..ca07957 100644 --- a/docs/src/app/[lang]/page.tsx +++ b/docs/src/app/[lang]/page.tsx @@ -6,88 +6,92 @@ import { importPage } from "nextra/pages"; import type { Lang } from "@/lib/types/lang"; import { DemoCode } from "@/components/demo-playground"; import Image from "next/image"; +import { InstallCopyButton } from "@/components/install-copy-button"; export const metadata = {}; const TEXT_MAP = { description: { - ko: "React 애플리케이션을 위한 완전한 이벤트 트래킹 시스템.", - en: "Comprehensive solution for event tracking in React applications.", + ko: "React 애플리케이션을 위한 완전한 이벤트 트래킹 시스템", + en: "Comprehensive solution for event tracking in React applications", }, get_started: { ko: "시작하기", en: "Get Started", }, featureCardsTitle: { - ko: "Event Tracker가 제시하는 새로운 패러다임", - en: "New Paradigm Offered by Event Tracker", + ko: "Event Tracker가 제시하는 새로운 이벤트 트래킹 패러다임", + en: "New Paradigm of Event Tracking Offered by Event Tracker", }, featureCards: [ { ko: { - title: "타입 안정성을 갖춘 선언적 API", - description: - "TypeScript를 완벽하게 지원하여 개발 과정에서의 오류를 줄이고, 자동 완성을 통해 생산성을 높입니다.", + title: "선언적 API", + description: "트래킹을 위한 별도의 설정이나 복잡한 코드 없이, 컴포넌트 형태로 직관적으로 선언할 수 있습니다.", }, en: { - title: "Declarative API with Type Safety", - description: - "Fully supports TypeScript, reducing errors during development and increasing productivity through auto-completion.", + title: "Declarative API", + description: "Track events intuitively through component-based declarations, without boilerplate.", }, }, { ko: { title: "강력한 데이터 타입 검증", - description: "Zod를 활용한 스키마 기반 검증으로 데이터의 신뢰성을 확보합니다.", + description: + "Zod 기반의 정적 스키마 검증으로 런타임 이전에 오류를 방지하고, 수집되는 이벤트 데이터의 신뢰도를 보장합니다.", }, en: { title: "Powerful Data Type Validation", - description: "Ensures data reliability through schema-based validation using Zod.", + description: + "Schema-based validation with Zod catches issues before runtime and guarantees the integrity of your event data.", }, }, { ko: { title: "최적화된 성능", description: - "배칭이나 디바운스, 스로틀링 기능을 통해 네트워크 요청을 최소화하고 애플리케이션 성능에 미치는 영향을 줄입니다.", + "배칭, 디바운스, 스로틀링 등 네트워크 요청을 최소화하는 다양한 전략이 내장되어 있어, 성능 저하 없이 안정적으로 트래킹할 수 있습니다.", }, en: { - title: "Optimized Performance ", + title: "Optimized Performance", description: - "Minimizes network requests through batching, debouncing, or throttling, reducing the impact on application performance.", + "Built-in strategies like batching, debouncing, and throttling minimize network overhead, enabling efficient and reliable tracking.", }, }, { ko: { title: "실행 순서 보장", - description: "비동기적으로 발생하는 이벤트들에 대해서도 의도한 순서대로 처리되도록 보장합니다.", + description: + "비동기 상황에서도 이벤트가 의도한 순서대로 처리되도록 설계되어, 복잡한 사용자 흐름에서도 정확한 트래킹이 가능합니다.", }, en: { title: "Guaranteed Execution Order", - description: "Ensures asynchronous events are processed in the intended sequence.", + description: + "Ensures that events are processed in the intended order, even under asynchronous conditions—crucial for accurate tracking in complex flows.", }, }, { ko: { - title: "애널리틱스 도구 독립성 ", + title: "애널리틱스 도구 독립성", description: - "특정 애널리틱스 서비스에 종속되지 않고, 원하는 모든 도구(Google Analytics, Amplitude 등)와 유연하게 통합할 수 있습니다.", + "Google Analytics, Amplitude, Segment 등 어떤 도구든 자유롭게 연동할 수 있어, 기존 인프라를 변경하지 않고도 도입이 가능합니다.", }, en: { title: "Analytics Tool Independence", description: - "Flexible integration with any analytics tool (e.g., Google Analytics, Amplitude), without being tied to any specific provider.", + "Easily integrates with tools like Google Analytics, Amplitude, or Segment—without forcing you to switch or couple to a specific vendor.", }, }, { ko: { title: "명확한 관심사 분리", - description: "트래킹 로직과 비즈니스 로직을 효과적으로 분리하여 코드의 유지보수성과 확장성을 극대화합니다.", + description: + "트래킹 로직을 비즈니스 코드에서 완전히 분리함으로써 코드의 가독성, 테스트 용이성, 유지보수성을 모두 향상시킵니다.", }, en: { title: "Clear Separation of Concerns", description: - "Effectively separates tracking logic from business logic, maximizing code maintainability and scalability.", + "Keeps tracking logic out of your business logic, resulting in cleaner code that’s easier to test, maintain, and evolve.", }, }, ], @@ -98,67 +102,58 @@ export default async function Page(props: { params: Promise<{ mdxPath: string[]; await importPage(mdxPath, lang); return ( -
+
-
+

Event Tracker

{TEXT_MAP.description[lang]}

-
+
{TEXT_MAP.get_started[lang]} - +
- +
+ +
-
+

{TEXT_MAP.featureCardsTitle[lang]}

- - + +
@@ -182,10 +177,9 @@ export default async function Page(props: { params: Promise<{ mdxPath: string[]; ); } -function FeatureCard({ icon, title, description }: { icon: string; title: string; description: string }) { +function FeatureCard({ title, description }: { title: string; description: string }) { return (
-
{icon}

{title}

{description}

From 7d30a1492cd31e258c9b192e58d41ed5d9acf988 Mon Sep 17 00:00:00 2001 From: gwansikk Date: Wed, 9 Jul 2025 00:27:46 +0900 Subject: [PATCH 11/24] feat(docs): update layout metadata and improve footer styling --- docs/src/app/[lang]/layout.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/src/app/[lang]/layout.tsx b/docs/src/app/[lang]/layout.tsx index 7782110..2e5efa7 100644 --- a/docs/src/app/[lang]/layout.tsx +++ b/docs/src/app/[lang]/layout.tsx @@ -13,13 +13,14 @@ import type { Metadata } from "next"; export const metadata: Metadata = { title: { template: "%s | Event Tracker", - default: "Event Tracker", + default: "Event Tracker | Comprehensive solution for event tracking in React applications", }, description: "Comprehensive solution for event tracking in React applications.", + metadataBase: new URL("https://event-tracker.offlegacy.org"), openGraph: { title: { template: "%s | Event Tracker", - default: "Event Tracker", + default: "Event Tracker | Comprehensive solution for event tracking in React applications", }, images: ["img/og-webp.webp"], description: "Comprehensive solution for event tracking in React applications.", @@ -35,7 +36,7 @@ const navbar = (
- Event Tracker + Event Tracker
} @@ -46,7 +47,7 @@ const navbar = ( ); const footer = ( -
MIT {new Date().getFullYear()} © OffLegacy
+
MIT {new Date().getFullYear()} © OffLegacy
); @@ -81,7 +82,7 @@ export default async function RootLayout({ pageMap={pageMap} docsRepositoryBase="https://github.com/offlegacy/event-tracker/tree/main/docs" footer={footer} - darkMode + darkMode={false} > {children} From 038f8271c5bc2f45ccaef0f2ed40f12cff664114 Mon Sep 17 00:00:00 2001 From: gwansikk Date: Wed, 9 Jul 2025 00:27:51 +0900 Subject: [PATCH 12/24] chore(docs): update package dependencies and lockfile versions --- docs/package.json | 23 +- docs/pnpm-lock.yaml | 812 +++++++++++++++++++++++++------------------- 2 files changed, 477 insertions(+), 358 deletions(-) diff --git a/docs/package.json b/docs/package.json index 960d0b8..d454c2d 100644 --- a/docs/package.json +++ b/docs/package.json @@ -12,25 +12,28 @@ }, "dependencies": { "@amplitude/analytics-browser": "^2.11.11", - "@offlegacy/event-tracker": "^1.0.3", + "@offlegacy/event-tracker": "latest", + "@uidotdev/usehooks": "^2.4.1", "codehike": "^1.0.7", - "next": "15.1.6", - "nextra": "^4.0.7", - "nextra-theme-docs": "^4.0.7", - "react": "^19.0.0", + "lucide-react": "^0.525.0", + "motion": "^12.23.0", + "next": "15.3.5", + "nextra": "^4.2.17", + "nextra-theme-docs": "^4.2.17", + "react": "^19.1.0", "react-dom": "^19.0.0", - "sonner": "^2.0.5" + "sonner": "^2.0.6" }, "devDependencies": { - "@eslint/eslintrc": "^3.2.0", + "@eslint/eslintrc": "^3.3.1", "@types/node": "^20", - "@types/react": "^19.0.0", + "@types/react": "^19.1.8", "@types/react-dom": "^19.0.0", "eslint": "^9", - "eslint-config-next": "15.1.6", + "eslint-config-next": "15.3.5", "pagefind": "^1.3.0", "postcss": "^8", - "prettier-plugin-tailwindcss": "^0.6.11", + "prettier-plugin-tailwindcss": "^0.6.13", "tailwindcss": "^3.4.1", "typescript": "^5" } diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 331d05f..f419721 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -12,48 +12,57 @@ importers: specifier: ^2.11.11 version: 2.11.11 '@offlegacy/event-tracker': - specifier: ^1.0.3 - version: 1.0.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: latest + version: 1.3.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(zod@3.24.1) + '@uidotdev/usehooks': + specifier: ^2.4.1 + version: 2.4.1(react-dom@19.0.0(react@19.1.0))(react@19.1.0) codehike: specifier: ^1.0.7 version: 1.0.7 + lucide-react: + specifier: ^0.525.0 + version: 0.525.0(react@19.1.0) + motion: + specifier: ^12.23.0 + version: 12.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) next: - specifier: 15.1.6 - version: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: 15.3.5 + version: 15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0) nextra: - specifier: ^4.0.7 - version: 4.0.7(acorn@8.14.0)(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + specifier: ^4.2.17 + version: 4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3) nextra-theme-docs: - specifier: ^4.0.7 - version: 4.0.7(@types/react@19.0.8)(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(nextra@4.0.7(acorn@8.14.0)(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: ^4.2.17 + version: 4.2.17(@types/react@19.1.8)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(nextra@4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3))(react-dom@19.0.0(react@19.1.0))(react@19.1.0) react: - specifier: ^19.0.0 - version: 19.0.0 + specifier: ^19.1.0 + version: 19.1.0 react-dom: specifier: ^19.0.0 - version: 19.0.0(react@19.0.0) + version: 19.0.0(react@19.1.0) sonner: - specifier: ^2.0.5 - version: 2.0.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: ^2.0.6 + version: 2.0.6(react-dom@19.0.0(react@19.1.0))(react@19.1.0) devDependencies: '@eslint/eslintrc': - specifier: ^3.2.0 - version: 3.2.0 + specifier: ^3.3.1 + version: 3.3.1 '@types/node': specifier: ^20 version: 20.17.16 '@types/react': - specifier: ^19.0.0 - version: 19.0.8 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.0.0 - version: 19.0.3(@types/react@19.0.8) + version: 19.0.3(@types/react@19.1.8) eslint: specifier: ^9 version: 9.18.0(jiti@1.21.7) eslint-config-next: - specifier: 15.1.6 - version: 15.1.6(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3) + specifier: 15.3.5 + version: 15.3.5(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3) pagefind: specifier: ^1.3.0 version: 1.3.0 @@ -61,8 +70,8 @@ importers: specifier: ^8 version: 8.5.1 prettier-plugin-tailwindcss: - specifier: ^0.6.11 - version: 0.6.11(prettier@3.4.2) + specifier: ^0.6.13 + version: 0.6.13(prettier@3.4.2) tailwindcss: specifier: ^3.4.1 version: 3.4.17 @@ -130,8 +139,8 @@ packages: '@code-hike/lighter@1.0.1': resolution: {integrity: sha512-mccvcsk5UTScRrE02oBz1/qzckyhD8YE3VQlQv++2bSVVZgNuCUX8MpokSCi5OmfRAAxbj6kmNiqq1Um8eXPrw==} - '@emnapi/runtime@1.3.1': - resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@emnapi/runtime@1.4.4': + resolution: {integrity: sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==} '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} @@ -151,8 +160,8 @@ packages: resolution: {integrity: sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.2.0': - resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.18.0': @@ -188,8 +197,8 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} - '@formatjs/intl-localematcher@0.5.10': - resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==} + '@formatjs/intl-localematcher@0.6.1': + resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==} '@headlessui/react@2.2.0': resolution: {integrity: sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==} @@ -224,107 +233,118 @@ packages: '@iconify/utils@2.2.1': resolution: {integrity: sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==} - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + '@img/sharp-darwin-arm64@0.34.2': + resolution: {integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + '@img/sharp-darwin-x64@0.34.2': + resolution: {integrity: sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + '@img/sharp-libvips-darwin-arm64@1.1.0': + resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + '@img/sharp-libvips-darwin-x64@1.1.0': + resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + '@img/sharp-libvips-linux-arm64@1.1.0': + resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + '@img/sharp-libvips-linux-arm@1.1.0': + resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + '@img/sharp-libvips-linux-ppc64@1.1.0': + resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.1.0': + resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + '@img/sharp-libvips-linux-x64@1.1.0': + resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + '@img/sharp-linux-arm64@0.34.2': + resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + '@img/sharp-linux-arm@0.34.2': + resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + '@img/sharp-linux-s390x@0.34.2': + resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + '@img/sharp-linux-x64@0.34.2': + resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + '@img/sharp-linuxmusl-arm64@0.34.2': + resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + '@img/sharp-linuxmusl-x64@0.34.2': + resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + '@img/sharp-wasm32@0.34.2': + resolution: {integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + '@img/sharp-win32-arm64@0.34.2': + resolution: {integrity: sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.2': + resolution: {integrity: sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + '@img/sharp-win32-x64@0.34.2': + resolution: {integrity: sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -445,56 +465,56 @@ packages: resolution: {integrity: sha512-jMxvwzkKzd3cXo2EB9GM2ic0eYo2rP/BS6gJt6HnWbsDO1O8GSD4k7o2Cpr2YERtMpGF/MGcDfsfj2EbQPtrXw==} engines: {node: '>= 10'} - '@next/env@15.1.6': - resolution: {integrity: sha512-d9AFQVPEYNr+aqokIiPLNK/MTyt3DWa/dpKveiAaVccUadFbhFEvY6FXYX2LJO2Hv7PHnLBu2oWwB4uBuHjr/w==} + '@next/env@15.3.5': + resolution: {integrity: sha512-7g06v8BUVtN2njAX/r8gheoVffhiKFVt4nx74Tt6G4Hqw9HCLYQVx/GkH2qHvPtAHZaUNZ0VXAa0pQP6v1wk7g==} - '@next/eslint-plugin-next@15.1.6': - resolution: {integrity: sha512-+slMxhTgILUntZDGNgsKEYHUvpn72WP1YTlkmEhS51vnVd7S9jEEy0n9YAMcI21vUG4akTw9voWH02lrClt/yw==} + '@next/eslint-plugin-next@15.3.5': + resolution: {integrity: sha512-BZwWPGfp9po/rAnJcwUBaM+yT/+yTWIkWdyDwc74G9jcfTrNrmsHe+hXHljV066YNdVs8cxROxX5IgMQGX190w==} - '@next/swc-darwin-arm64@15.1.6': - resolution: {integrity: sha512-u7lg4Mpl9qWpKgy6NzEkz/w0/keEHtOybmIl0ykgItBxEM5mYotS5PmqTpo+Rhg8FiOiWgwr8USxmKQkqLBCrw==} + '@next/swc-darwin-arm64@15.3.5': + resolution: {integrity: sha512-lM/8tilIsqBq+2nq9kbTW19vfwFve0NR7MxfkuSUbRSgXlMQoJYg+31+++XwKVSXk4uT23G2eF/7BRIKdn8t8w==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.1.6': - resolution: {integrity: sha512-x1jGpbHbZoZ69nRuogGL2MYPLqohlhnT9OCU6E6QFewwup+z+M6r8oU47BTeJcWsF2sdBahp5cKiAcDbwwK/lg==} + '@next/swc-darwin-x64@15.3.5': + resolution: {integrity: sha512-WhwegPQJ5IfoUNZUVsI9TRAlKpjGVK0tpJTL6KeiC4cux9774NYE9Wu/iCfIkL/5J8rPAkqZpG7n+EfiAfidXA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.1.6': - resolution: {integrity: sha512-jar9sFw0XewXsBzPf9runGzoivajeWJUc/JkfbLTC4it9EhU8v7tCRLH7l5Y1ReTMN6zKJO0kKAGqDk8YSO2bg==} + '@next/swc-linux-arm64-gnu@15.3.5': + resolution: {integrity: sha512-LVD6uMOZ7XePg3KWYdGuzuvVboxujGjbcuP2jsPAN3MnLdLoZUXKRc6ixxfs03RH7qBdEHCZjyLP/jBdCJVRJQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.1.6': - resolution: {integrity: sha512-+n3u//bfsrIaZch4cgOJ3tXCTbSxz0s6brJtU3SzLOvkJlPQMJ+eHVRi6qM2kKKKLuMY+tcau8XD9CJ1OjeSQQ==} + '@next/swc-linux-arm64-musl@15.3.5': + resolution: {integrity: sha512-k8aVScYZ++BnS2P69ClK7v4nOu702jcF9AIHKu6llhHEtBSmM2zkPGl9yoqbSU/657IIIb0QHpdxEr0iW9z53A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.1.6': - resolution: {integrity: sha512-SpuDEXixM3PycniL4iVCLyUyvcl6Lt0mtv3am08sucskpG0tYkW1KlRhTgj4LI5ehyxriVVcfdoxuuP8csi3kQ==} + '@next/swc-linux-x64-gnu@15.3.5': + resolution: {integrity: sha512-2xYU0DI9DGN/bAHzVwADid22ba5d/xrbrQlr2U+/Q5WkFUzeL0TDR963BdrtLS/4bMmKZGptLeg6282H/S2i8A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.1.6': - resolution: {integrity: sha512-L4druWmdFSZIIRhF+G60API5sFB7suTbDRhYWSjiw0RbE+15igQvE2g2+S973pMGvwN3guw7cJUjA/TmbPWTHQ==} + '@next/swc-linux-x64-musl@15.3.5': + resolution: {integrity: sha512-TRYIqAGf1KCbuAB0gjhdn5Ytd8fV+wJSM2Nh2is/xEqR8PZHxfQuaiNhoF50XfY90sNpaRMaGhF6E+qjV1b9Tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.1.6': - resolution: {integrity: sha512-s8w6EeqNmi6gdvM19tqKKWbCyOBvXFbndkGHl+c9YrzsLARRdCHsD9S1fMj8gsXm9v8vhC8s3N8rjuC/XrtkEg==} + '@next/swc-win32-arm64-msvc@15.3.5': + resolution: {integrity: sha512-h04/7iMEUSMY6fDGCvdanKqlO1qYvzNxntZlCzfE8i5P0uqzVQWQquU1TIhlz0VqGQGXLrFDuTJVONpqGqjGKQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.1.6': - resolution: {integrity: sha512-6xomMuu54FAFxttYr5PJbEfu96godcxBTRk1OhAvJq0/EnmFU/Ybiax30Snis4vdWZ9LGpf7Roy5fSs7v/5ROQ==} + '@next/swc-win32-x64-msvc@15.3.5': + resolution: {integrity: sha512-5fhH6fccXxnX2KhllnGhkYMndhOiLOLEiVGYjP2nizqeGWkN10sA9taATlXwake2E2XMvYZjjz0Uj7T0y+z1yw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -515,11 +535,12 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} - '@offlegacy/event-tracker@1.0.3': - resolution: {integrity: sha512-RAbZgPRSk3NDe6hUa3GrezmBCf4pFe7H6XoAqE0u0Y1tnZugk+9Lx1ImC0yT6sERCtG9kPHlWPNpTv4WzsOiWQ==} + '@offlegacy/event-tracker@1.3.0': + resolution: {integrity: sha512-js1f8wBkNH7LAorneda0BAW3pElfE9EmhJTmUJgB8j7Q5ccWrTUdP9pjPK28bOPA7r4FNaoQojunN6m49Cd7Dg==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + zod: ^3.20.0 '@pagefind/darwin-arm64@1.3.0': resolution: {integrity: sha512-365BEGl6ChOsauRjyVpBjXybflXAOvoMROw3TucAROHIcdBvXk9/2AmEvGFU0r75+vdQI4LJdJdpH4Y6Yqaj4A==} @@ -590,29 +611,29 @@ packages: '@rushstack/eslint-patch@1.10.5': resolution: {integrity: sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==} - '@shikijs/core@1.29.1': - resolution: {integrity: sha512-Mo1gGGkuOYjDu5H8YwzmOuly9vNr8KDVkqj9xiKhhhFS8jisAtDSEWB9hzqRHLVQgFdA310e8XRJcW4tYhRB2A==} + '@shikijs/core@2.5.0': + resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} - '@shikijs/engine-javascript@1.29.1': - resolution: {integrity: sha512-Hpi8k9x77rCQ7F/7zxIOUruNkNidMyBnP5qAGbLFqg4kRrg1HZhkB8btib5EXbQWTtLb5gBHOdBwshk20njD7Q==} + '@shikijs/engine-javascript@2.5.0': + resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==} - '@shikijs/engine-oniguruma@1.29.1': - resolution: {integrity: sha512-gSt2WhLNgEeLstcweQOSp+C+MhOpTsgdNXRqr3zP6M+BUBZ8Md9OU2BYwUYsALBxHza7hwaIWtFHjQ/aOOychw==} + '@shikijs/engine-oniguruma@2.5.0': + resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==} - '@shikijs/langs@1.29.1': - resolution: {integrity: sha512-iERn4HlyuT044/FgrvLOaZgKVKf3PozjKjyV/RZ5GnlyYEAZFcgwHGkYboeBv2IybQG1KVS/e7VGgiAU4JY2Gw==} + '@shikijs/langs@2.5.0': + resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==} - '@shikijs/themes@1.29.1': - resolution: {integrity: sha512-lb11zf72Vc9uxkl+aec2oW1HVTHJ2LtgZgumb4Rr6By3y/96VmlU44bkxEb8WBWH3RUtbqAJEN0jljD9cF7H7g==} + '@shikijs/themes@2.5.0': + resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==} - '@shikijs/twoslash@1.29.1': - resolution: {integrity: sha512-SN2aam87NjkpjS0O2Zq9SeXSDX1CztLBAROXrJpEe5Qe19dkMUUXY8uhw32Qu/FKjqsK8ycEP2S6FZrd9A2pzw==} + '@shikijs/twoslash@2.5.0': + resolution: {integrity: sha512-OdyoZRbzTB80qHFHdaXT070OG9hiljxbsJMZmrMAPWXG2e4FV8wbC63VBM5BJXa1DH645nw20VX1MzASkO5V9g==} - '@shikijs/types@1.29.1': - resolution: {integrity: sha512-aBqAuhYRp5vSir3Pc9+QPu9WESBOjUo03ao0IHLC4TyTioSsp/SkbAZSrIH4ghYYC1T1KTEpRSBa83bas4RnPA==} + '@shikijs/types@2.5.0': + resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==} - '@shikijs/vscode-textmate@10.0.1': - resolution: {integrity: sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==} + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -777,8 +798,8 @@ packages: peerDependencies: '@types/react': ^19.0.0 - '@types/react@19.0.8': - resolution: {integrity: sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==} + '@types/react@19.1.8': + resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -841,6 +862,13 @@ packages: peerDependencies: typescript: '*' + '@uidotdev/usehooks@2.4.1': + resolution: {integrity: sha512-1I+RwWyS+kdv3Mv0Vmc+p0dPYH0DTRAo04HLyXReYBL9AeseDWUJyi4THuksBJcu9F0Pih69Ak150VDnqbVnXg==} + engines: {node: '>=16'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -1327,8 +1355,8 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} devlop@1.1.0: @@ -1420,8 +1448,8 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - eslint-config-next@15.1.6: - resolution: {integrity: sha512-Wd1uy6y7nBbXUSg9QAuQ+xYEKli5CgUhLjz1QHW11jLDis5vK5XB3PemL6jEmy7HrdhaRFDz+GTZ/3FoH+EUjg==} + eslint-config-next@15.3.5: + resolution: {integrity: sha512-oQdvnIgP68wh2RlR3MdQpvaJ94R6qEFl+lnu8ZKxPj5fsAHrSF/HlAOZcsimLw3DT6bnEQIUdbZC2Ab6sWyptg==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: '>=3.3.1' @@ -1631,6 +1659,20 @@ packages: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} + framer-motion@12.23.0: + resolution: {integrity: sha512-xf6NxTGAyf7zR4r2KlnhFmsRfKIbjqeBupEDBAaEtVIBJX96sAon00kMlsKButSIRwPSHjbRrAPnYdJJ9kyhbA==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2075,6 +2117,11 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lucide-react@0.525.0: + resolution: {integrity: sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + markdown-extensions@2.0.0: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} @@ -2300,6 +2347,26 @@ packages: mlly@1.7.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + motion-dom@12.22.0: + resolution: {integrity: sha512-ooH7+/BPw9gOsL9VtPhEJHE2m4ltnhMlcGMhEqA0YGNhKof7jdaszvsyThXI6LVIKshJUZ9/CP6HNqQhJfV7kw==} + + motion-utils@12.19.0: + resolution: {integrity: sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==} + + motion@12.23.0: + resolution: {integrity: sha512-PPNwblArRH9GRC4F3KtOTiIaYd+mtp324vYq3HIL+ueseoAVqPRK5TPFTAQBcIprfVd0NWo3DLzZSiyWaYFXXQ==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2324,8 +2391,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@15.1.6: - resolution: {integrity: sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==} + next@15.3.5: + resolution: {integrity: sha512-RkazLBMMDJSJ4XZQ81kolSpwiCt907l0xcgcpF4xC2Vml6QVcPNXW0NQRwQ80FFtSn7UM52XN0anaw8TEJXaiw==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -2345,16 +2412,16 @@ packages: sass: optional: true - nextra-theme-docs@4.0.7: - resolution: {integrity: sha512-KZMphMeN8r4mRVczDwlmIfDAzFyfYdJyE4TWqx9OblybazMD8gwHSC7nZAidLZSLzwWEUrf8vnBWN4KcXqyM8w==} + nextra-theme-docs@4.2.17: + resolution: {integrity: sha512-QQ7iPHQ7zqh7dKJZ3SQbxqoFt7r8RHD9v7dFtJ+x8evEfxwM23oBGHNBqIjBoPl5uSwtvufEiVd7WMhK+Dxdww==} peerDependencies: next: '>=14' - nextra: 4.0.7 + nextra: 4.2.17 react: '>=18' react-dom: '>=18' - nextra@4.0.7: - resolution: {integrity: sha512-jf+jpJoAE//bS0xZn0tn7fAGascdyI4U/JBj0kiI53gUsdcXV3j5/Lw6n/XLoeoPM0ptfb5WbVP2wrKIwNX59g==} + nextra@4.2.17: + resolution: {integrity: sha512-WBZGSUeUJqkYm3F3F7+4N1oMP84r/YK/rAg96wkywu/MIsuUREY8fLXQgQbKkvcLbBl/7Wk2Iy+9xlhDu+weNg==} engines: {node: '>=18'} peerDependencies: next: '>=14' @@ -2416,8 +2483,8 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} - oniguruma-to-es@2.3.0: - resolution: {integrity: sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==} + oniguruma-to-es@3.1.1: + resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -2563,8 +2630,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-plugin-tailwindcss@0.6.11: - resolution: {integrity: sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==} + prettier-plugin-tailwindcss@0.6.13: + resolution: {integrity: sha512-uQ0asli1+ic8xrrSmIOaElDu0FacR4x69GynTh2oZjFY10JUt6EEumTQl5tB4fMeD6I1naKd+4rXQQ7esT2i1g==} engines: {node: '>=14.21.3'} peerDependencies: '@ianvs/prettier-plugin-sort-imports': '*' @@ -2655,8 +2722,8 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react@19.0.0: - resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -2685,14 +2752,14 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - regex-recursion@5.1.1: - resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} regex-utilities@2.3.0: resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - regex@5.1.1: - resolution: {integrity: sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==} + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} @@ -2704,11 +2771,11 @@ packages: rehype-parse@9.0.1: resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} - rehype-pretty-code@0.14.0: - resolution: {integrity: sha512-hBeKF/Wkkf3zyUS8lal9RCUuhypDWLQc+h9UrP9Pav25FUm/AQAVh4m5gdvJxh4Oz+U+xKvdsV01p1LdvsZTiQ==} + rehype-pretty-code@0.14.1: + resolution: {integrity: sha512-IpG4OL0iYlbx78muVldsK86hdfNoht0z63AP7sekQNW2QOTmjxB7RbTO+rhIYNGRljgHxgVZoPwUl6bIC9SbjA==} engines: {node: '>=18'} peerDependencies: - shiki: ^1.3.0 + shiki: ^1.0.0 || ^2.0.0 || ^3.0.0 rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} @@ -2821,6 +2888,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2833,8 +2905,8 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + sharp@0.34.2: + resolution: {integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: @@ -2845,8 +2917,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shiki@1.29.1: - resolution: {integrity: sha512-TghWKV9pJTd/N+IgAIVJtr0qZkB7FfFCUrrEJc0aRmZupo3D1OCVRknQWVRVA7AX/M0Ld7QfoAruPzr3CnUJuw==} + shiki@2.5.0: + resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==} side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} @@ -2875,8 +2947,8 @@ packages: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} - sonner@2.0.5: - resolution: {integrity: sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ==} + sonner@2.0.6: + resolution: {integrity: sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==} peerDependencies: react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc @@ -3343,7 +3415,7 @@ snapshots: dependencies: ansi-sequence-parser: 1.1.1 - '@emnapi/runtime@1.3.1': + '@emnapi/runtime@1.4.4': dependencies: tslib: 2.8.1 optional: true @@ -3367,7 +3439,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.2.0': + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 debug: 4.4.0 @@ -3399,34 +3471,34 @@ snapshots: '@floating-ui/core': 1.6.9 '@floating-ui/utils': 0.2.9 - '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': dependencies: '@floating-ui/dom': 1.6.13 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) - '@floating-ui/react@0.26.28(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@floating-ui/react@0.26.28(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.1.0))(react@19.1.0) '@floating-ui/utils': 0.2.9 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) tabbable: 6.2.0 '@floating-ui/utils@0.2.9': {} - '@formatjs/intl-localematcher@0.5.10': + '@formatjs/intl-localematcher@0.6.1': dependencies: tslib: 2.8.1 - '@headlessui/react@2.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@headlessui/react@2.2.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': dependencies: - '@floating-ui/react': 0.26.28(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@react-aria/focus': 3.19.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@react-aria/interactions': 3.23.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@tanstack/react-virtual': 3.11.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@floating-ui/react': 0.26.28(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@react-aria/focus': 3.19.1(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@react-aria/interactions': 3.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@tanstack/react-virtual': 3.11.2(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) '@humanfs/core@0.19.1': {} @@ -3456,79 +3528,85 @@ snapshots: transitivePeerDependencies: - supports-color - '@img/sharp-darwin-arm64@0.33.5': + '@img/sharp-darwin-arm64@0.34.2': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-arm64': 1.1.0 optional: true - '@img/sharp-darwin-x64@0.33.5': + '@img/sharp-darwin-x64@0.34.2': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.1.0 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.1.0': optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': + '@img/sharp-libvips-darwin-x64@1.1.0': optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': + '@img/sharp-libvips-linux-arm64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': + '@img/sharp-libvips-linux-arm@1.1.0': optional: true - '@img/sharp-libvips-linux-arm@1.0.5': + '@img/sharp-libvips-linux-ppc64@1.1.0': optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': + '@img/sharp-libvips-linux-s390x@1.1.0': optional: true - '@img/sharp-libvips-linux-x64@1.0.4': + '@img/sharp-libvips-linux-x64@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': + '@img/sharp-libvips-linuxmusl-x64@1.1.0': optional: true - '@img/sharp-linux-arm64@0.33.5': + '@img/sharp-linux-arm64@0.34.2': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-arm64': 1.1.0 optional: true - '@img/sharp-linux-arm@0.33.5': + '@img/sharp-linux-arm@0.34.2': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm': 1.1.0 optional: true - '@img/sharp-linux-s390x@0.33.5': + '@img/sharp-linux-s390x@0.34.2': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.1.0 optional: true - '@img/sharp-linux-x64@0.33.5': + '@img/sharp-linux-x64@0.34.2': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.1.0 optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': + '@img/sharp-linuxmusl-arm64@0.34.2': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 optional: true - '@img/sharp-linuxmusl-x64@0.33.5': + '@img/sharp-linuxmusl-x64@0.34.2': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 optional: true - '@img/sharp-wasm32@0.33.5': + '@img/sharp-wasm32@0.34.2': dependencies: - '@emnapi/runtime': 1.3.1 + '@emnapi/runtime': 1.4.4 optional: true - '@img/sharp-win32-ia32@0.33.5': + '@img/sharp-win32-arm64@0.34.2': optional: true - '@img/sharp-win32-x64@0.33.5': + '@img/sharp-win32-ia32@0.34.2': + optional: true + + '@img/sharp-win32-x64@0.34.2': optional: true '@isaacs/cliui@8.0.2': @@ -3650,34 +3728,34 @@ snapshots: '@napi-rs/simple-git-win32-arm64-msvc': 0.1.19 '@napi-rs/simple-git-win32-x64-msvc': 0.1.19 - '@next/env@15.1.6': {} + '@next/env@15.3.5': {} - '@next/eslint-plugin-next@15.1.6': + '@next/eslint-plugin-next@15.3.5': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@15.1.6': + '@next/swc-darwin-arm64@15.3.5': optional: true - '@next/swc-darwin-x64@15.1.6': + '@next/swc-darwin-x64@15.3.5': optional: true - '@next/swc-linux-arm64-gnu@15.1.6': + '@next/swc-linux-arm64-gnu@15.3.5': optional: true - '@next/swc-linux-arm64-musl@15.1.6': + '@next/swc-linux-arm64-musl@15.3.5': optional: true - '@next/swc-linux-x64-gnu@15.1.6': + '@next/swc-linux-x64-gnu@15.3.5': optional: true - '@next/swc-linux-x64-musl@15.1.6': + '@next/swc-linux-x64-musl@15.3.5': optional: true - '@next/swc-win32-arm64-msvc@15.1.6': + '@next/swc-win32-arm64-msvc@15.3.5': optional: true - '@next/swc-win32-x64-msvc@15.1.6': + '@next/swc-win32-x64-msvc@15.3.5': optional: true '@nodelib/fs.scandir@2.1.5': @@ -3694,10 +3772,11 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} - '@offlegacy/event-tracker@1.0.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@offlegacy/event-tracker@1.3.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(zod@3.24.1)': dependencies: - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) + zod: 3.24.1 '@pagefind/darwin-arm64@1.3.0': optional: true @@ -3717,96 +3796,96 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@react-aria/focus@3.19.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@react-aria/focus@3.19.1(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': dependencies: - '@react-aria/interactions': 3.23.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@react-aria/utils': 3.27.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@react-types/shared': 3.27.0(react@19.0.0) + '@react-aria/interactions': 3.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@react-aria/utils': 3.27.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@react-types/shared': 3.27.0(react@19.1.0) '@swc/helpers': 0.5.15 clsx: 2.1.1 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) - '@react-aria/interactions@3.23.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@react-aria/interactions@3.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': dependencies: - '@react-aria/ssr': 3.9.7(react@19.0.0) - '@react-aria/utils': 3.27.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@react-types/shared': 3.27.0(react@19.0.0) + '@react-aria/ssr': 3.9.7(react@19.1.0) + '@react-aria/utils': 3.27.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@react-types/shared': 3.27.0(react@19.1.0) '@swc/helpers': 0.5.15 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) - '@react-aria/ssr@3.9.7(react@19.0.0)': + '@react-aria/ssr@3.9.7(react@19.1.0)': dependencies: '@swc/helpers': 0.5.15 - react: 19.0.0 + react: 19.1.0 - '@react-aria/utils@3.27.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@react-aria/utils@3.27.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': dependencies: - '@react-aria/ssr': 3.9.7(react@19.0.0) - '@react-stately/utils': 3.10.5(react@19.0.0) - '@react-types/shared': 3.27.0(react@19.0.0) + '@react-aria/ssr': 3.9.7(react@19.1.0) + '@react-stately/utils': 3.10.5(react@19.1.0) + '@react-types/shared': 3.27.0(react@19.1.0) '@swc/helpers': 0.5.15 clsx: 2.1.1 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) - '@react-stately/utils@3.10.5(react@19.0.0)': + '@react-stately/utils@3.10.5(react@19.1.0)': dependencies: '@swc/helpers': 0.5.15 - react: 19.0.0 + react: 19.1.0 - '@react-types/shared@3.27.0(react@19.0.0)': + '@react-types/shared@3.27.0(react@19.1.0)': dependencies: - react: 19.0.0 + react: 19.1.0 '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.10.5': {} - '@shikijs/core@1.29.1': + '@shikijs/core@2.5.0': dependencies: - '@shikijs/engine-javascript': 1.29.1 - '@shikijs/engine-oniguruma': 1.29.1 - '@shikijs/types': 1.29.1 - '@shikijs/vscode-textmate': 10.0.1 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.4 - '@shikijs/engine-javascript@1.29.1': + '@shikijs/engine-javascript@2.5.0': dependencies: - '@shikijs/types': 1.29.1 - '@shikijs/vscode-textmate': 10.0.1 - oniguruma-to-es: 2.3.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 3.1.1 - '@shikijs/engine-oniguruma@1.29.1': + '@shikijs/engine-oniguruma@2.5.0': dependencies: - '@shikijs/types': 1.29.1 - '@shikijs/vscode-textmate': 10.0.1 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@1.29.1': + '@shikijs/langs@2.5.0': dependencies: - '@shikijs/types': 1.29.1 + '@shikijs/types': 2.5.0 - '@shikijs/themes@1.29.1': + '@shikijs/themes@2.5.0': dependencies: - '@shikijs/types': 1.29.1 + '@shikijs/types': 2.5.0 - '@shikijs/twoslash@1.29.1(typescript@5.7.3)': + '@shikijs/twoslash@2.5.0(typescript@5.7.3)': dependencies: - '@shikijs/core': 1.29.1 - '@shikijs/types': 1.29.1 + '@shikijs/core': 2.5.0 + '@shikijs/types': 2.5.0 twoslash: 0.2.12(typescript@5.7.3) transitivePeerDependencies: - supports-color - typescript - '@shikijs/types@1.29.1': + '@shikijs/types@2.5.0': dependencies: - '@shikijs/vscode-textmate': 10.0.1 + '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 - '@shikijs/vscode-textmate@10.0.1': {} + '@shikijs/vscode-textmate@10.0.2': {} '@swc/counter@0.1.3': {} @@ -3814,18 +3893,18 @@ snapshots: dependencies: tslib: 2.8.1 - '@tanstack/react-virtual@3.11.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@tanstack/react-virtual@3.11.2(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/virtual-core': 3.11.2 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) '@tanstack/virtual-core@3.11.2': {} - '@theguild/remark-mermaid@0.2.0(react@19.0.0)': + '@theguild/remark-mermaid@0.2.0(react@19.1.0)': dependencies: mermaid: 11.4.1 - react: 19.0.0 + react: 19.1.0 unist-util-visit: 5.0.0 transitivePeerDependencies: - supports-color @@ -3994,11 +4073,11 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/react-dom@19.0.3(@types/react@19.0.8)': + '@types/react-dom@19.0.3(@types/react@19.1.8)': dependencies: - '@types/react': 19.0.8 + '@types/react': 19.1.8 - '@types/react@19.0.8': + '@types/react@19.1.8': dependencies: csstype: 3.1.3 @@ -4093,6 +4172,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@uidotdev/usehooks@2.4.1(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': + dependencies: + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) + '@ungap/structured-clone@1.3.0': {} acorn-jsx@5.3.2(acorn@8.14.0): @@ -4217,10 +4301,10 @@ snapshots: balanced-match@1.0.2: {} - better-react-mathjax@2.0.3(react@19.0.0): + better-react-mathjax@2.0.3(react@19.1.0): dependencies: mathjax-full: 3.2.2 - react: 19.0.0 + react: 19.1.0 binary-extensions@2.3.0: {} @@ -4619,7 +4703,7 @@ snapshots: dequal@2.0.3: {} - detect-libc@2.0.3: + detect-libc@2.0.4: optional: true devlop@1.1.0: @@ -4777,9 +4861,9 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@15.1.6(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3): + eslint-config-next@15.3.5(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3): dependencies: - '@next/eslint-plugin-next': 15.1.6 + '@next/eslint-plugin-next': 15.3.5 '@rushstack/eslint-patch': 1.10.5 '@typescript-eslint/eslint-plugin': 8.21.0(@typescript-eslint/parser@8.21.0(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3))(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3) '@typescript-eslint/parser': 8.21.0(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3) @@ -4921,7 +5005,7 @@ snapshots: '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.19.1 '@eslint/core': 0.10.0 - '@eslint/eslintrc': 3.2.0 + '@eslint/eslintrc': 3.3.1 '@eslint/js': 9.18.0 '@eslint/plugin-kit': 0.2.5 '@humanfs/node': 0.16.6 @@ -5094,6 +5178,15 @@ snapshots: format@0.2.2: {} + framer-motion@12.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0): + dependencies: + motion-dom: 12.22.0 + motion-utils: 12.19.0 + tslib: 2.8.1 + optionalDependencies: + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) + fsevents@2.3.3: optional: true @@ -5627,6 +5720,10 @@ snapshots: lru-cache@10.4.3: {} + lucide-react@0.525.0(react@19.1.0): + dependencies: + react: 19.1.0 + markdown-extensions@2.0.0: {} markdown-table@3.0.4: {} @@ -6170,6 +6267,20 @@ snapshots: pkg-types: 1.3.1 ufo: 1.5.4 + motion-dom@12.22.0: + dependencies: + motion-utils: 12.19.0 + + motion-utils@12.19.0: {} + + motion@12.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0): + dependencies: + framer-motion: 12.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + tslib: 2.8.1 + optionalDependencies: + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) + ms@2.1.3: {} mz@2.7.0: @@ -6184,65 +6295,65 @@ snapshots: negotiator@1.0.0: {} - next-themes@0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next-themes@0.4.4(react-dom@19.0.0(react@19.1.0))(react@19.1.0): dependencies: - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) - next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0): dependencies: - '@next/env': 15.1.6 + '@next/env': 15.3.5 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 caniuse-lite: 1.0.30001695 postcss: 8.4.31 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - styled-jsx: 5.1.6(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.1.6 - '@next/swc-darwin-x64': 15.1.6 - '@next/swc-linux-arm64-gnu': 15.1.6 - '@next/swc-linux-arm64-musl': 15.1.6 - '@next/swc-linux-x64-gnu': 15.1.6 - '@next/swc-linux-x64-musl': 15.1.6 - '@next/swc-win32-arm64-msvc': 15.1.6 - '@next/swc-win32-x64-msvc': 15.1.6 - sharp: 0.33.5 + '@next/swc-darwin-arm64': 15.3.5 + '@next/swc-darwin-x64': 15.3.5 + '@next/swc-linux-arm64-gnu': 15.3.5 + '@next/swc-linux-arm64-musl': 15.3.5 + '@next/swc-linux-x64-gnu': 15.3.5 + '@next/swc-linux-x64-musl': 15.3.5 + '@next/swc-win32-arm64-msvc': 15.3.5 + '@next/swc-win32-x64-msvc': 15.3.5 + sharp: 0.34.2 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@4.0.7(@types/react@19.0.8)(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(nextra@4.0.7(acorn@8.14.0)(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + nextra-theme-docs@4.2.17(@types/react@19.1.8)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(nextra@4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3))(react-dom@19.0.0(react@19.1.0))(react@19.1.0): dependencies: - '@headlessui/react': 2.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@headlessui/react': 2.2.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) clsx: 2.1.1 - next: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - next-themes: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - nextra: 4.0.7(acorn@8.14.0)(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) - react: 19.0.0 - react-compiler-runtime: 0.0.0-experimental-22c6e49-20241219(react@19.0.0) - react-dom: 19.0.0(react@19.0.0) + next: 15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + next-themes: 0.4.4(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + nextra: 4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3) + react: 19.1.0 + react-compiler-runtime: 0.0.0-experimental-22c6e49-20241219(react@19.1.0) + react-dom: 19.0.0(react@19.1.0) scroll-into-view-if-needed: 3.1.0 zod: 3.24.1 zod-validation-error: 3.4.0(zod@3.24.1) - zustand: 5.0.3(@types/react@19.0.8)(react@19.0.0) + zustand: 5.0.3(@types/react@19.1.8)(react@19.1.0) transitivePeerDependencies: - '@types/react' - immer - use-sync-external-store - nextra@4.0.7(acorn@8.14.0)(next@15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3): + nextra@4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3): dependencies: - '@formatjs/intl-localematcher': 0.5.10 - '@headlessui/react': 2.2.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@formatjs/intl-localematcher': 0.6.1 + '@headlessui/react': 2.2.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) '@mdx-js/mdx': 3.1.0(acorn@8.14.0) '@napi-rs/simple-git': 0.1.19 - '@shikijs/twoslash': 1.29.1(typescript@5.7.3) - '@theguild/remark-mermaid': 0.2.0(react@19.0.0) + '@shikijs/twoslash': 2.5.0(typescript@5.7.3) + '@theguild/remark-mermaid': 0.2.0(react@19.1.0) '@theguild/remark-npm2yarn': 0.3.3 - better-react-mathjax: 2.0.3(react@19.0.0) + better-react-mathjax: 2.0.3(react@19.1.0) clsx: 2.1.1 estree-util-to-js: 2.0.0 estree-util-value-to-estree: 3.2.1 @@ -6254,24 +6365,25 @@ snapshots: mdast-util-gfm: 3.0.0 mdast-util-to-hast: 13.2.0 negotiator: 1.0.0 - next: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-compiler-runtime: 0.0.0-experimental-22c6e49-20241219(react@19.0.0) - react-dom: 19.0.0(react@19.0.0) - react-medium-image-zoom: 5.2.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next: 15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-compiler-runtime: 0.0.0-experimental-22c6e49-20241219(react@19.1.0) + react-dom: 19.0.0(react@19.1.0) + react-medium-image-zoom: 5.2.13(react-dom@19.0.0(react@19.1.0))(react@19.1.0) rehype-katex: 7.0.1 - rehype-pretty-code: 0.14.0(shiki@1.29.1) + rehype-pretty-code: 0.14.1(shiki@2.5.0) rehype-raw: 7.0.0 remark-frontmatter: 5.0.0 remark-gfm: 4.0.0 remark-math: 6.0.0 remark-reading-time: 2.0.1 remark-smartypants: 3.0.2 - shiki: 1.29.1 + shiki: 2.5.0 slash: 5.1.0 title: 4.0.1 unist-util-remove: 4.0.0 unist-util-visit: 5.0.0 + unist-util-visit-children: 3.0.0 yaml: 2.7.0 zod: 3.24.1 zod-validation-error: 3.4.0(zod@3.24.1) @@ -6339,11 +6451,11 @@ snapshots: dependencies: mimic-fn: 4.0.0 - oniguruma-to-es@2.3.0: + oniguruma-to-es@3.1.1: dependencies: emoji-regex-xs: 1.0.0 - regex: 5.1.1 - regex-recursion: 5.1.1 + regex: 6.0.1 + regex-recursion: 6.0.2 optionator@0.9.4: dependencies: @@ -6494,7 +6606,7 @@ snapshots: prelude-ls@1.2.1: {} - prettier-plugin-tailwindcss@0.6.11(prettier@3.4.2): + prettier-plugin-tailwindcss@0.6.13(prettier@3.4.2): dependencies: prettier: 3.4.2 @@ -6512,23 +6624,23 @@ snapshots: queue-microtask@1.2.3: {} - react-compiler-runtime@0.0.0-experimental-22c6e49-20241219(react@19.0.0): + react-compiler-runtime@0.0.0-experimental-22c6e49-20241219(react@19.1.0): dependencies: - react: 19.0.0 + react: 19.1.0 - react-dom@19.0.0(react@19.0.0): + react-dom@19.0.0(react@19.1.0): dependencies: - react: 19.0.0 + react: 19.1.0 scheduler: 0.25.0 react-is@16.13.1: {} - react-medium-image-zoom@5.2.13(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + react-medium-image-zoom@5.2.13(react-dom@19.0.0(react@19.1.0))(react@19.1.0): dependencies: - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) - react@19.0.0: {} + react@19.1.0: {} read-cache@1.0.0: dependencies: @@ -6581,14 +6693,13 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 - regex-recursion@5.1.1: + regex-recursion@6.0.2: dependencies: - regex: 5.1.1 regex-utilities: 2.3.0 regex-utilities@2.3.0: {} - regex@5.1.1: + regex@6.0.1: dependencies: regex-utilities: 2.3.0 @@ -6617,13 +6728,13 @@ snapshots: hast-util-from-html: 2.0.3 unified: 11.0.5 - rehype-pretty-code@0.14.0(shiki@1.29.1): + rehype-pretty-code@0.14.1(shiki@2.5.0): dependencies: '@types/hast': 3.0.4 hast-util-to-string: 3.0.1 parse-numeric-range: 1.3.0 rehype-parse: 9.0.1 - shiki: 1.29.1 + shiki: 2.5.0 unified: 11.0.5 unist-util-visit: 5.0.0 @@ -6807,6 +6918,9 @@ snapshots: semver@7.6.3: {} + semver@7.7.2: + optional: true + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -6829,31 +6943,33 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - sharp@0.33.5: + sharp@0.34.2: dependencies: color: 4.2.3 - detect-libc: 2.0.3 - semver: 7.6.3 + detect-libc: 2.0.4 + semver: 7.7.2 optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 + '@img/sharp-darwin-arm64': 0.34.2 + '@img/sharp-darwin-x64': 0.34.2 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.2 + '@img/sharp-linux-arm64': 0.34.2 + '@img/sharp-linux-s390x': 0.34.2 + '@img/sharp-linux-x64': 0.34.2 + '@img/sharp-linuxmusl-arm64': 0.34.2 + '@img/sharp-linuxmusl-x64': 0.34.2 + '@img/sharp-wasm32': 0.34.2 + '@img/sharp-win32-arm64': 0.34.2 + '@img/sharp-win32-ia32': 0.34.2 + '@img/sharp-win32-x64': 0.34.2 optional: true shebang-command@2.0.0: @@ -6862,15 +6978,15 @@ snapshots: shebang-regex@3.0.0: {} - shiki@1.29.1: + shiki@2.5.0: dependencies: - '@shikijs/core': 1.29.1 - '@shikijs/engine-javascript': 1.29.1 - '@shikijs/engine-oniguruma': 1.29.1 - '@shikijs/langs': 1.29.1 - '@shikijs/themes': 1.29.1 - '@shikijs/types': 1.29.1 - '@shikijs/vscode-textmate': 10.0.1 + '@shikijs/core': 2.5.0 + '@shikijs/engine-javascript': 2.5.0 + '@shikijs/engine-oniguruma': 2.5.0 + '@shikijs/langs': 2.5.0 + '@shikijs/themes': 2.5.0 + '@shikijs/types': 2.5.0 + '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 side-channel-list@1.0.0: @@ -6910,10 +7026,10 @@ snapshots: slash@5.1.0: {} - sonner@2.0.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + sonner@2.0.6(react-dom@19.0.0(react@19.1.0))(react@19.1.0): dependencies: - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.1.0 + react-dom: 19.0.0(react@19.1.0) source-map-js@1.2.1: {} @@ -7016,10 +7132,10 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-jsx@5.1.6(react@19.0.0): + styled-jsx@5.1.6(react@19.1.0): dependencies: client-only: 0.0.1 - react: 19.0.0 + react: 19.1.0 stylis@4.3.5: {} @@ -7364,9 +7480,9 @@ snapshots: zod@3.24.1: {} - zustand@5.0.3(@types/react@19.0.8)(react@19.0.0): + zustand@5.0.3(@types/react@19.1.8)(react@19.1.0): optionalDependencies: - '@types/react': 19.0.8 - react: 19.0.0 + '@types/react': 19.1.8 + react: 19.1.0 zwitch@2.0.4: {} From 59b2c35d4aaf75f332c27d6dd21c97b7aef0aab9 Mon Sep 17 00:00:00 2001 From: gwansikk Date: Wed, 9 Jul 2025 01:33:30 +0900 Subject: [PATCH 13/24] feat(docs): new documentation --- docs/README.md | 36 ---- docs/next.config.ts | 3 +- docs/package.json | 7 +- docs/pnpm-lock.yaml | 147 +++++++------- .../[lang]/{docs => }/[[...mdxPath]]/page.tsx | 4 +- docs/src/app/[lang]/_meta.ts | 15 -- docs/src/app/[lang]/page.tsx | 187 ------------------ docs/src/components/home-page.tsx | 85 ++++++++ docs/src/content/en/_meta.tsx | 16 ++ docs/src/content/en/{ => docs}/_meta.ts | 12 +- .../content/en/{ => docs}/basic-example.mdx | 0 docs/src/content/en/{ => docs}/batching.mdx | 0 .../content/en/{ => docs}/components/_meta.ts | 0 .../en/{ => docs}/components/click.mdx | 0 .../en/{ => docs}/components/dom-event.mdx | 0 .../en/{ => docs}/components/impression.mdx | 0 .../en/{ => docs}/components/index.mdx | 0 .../en/{ => docs}/components/page-view.mdx | 0 .../en/{ => docs}/components/set-context.mdx | 0 .../content/en/{ => docs}/create-tracker.mdx | 0 .../en/{ => docs}/data-type-validation.mdx | 0 .../content/en/{ => docs}/installation.mdx | 0 docs/src/content/en/docs/introduction.mdx | 89 +++++++++ .../src/content/en/{ => docs}/use-tracker.mdx | 0 .../en/{ => docs}/why-event-tracker.mdx | 0 docs/src/content/en/index.mdx | 129 ++++-------- docs/src/content/ko/_meta.tsx | 16 ++ docs/src/content/ko/{ => docs}/_meta.ts | 11 +- .../content/ko/{ => docs}/basic-example.mdx | 0 docs/src/content/ko/{ => docs}/batching.mdx | 0 .../content/ko/{ => docs}/components/_meta.ts | 0 .../ko/{ => docs}/components/click.mdx | 0 .../ko/{ => docs}/components/dom-event.mdx | 12 +- .../ko/{ => docs}/components/impression.mdx | 0 .../ko/{ => docs}/components/index.mdx | 0 .../ko/{ => docs}/components/page-view.mdx | 0 .../ko/{ => docs}/components/set-context.mdx | 0 .../content/ko/{ => docs}/create-tracker.mdx | 0 .../ko/{ => docs}/data-type-validation.mdx | 0 .../content/ko/{ => docs}/installation.mdx | 0 docs/src/content/ko/docs/introduction.mdx | 89 +++++++++ .../src/content/ko/{ => docs}/use-tracker.mdx | 0 .../ko/{ => docs}/why-event-tracker.mdx | 0 docs/src/content/ko/index.mdx | 129 ++++-------- docs/{ => src}/mdx-components.ts | 2 +- docs/src/middleware.ts | 12 +- package.json | 1 + 47 files changed, 480 insertions(+), 522 deletions(-) delete mode 100644 docs/README.md rename docs/src/app/[lang]/{docs => }/[[...mdxPath]]/page.tsx (90%) delete mode 100644 docs/src/app/[lang]/_meta.ts delete mode 100644 docs/src/app/[lang]/page.tsx create mode 100644 docs/src/components/home-page.tsx create mode 100644 docs/src/content/en/_meta.tsx rename docs/src/content/en/{ => docs}/_meta.ts (89%) rename docs/src/content/en/{ => docs}/basic-example.mdx (100%) rename docs/src/content/en/{ => docs}/batching.mdx (100%) rename docs/src/content/en/{ => docs}/components/_meta.ts (100%) rename docs/src/content/en/{ => docs}/components/click.mdx (100%) rename docs/src/content/en/{ => docs}/components/dom-event.mdx (100%) rename docs/src/content/en/{ => docs}/components/impression.mdx (100%) rename docs/src/content/en/{ => docs}/components/index.mdx (100%) rename docs/src/content/en/{ => docs}/components/page-view.mdx (100%) rename docs/src/content/en/{ => docs}/components/set-context.mdx (100%) rename docs/src/content/en/{ => docs}/create-tracker.mdx (100%) rename docs/src/content/en/{ => docs}/data-type-validation.mdx (100%) rename docs/src/content/en/{ => docs}/installation.mdx (100%) create mode 100644 docs/src/content/en/docs/introduction.mdx rename docs/src/content/en/{ => docs}/use-tracker.mdx (100%) rename docs/src/content/en/{ => docs}/why-event-tracker.mdx (100%) create mode 100644 docs/src/content/ko/_meta.tsx rename docs/src/content/ko/{ => docs}/_meta.ts (90%) rename docs/src/content/ko/{ => docs}/basic-example.mdx (100%) rename docs/src/content/ko/{ => docs}/batching.mdx (100%) rename docs/src/content/ko/{ => docs}/components/_meta.ts (100%) rename docs/src/content/ko/{ => docs}/components/click.mdx (100%) rename docs/src/content/ko/{ => docs}/components/dom-event.mdx (92%) rename docs/src/content/ko/{ => docs}/components/impression.mdx (100%) rename docs/src/content/ko/{ => docs}/components/index.mdx (100%) rename docs/src/content/ko/{ => docs}/components/page-view.mdx (100%) rename docs/src/content/ko/{ => docs}/components/set-context.mdx (100%) rename docs/src/content/ko/{ => docs}/create-tracker.mdx (100%) rename docs/src/content/ko/{ => docs}/data-type-validation.mdx (100%) rename docs/src/content/ko/{ => docs}/installation.mdx (100%) create mode 100644 docs/src/content/ko/docs/introduction.mdx rename docs/src/content/ko/{ => docs}/use-tracker.mdx (100%) rename docs/src/content/ko/{ => docs}/why-event-tracker.mdx (100%) rename docs/{ => src}/mdx-components.ts (80%) diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index e215bc4..0000000 --- a/docs/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/docs/next.config.ts b/docs/next.config.ts index ce2bbac..c453c5e 100644 --- a/docs/next.config.ts +++ b/docs/next.config.ts @@ -6,10 +6,11 @@ const withNextra = nextra({ search: { codeblocks: false, }, - contentDirBasePath: "/docs", + readingTime: true, }); const nextConfig: NextConfig = { + reactStrictMode: true, i18n: { locales: ["en", "ko"], defaultLocale: "en", diff --git a/docs/package.json b/docs/package.json index d454c2d..963f1e0 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "next dev --turbopack", + "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint", @@ -21,14 +21,13 @@ "nextra": "^4.2.17", "nextra-theme-docs": "^4.2.17", "react": "^19.1.0", - "react-dom": "^19.0.0", + "react-dom": "^19.1.0", "sonner": "^2.0.6" }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", "@types/node": "^20", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.0.0", + "@types/react-dom": "^19.1.6", "eslint": "^9", "eslint-config-next": "15.3.5", "pagefind": "^1.3.0", diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index f419721..3e7d84b 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: version: 2.11.11 '@offlegacy/event-tracker': specifier: latest - version: 1.3.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(zod@3.24.1) + version: 1.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@3.24.1) '@uidotdev/usehooks': specifier: ^2.4.1 - version: 2.4.1(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + version: 2.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) codehike: specifier: ^1.0.7 version: 1.0.7 @@ -25,25 +25,25 @@ importers: version: 0.525.0(react@19.1.0) motion: specifier: ^12.23.0 - version: 12.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + version: 12.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next: specifier: 15.3.5 - version: 15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + version: 15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) nextra: specifier: ^4.2.17 - version: 4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3) + version: 4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3) nextra-theme-docs: specifier: ^4.2.17 - version: 4.2.17(@types/react@19.1.8)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(nextra@4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3))(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + version: 4.2.17(@types/react@19.1.8)(next@15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(nextra@4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: specifier: ^19.1.0 version: 19.1.0 react-dom: - specifier: ^19.0.0 - version: 19.0.0(react@19.1.0) + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) sonner: specifier: ^2.0.6 - version: 2.0.6(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + version: 2.0.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) devDependencies: '@eslint/eslintrc': specifier: ^3.3.1 @@ -51,12 +51,9 @@ importers: '@types/node': specifier: ^20 version: 20.17.16 - '@types/react': - specifier: ^19.1.8 - version: 19.1.8 '@types/react-dom': - specifier: ^19.0.0 - version: 19.0.3(@types/react@19.1.8) + specifier: ^19.1.6 + version: 19.1.6(@types/react@19.1.8) eslint: specifier: ^9 version: 9.18.0(jiti@1.21.7) @@ -793,8 +790,8 @@ packages: '@types/node@20.17.16': resolution: {integrity: sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==} - '@types/react-dom@19.0.3': - resolution: {integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==} + '@types/react-dom@19.1.6': + resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} peerDependencies: '@types/react': ^19.0.0 @@ -2708,10 +2705,10 @@ packages: peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom@19.0.0: - resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: - react: ^19.0.0 + react: ^19.1.0 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -2873,8 +2870,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scheduler@0.25.0: - resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} scroll-into-view-if-needed@3.1.0: resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} @@ -3471,18 +3468,18 @@ snapshots: '@floating-ui/core': 1.6.9 '@floating-ui/utils': 0.2.9 - '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': + '@floating-ui/react-dom@2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@floating-ui/dom': 1.6.13 react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) - '@floating-ui/react@0.26.28(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': + '@floating-ui/react@0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@floating-ui/utils': 0.2.9 react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) tabbable: 6.2.0 '@floating-ui/utils@0.2.9': {} @@ -3491,14 +3488,14 @@ snapshots: dependencies: tslib: 2.8.1 - '@headlessui/react@2.2.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': + '@headlessui/react@2.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@floating-ui/react': 0.26.28(react-dom@19.0.0(react@19.1.0))(react@19.1.0) - '@react-aria/focus': 3.19.1(react-dom@19.0.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) - '@tanstack/react-virtual': 3.11.2(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@floating-ui/react': 0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/focus': 3.19.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/interactions': 3.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/react-virtual': 3.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) '@humanfs/core@0.19.1': {} @@ -3772,10 +3769,10 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} - '@offlegacy/event-tracker@1.3.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(zod@3.24.1)': + '@offlegacy/event-tracker@1.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@3.24.1)': dependencies: react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) zod: 3.24.1 '@pagefind/darwin-arm64@1.3.0': @@ -3796,31 +3793,31 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@react-aria/focus@3.19.1(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': + '@react-aria/focus@3.19.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - '@react-aria/interactions': 3.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.27.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@react-aria/interactions': 3.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@react-aria/utils': 3.27.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@react-types/shared': 3.27.0(react@19.1.0) '@swc/helpers': 0.5.15 clsx: 2.1.1 react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) - '@react-aria/interactions@3.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': + '@react-aria/interactions@3.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@react-aria/ssr': 3.9.7(react@19.1.0) - '@react-aria/utils': 3.27.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@react-aria/utils': 3.27.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@react-types/shared': 3.27.0(react@19.1.0) '@swc/helpers': 0.5.15 react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) '@react-aria/ssr@3.9.7(react@19.1.0)': dependencies: '@swc/helpers': 0.5.15 react: 19.1.0 - '@react-aria/utils@3.27.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': + '@react-aria/utils@3.27.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@react-aria/ssr': 3.9.7(react@19.1.0) '@react-stately/utils': 3.10.5(react@19.1.0) @@ -3828,7 +3825,7 @@ snapshots: '@swc/helpers': 0.5.15 clsx: 2.1.1 react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) '@react-stately/utils@3.10.5(react@19.1.0)': dependencies: @@ -3893,11 +3890,11 @@ snapshots: dependencies: tslib: 2.8.1 - '@tanstack/react-virtual@3.11.2(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': + '@tanstack/react-virtual@3.11.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@tanstack/virtual-core': 3.11.2 react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) '@tanstack/virtual-core@3.11.2': {} @@ -4073,7 +4070,7 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/react-dom@19.0.3(@types/react@19.1.8)': + '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: '@types/react': 19.1.8 @@ -4172,10 +4169,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@uidotdev/usehooks@2.4.1(react-dom@19.0.0(react@19.1.0))(react@19.1.0)': + '@uidotdev/usehooks@2.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) '@ungap/structured-clone@1.3.0': {} @@ -5178,14 +5175,14 @@ snapshots: format@0.2.2: {} - framer-motion@12.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0): + framer-motion@12.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: motion-dom: 12.22.0 motion-utils: 12.19.0 tslib: 2.8.1 optionalDependencies: react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) fsevents@2.3.3: optional: true @@ -6273,13 +6270,13 @@ snapshots: motion-utils@12.19.0: {} - motion@12.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0): + motion@12.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - framer-motion: 12.23.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + framer-motion: 12.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) tslib: 2.8.1 optionalDependencies: react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) ms@2.1.3: {} @@ -6295,12 +6292,12 @@ snapshots: negotiator@1.0.0: {} - next-themes@0.4.4(react-dom@19.0.0(react@19.1.0))(react@19.1.0): + next-themes@0.4.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) - next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0): + next@15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 15.3.5 '@swc/counter': 0.1.3 @@ -6309,7 +6306,7 @@ snapshots: caniuse-lite: 1.0.30001695 postcss: 8.4.31 react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) styled-jsx: 5.1.6(react@19.1.0) optionalDependencies: '@next/swc-darwin-arm64': 15.3.5 @@ -6325,16 +6322,16 @@ snapshots: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@4.2.17(@types/react@19.1.8)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(nextra@4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3))(react-dom@19.0.0(react@19.1.0))(react@19.1.0): + nextra-theme-docs@4.2.17(@types/react@19.1.8)(next@15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(nextra@4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@headlessui/react': 2.2.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@headlessui/react': 2.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) clsx: 2.1.1 - next: 15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0) - next-themes: 0.4.4(react-dom@19.0.0(react@19.1.0))(react@19.1.0) - nextra: 4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3) + next: 15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next-themes: 0.4.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + nextra: 4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3) react: 19.1.0 react-compiler-runtime: 0.0.0-experimental-22c6e49-20241219(react@19.1.0) - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) scroll-into-view-if-needed: 3.1.0 zod: 3.24.1 zod-validation-error: 3.4.0(zod@3.24.1) @@ -6344,10 +6341,10 @@ snapshots: - immer - use-sync-external-store - nextra@4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0))(react-dom@19.0.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3): + nextra@4.2.17(acorn@8.14.0)(next@15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.7.3): dependencies: '@formatjs/intl-localematcher': 0.6.1 - '@headlessui/react': 2.2.0(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + '@headlessui/react': 2.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@mdx-js/mdx': 3.1.0(acorn@8.14.0) '@napi-rs/simple-git': 0.1.19 '@shikijs/twoslash': 2.5.0(typescript@5.7.3) @@ -6365,11 +6362,11 @@ snapshots: mdast-util-gfm: 3.0.0 mdast-util-to-hast: 13.2.0 negotiator: 1.0.0 - next: 15.3.5(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + next: 15.3.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-compiler-runtime: 0.0.0-experimental-22c6e49-20241219(react@19.1.0) - react-dom: 19.0.0(react@19.1.0) - react-medium-image-zoom: 5.2.13(react-dom@19.0.0(react@19.1.0))(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) + react-medium-image-zoom: 5.2.13(react-dom@19.1.0(react@19.1.0))(react@19.1.0) rehype-katex: 7.0.1 rehype-pretty-code: 0.14.1(shiki@2.5.0) rehype-raw: 7.0.0 @@ -6628,17 +6625,17 @@ snapshots: dependencies: react: 19.1.0 - react-dom@19.0.0(react@19.1.0): + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 - scheduler: 0.25.0 + scheduler: 0.26.0 react-is@16.13.1: {} - react-medium-image-zoom@5.2.13(react-dom@19.0.0(react@19.1.0))(react@19.1.0): + react-medium-image-zoom@5.2.13(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) react@19.1.0: {} @@ -6908,7 +6905,7 @@ snapshots: safer-buffer@2.1.2: {} - scheduler@0.25.0: {} + scheduler@0.26.0: {} scroll-into-view-if-needed@3.1.0: dependencies: @@ -7026,10 +7023,10 @@ snapshots: slash@5.1.0: {} - sonner@2.0.6(react-dom@19.0.0(react@19.1.0))(react@19.1.0): + sonner@2.0.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 - react-dom: 19.0.0(react@19.1.0) + react-dom: 19.1.0(react@19.1.0) source-map-js@1.2.1: {} diff --git a/docs/src/app/[lang]/docs/[[...mdxPath]]/page.tsx b/docs/src/app/[lang]/[[...mdxPath]]/page.tsx similarity index 90% rename from docs/src/app/[lang]/docs/[[...mdxPath]]/page.tsx rename to docs/src/app/[lang]/[[...mdxPath]]/page.tsx index 1420818..37e0e99 100644 --- a/docs/src/app/[lang]/docs/[[...mdxPath]]/page.tsx +++ b/docs/src/app/[lang]/[[...mdxPath]]/page.tsx @@ -1,9 +1,9 @@ import { generateStaticParamsFor, importPage } from "nextra/pages"; -import { useMDXComponents } from "../../../../../mdx-components"; +import { useMDXComponents } from "@/mdx-components"; import { TrackPageView } from "@/tracker"; -import { Lang } from "@/lib/types/lang"; +import type { Lang } from "@/lib/types/lang"; export const generateStaticParams = generateStaticParamsFor("mdxPath"); diff --git a/docs/src/app/[lang]/_meta.ts b/docs/src/app/[lang]/_meta.ts deleted file mode 100644 index c50c667..0000000 --- a/docs/src/app/[lang]/_meta.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { MetaRecord } from "nextra"; - -const meta: MetaRecord = { - index: { - title: "Home", - type: "page", - display: "hidden", - }, - docs: { - title: "Documentation", - type: "page", - }, -}; - -export default meta; diff --git a/docs/src/app/[lang]/page.tsx b/docs/src/app/[lang]/page.tsx deleted file mode 100644 index ca07957..0000000 --- a/docs/src/app/[lang]/page.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import Link from "next/link"; - -import { Logo } from "@/logo"; -import { TrackClick, TrackPageView } from "@/tracker"; -import { importPage } from "nextra/pages"; -import type { Lang } from "@/lib/types/lang"; -import { DemoCode } from "@/components/demo-playground"; -import Image from "next/image"; -import { InstallCopyButton } from "@/components/install-copy-button"; - -export const metadata = {}; - -const TEXT_MAP = { - description: { - ko: "React 애플리케이션을 위한 완전한 이벤트 트래킹 시스템", - en: "Comprehensive solution for event tracking in React applications", - }, - get_started: { - ko: "시작하기", - en: "Get Started", - }, - featureCardsTitle: { - ko: "Event Tracker가 제시하는 새로운 이벤트 트래킹 패러다임", - en: "New Paradigm of Event Tracking Offered by Event Tracker", - }, - featureCards: [ - { - ko: { - title: "선언적 API", - description: "트래킹을 위한 별도의 설정이나 복잡한 코드 없이, 컴포넌트 형태로 직관적으로 선언할 수 있습니다.", - }, - en: { - title: "Declarative API", - description: "Track events intuitively through component-based declarations, without boilerplate.", - }, - }, - { - ko: { - title: "강력한 데이터 타입 검증", - description: - "Zod 기반의 정적 스키마 검증으로 런타임 이전에 오류를 방지하고, 수집되는 이벤트 데이터의 신뢰도를 보장합니다.", - }, - en: { - title: "Powerful Data Type Validation", - description: - "Schema-based validation with Zod catches issues before runtime and guarantees the integrity of your event data.", - }, - }, - { - ko: { - title: "최적화된 성능", - description: - "배칭, 디바운스, 스로틀링 등 네트워크 요청을 최소화하는 다양한 전략이 내장되어 있어, 성능 저하 없이 안정적으로 트래킹할 수 있습니다.", - }, - en: { - title: "Optimized Performance", - description: - "Built-in strategies like batching, debouncing, and throttling minimize network overhead, enabling efficient and reliable tracking.", - }, - }, - { - ko: { - title: "실행 순서 보장", - description: - "비동기 상황에서도 이벤트가 의도한 순서대로 처리되도록 설계되어, 복잡한 사용자 흐름에서도 정확한 트래킹이 가능합니다.", - }, - en: { - title: "Guaranteed Execution Order", - description: - "Ensures that events are processed in the intended order, even under asynchronous conditions—crucial for accurate tracking in complex flows.", - }, - }, - { - ko: { - title: "애널리틱스 도구 독립성", - description: - "Google Analytics, Amplitude, Segment 등 어떤 도구든 자유롭게 연동할 수 있어, 기존 인프라를 변경하지 않고도 도입이 가능합니다.", - }, - en: { - title: "Analytics Tool Independence", - description: - "Easily integrates with tools like Google Analytics, Amplitude, or Segment—without forcing you to switch or couple to a specific vendor.", - }, - }, - { - ko: { - title: "명확한 관심사 분리", - description: - "트래킹 로직을 비즈니스 코드에서 완전히 분리함으로써 코드의 가독성, 테스트 용이성, 유지보수성을 모두 향상시킵니다.", - }, - en: { - title: "Clear Separation of Concerns", - description: - "Keeps tracking logic out of your business logic, resulting in cleaner code that’s easier to test, maintain, and evolve.", - }, - }, - ], -} as const; - -export default async function Page(props: { params: Promise<{ mdxPath: string[]; lang: Lang }> }) { - const { mdxPath, lang } = await props.params; - await importPage(mdxPath, lang); - - return ( -
- -
-
- -
-

Event Tracker

-

{TEXT_MAP.description[lang]}

-
-
- - - {TEXT_MAP.get_started[lang]} - - - -
-
-
- -
-
-
-

{TEXT_MAP.featureCardsTitle[lang]}

-
- - - - - - -
-
-
-

Driven by the Community

- - Contributors - -
-
- ); -} - -function FeatureCard({ title, description }: { title: string; description: string }) { - return ( -
-

{title}

-

{description}

-
- ); -} diff --git a/docs/src/components/home-page.tsx b/docs/src/components/home-page.tsx new file mode 100644 index 0000000..3f6bbad --- /dev/null +++ b/docs/src/components/home-page.tsx @@ -0,0 +1,85 @@ +import Link from "next/link"; + +import { Logo } from "@/logo"; +import { TrackClick, TrackPageView } from "@/tracker"; +import { DemoCode } from "@/components/demo-playground"; +import Image from "next/image"; +import { InstallCopyButton } from "@/components/install-copy-button"; + +type FeatureCardText = { + title: string; + description: string; +}; + +type Props = { + description: string; + get_started: string; + featureCardsTitle: string; + featureCards: FeatureCardText[]; +}; + +export default async function HomePage(props: Props) { + const { description, get_started, featureCardsTitle, featureCards } = props; + return ( +
+ +
+
+ +
+

Event Tracker

+

{description}

+
+
+ + + {get_started} + + + +
+
+
+ +
+
+
+

{featureCardsTitle}

+
+ {featureCards.map((card) => ( + + ))} +
+
+
+

Driven by the Community

+ + Contributors + +
+
+ ); +} + +function FeatureCard({ title, description }: { title: string; description: string }) { + return ( +
+

{title}

+

{description}

+
+ ); +} diff --git a/docs/src/content/en/_meta.tsx b/docs/src/content/en/_meta.tsx new file mode 100644 index 0000000..43b6655 --- /dev/null +++ b/docs/src/content/en/_meta.tsx @@ -0,0 +1,16 @@ +import type { MetaRecord } from "nextra"; + +export default { + index: { + type: "page", + display: "hidden", + theme: { + layout: "full", + toc: false, + }, + }, + docs: { + type: "page", + title: "Docs", + }, +} satisfies MetaRecord; diff --git a/docs/src/content/en/_meta.ts b/docs/src/content/en/docs/_meta.ts similarity index 89% rename from docs/src/content/en/_meta.ts rename to docs/src/content/en/docs/_meta.ts index 1c36a1e..52a1f1e 100644 --- a/docs/src/content/en/_meta.ts +++ b/docs/src/content/en/docs/_meta.ts @@ -1,11 +1,11 @@ -import { MetaRecord } from "nextra"; +import type { MetaRecord } from "nextra"; export default { "getting-started-separator": { type: "separator", title: "Getting Started", }, - index: { + introduction: { title: "Introduction", theme: { toc: true, @@ -37,13 +37,7 @@ export default { layout: "default", }, }, - components: { - title: "Components", - theme: { - toc: true, - layout: "default", - }, - }, + components: { title: "Components" }, "use-tracker": { title: "useTracker", theme: { diff --git a/docs/src/content/en/basic-example.mdx b/docs/src/content/en/docs/basic-example.mdx similarity index 100% rename from docs/src/content/en/basic-example.mdx rename to docs/src/content/en/docs/basic-example.mdx diff --git a/docs/src/content/en/batching.mdx b/docs/src/content/en/docs/batching.mdx similarity index 100% rename from docs/src/content/en/batching.mdx rename to docs/src/content/en/docs/batching.mdx diff --git a/docs/src/content/en/components/_meta.ts b/docs/src/content/en/docs/components/_meta.ts similarity index 100% rename from docs/src/content/en/components/_meta.ts rename to docs/src/content/en/docs/components/_meta.ts diff --git a/docs/src/content/en/components/click.mdx b/docs/src/content/en/docs/components/click.mdx similarity index 100% rename from docs/src/content/en/components/click.mdx rename to docs/src/content/en/docs/components/click.mdx diff --git a/docs/src/content/en/components/dom-event.mdx b/docs/src/content/en/docs/components/dom-event.mdx similarity index 100% rename from docs/src/content/en/components/dom-event.mdx rename to docs/src/content/en/docs/components/dom-event.mdx diff --git a/docs/src/content/en/components/impression.mdx b/docs/src/content/en/docs/components/impression.mdx similarity index 100% rename from docs/src/content/en/components/impression.mdx rename to docs/src/content/en/docs/components/impression.mdx diff --git a/docs/src/content/en/components/index.mdx b/docs/src/content/en/docs/components/index.mdx similarity index 100% rename from docs/src/content/en/components/index.mdx rename to docs/src/content/en/docs/components/index.mdx diff --git a/docs/src/content/en/components/page-view.mdx b/docs/src/content/en/docs/components/page-view.mdx similarity index 100% rename from docs/src/content/en/components/page-view.mdx rename to docs/src/content/en/docs/components/page-view.mdx diff --git a/docs/src/content/en/components/set-context.mdx b/docs/src/content/en/docs/components/set-context.mdx similarity index 100% rename from docs/src/content/en/components/set-context.mdx rename to docs/src/content/en/docs/components/set-context.mdx diff --git a/docs/src/content/en/create-tracker.mdx b/docs/src/content/en/docs/create-tracker.mdx similarity index 100% rename from docs/src/content/en/create-tracker.mdx rename to docs/src/content/en/docs/create-tracker.mdx diff --git a/docs/src/content/en/data-type-validation.mdx b/docs/src/content/en/docs/data-type-validation.mdx similarity index 100% rename from docs/src/content/en/data-type-validation.mdx rename to docs/src/content/en/docs/data-type-validation.mdx diff --git a/docs/src/content/en/installation.mdx b/docs/src/content/en/docs/installation.mdx similarity index 100% rename from docs/src/content/en/installation.mdx rename to docs/src/content/en/docs/installation.mdx diff --git a/docs/src/content/en/docs/introduction.mdx b/docs/src/content/en/docs/introduction.mdx new file mode 100644 index 0000000..c2585a0 --- /dev/null +++ b/docs/src/content/en/docs/introduction.mdx @@ -0,0 +1,89 @@ +import { Steps } from "nextra/components"; + +# Introduction + +Welcome to the Event Tracker documentation. + +## What is Event Tracker? + +Event Tracker is a declarative React library designed to simplify the implementation of complex event tracking, allowing developers to focus more on their business logic. It is designed to efficiently manage event tracking in applications of any scale. + +```tsx +import { createTracker } from "@offlegacy/event-tracker"; + +// Creating a tracker instance +const [Track, useTracker] = createTracker({ + DOMEvents: { + onClick: (params, context) => { + log("Click event:", params, context); + }, + }, +}); + +// Usage within an app +function App() { + return ( + + + + + + ); +} +``` + +### Key Features + +Event Tracker provides various features prioritizing both developer experience and application performance. + +| Feature | Description | +| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Declarative API with Type Safety | Fully supports [TypeScript](https://www.typescriptlang.org/), reducing errors during development and increasing productivity through auto-completion. | +| Powerful Data Type Validation | Ensures data reliability through schema-based validation using [Zod](https://zod.dev/). | +| Optimized Performance | Minimizes network requests through batching, debouncing, or throttling, reducing the impact on application performance. | +| Guaranteed Execution Order | Ensures asynchronous events are processed in the intended sequence. | +| Analytics Tool Independence | Flexible integration with any analytics tool (e.g., [Google Analytics](https://analytics.google.com/), [Amplitude](https://amplitude.com/)), without being tied to any specific provider. | +| Clear Separation of Concerns | Effectively separates tracking logic from business logic, maximizing code maintainability and scalability. | + +## Core Concepts + +There are several key concepts you need to understand to use Event Tracker effectively. + + + +### Instance (`createTracker`) + +The foundational starting point of the library. Use the `createTracker` function to generate a tracker instance (a collection of `Track` components and the `useTracker` hook). Define event tracking by configuring DOM event handlers, impression event handlers, schemas, and more. + +Multiple tracker instances can be created according to specific purposes (e.g., separate instances for sending events to Google Analytics and Amplitude). + +### Provider (`Track.Provider`) + +Implemented using React's Context API. Wrap your application or component tree at the top with `Track.Provider` to provide common tracking data (context) to child components. For example, providing information such as `userId` and `pageName` through context allows utilizing this information during each event tracking. + +### Event Components (`Track.Click`, `Track.PageView`, etc.) + +Special components provided to track events declaratively. These are available from the first element of the array returned by `createTracker`. + +- `Track.Click`: Tracks click events occurring on child elements. +- `Track.Impression`: Tracks when child elements become visible on the screen. +- `Track.PageView`: Tracks page view events upon component mount. + +In addition, you can customize or utilize various provided components that respond to different user interactions and lifecycle events. Each component can receive and utilize specific event-related data through the `context` and `params` props. + +### Custom Hook (`useTracker`) + +Used for more complex or conditional event tracking unrelated directly to component lifecycles or DOM events. The `useTracker` hook lets you access context information from `Track.Provider` and execute defined tracking logic imperatively. + + + +## Next Steps + +These core concepts work synergistically to provide Event Tracker's powerful and flexible event-tracking environment. For detailed usage and in-depth information about each feature, refer to the documents below: + +- [Why Event Tracker?](/docs/why-event-tracker): Introduction to the necessity and core features of Event Tracker. +- [`createTracker`](/docs/create-tracker): Guide to creating tracker instances and detailed configurations. +- [Components](/docs/components): Examples and details of all available tracking components. +- [`useTracker`](/docs/hook): Custom tracking techniques using the custom hook. +- [Batching](/docs/batching): Strategies for optimizing performance through event batching. +- [Data Type Validation](/docs/data-type-validation): Guide to data validation using Zod schemas. diff --git a/docs/src/content/en/use-tracker.mdx b/docs/src/content/en/docs/use-tracker.mdx similarity index 100% rename from docs/src/content/en/use-tracker.mdx rename to docs/src/content/en/docs/use-tracker.mdx diff --git a/docs/src/content/en/why-event-tracker.mdx b/docs/src/content/en/docs/why-event-tracker.mdx similarity index 100% rename from docs/src/content/en/why-event-tracker.mdx rename to docs/src/content/en/docs/why-event-tracker.mdx diff --git a/docs/src/content/en/index.mdx b/docs/src/content/en/index.mdx index c2585a0..4c05ecc 100644 --- a/docs/src/content/en/index.mdx +++ b/docs/src/content/en/index.mdx @@ -1,89 +1,42 @@ -import { Steps } from "nextra/components"; - -# Introduction - -Welcome to the Event Tracker documentation. - -## What is Event Tracker? - -Event Tracker is a declarative React library designed to simplify the implementation of complex event tracking, allowing developers to focus more on their business logic. It is designed to efficiently manage event tracking in applications of any scale. - -```tsx -import { createTracker } from "@offlegacy/event-tracker"; - -// Creating a tracker instance -const [Track, useTracker] = createTracker({ - DOMEvents: { - onClick: (params, context) => { - log("Click event:", params, context); +--- +title: Event Tracker | Comprehensive solution for event tracking in React applications +--- + +import HomePage from "@/components/home-page"; + + - - - - - ); -} -``` - -### Key Features - -Event Tracker provides various features prioritizing both developer experience and application performance. - -| Feature | Description | -| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Declarative API with Type Safety | Fully supports [TypeScript](https://www.typescriptlang.org/), reducing errors during development and increasing productivity through auto-completion. | -| Powerful Data Type Validation | Ensures data reliability through schema-based validation using [Zod](https://zod.dev/). | -| Optimized Performance | Minimizes network requests through batching, debouncing, or throttling, reducing the impact on application performance. | -| Guaranteed Execution Order | Ensures asynchronous events are processed in the intended sequence. | -| Analytics Tool Independence | Flexible integration with any analytics tool (e.g., [Google Analytics](https://analytics.google.com/), [Amplitude](https://amplitude.com/)), without being tied to any specific provider. | -| Clear Separation of Concerns | Effectively separates tracking logic from business logic, maximizing code maintainability and scalability. | - -## Core Concepts - -There are several key concepts you need to understand to use Event Tracker effectively. - - - -### Instance (`createTracker`) - -The foundational starting point of the library. Use the `createTracker` function to generate a tracker instance (a collection of `Track` components and the `useTracker` hook). Define event tracking by configuring DOM event handlers, impression event handlers, schemas, and more. - -Multiple tracker instances can be created according to specific purposes (e.g., separate instances for sending events to Google Analytics and Amplitude). - -### Provider (`Track.Provider`) - -Implemented using React's Context API. Wrap your application or component tree at the top with `Track.Provider` to provide common tracking data (context) to child components. For example, providing information such as `userId` and `pageName` through context allows utilizing this information during each event tracking. - -### Event Components (`Track.Click`, `Track.PageView`, etc.) - -Special components provided to track events declaratively. These are available from the first element of the array returned by `createTracker`. - -- `Track.Click`: Tracks click events occurring on child elements. -- `Track.Impression`: Tracks when child elements become visible on the screen. -- `Track.PageView`: Tracks page view events upon component mount. - -In addition, you can customize or utilize various provided components that respond to different user interactions and lifecycle events. Each component can receive and utilize specific event-related data through the `context` and `params` props. - -### Custom Hook (`useTracker`) - -Used for more complex or conditional event tracking unrelated directly to component lifecycles or DOM events. The `useTracker` hook lets you access context information from `Track.Provider` and execute defined tracking logic imperatively. - - - -## Next Steps - -These core concepts work synergistically to provide Event Tracker's powerful and flexible event-tracking environment. For detailed usage and in-depth information about each feature, refer to the documents below: - -- [Why Event Tracker?](/docs/why-event-tracker): Introduction to the necessity and core features of Event Tracker. -- [`createTracker`](/docs/create-tracker): Guide to creating tracker instances and detailed configurations. -- [Components](/docs/components): Examples and details of all available tracking components. -- [`useTracker`](/docs/hook): Custom tracking techniques using the custom hook. -- [Batching](/docs/batching): Strategies for optimizing performance through event batching. -- [Data Type Validation](/docs/data-type-validation): Guide to data validation using Zod schemas. + { + title: "Analytics Tool Independence", + description: + "Easily integrates with tools like Google Analytics, Amplitude, or Segment—without forcing you to switch or couple to a specific vendor.", + }, + { + title: "Clear Separation of Concerns", + description: + "Keeps tracking logic out of your business logic, resulting in cleaner code that’s easier to test, maintain, and evolve.", + }, + { + title: "Optimized Performance", + description: + "Built-in strategies like batching, debouncing, and throttling minimize network overhead, enabling efficient and reliable tracking.", + }, + { + title: "Guaranteed Execution Order", + description: + "Ensures that events are processed in the intended order, even under asynchronous conditions—crucial for accurate tracking in complex flows.", + }, + { + title: "Powerful Data Type Validation", + description: + "Schema-based validation with Zod catches issues before runtime and guarantees the integrity of your event data.", + }, + ]} +/> diff --git a/docs/src/content/ko/_meta.tsx b/docs/src/content/ko/_meta.tsx new file mode 100644 index 0000000..6fef8c5 --- /dev/null +++ b/docs/src/content/ko/_meta.tsx @@ -0,0 +1,16 @@ +import type { MetaRecord } from "nextra"; + +export default { + index: { + type: "page", + display: "hidden", + theme: { + layout: "full", + toc: false, + }, + }, + docs: { + type: "page", + title: "문서보기", + }, +} satisfies MetaRecord; diff --git a/docs/src/content/ko/_meta.ts b/docs/src/content/ko/docs/_meta.ts similarity index 90% rename from docs/src/content/ko/_meta.ts rename to docs/src/content/ko/docs/_meta.ts index 7705f64..bb10b12 100644 --- a/docs/src/content/ko/_meta.ts +++ b/docs/src/content/ko/docs/_meta.ts @@ -5,7 +5,7 @@ export default { type: "separator", title: "시작하기", }, - index: { + introduction: { title: "소개", theme: { toc: true, @@ -37,14 +37,7 @@ export default { layout: "default", }, }, - components: { - title: "Components", - theme: { - toc: true, - layout: "default", - collapsed: true, - }, - }, + components: { title: "Components" }, "use-tracker": { title: "useTracker", theme: { diff --git a/docs/src/content/ko/basic-example.mdx b/docs/src/content/ko/docs/basic-example.mdx similarity index 100% rename from docs/src/content/ko/basic-example.mdx rename to docs/src/content/ko/docs/basic-example.mdx diff --git a/docs/src/content/ko/batching.mdx b/docs/src/content/ko/docs/batching.mdx similarity index 100% rename from docs/src/content/ko/batching.mdx rename to docs/src/content/ko/docs/batching.mdx diff --git a/docs/src/content/ko/components/_meta.ts b/docs/src/content/ko/docs/components/_meta.ts similarity index 100% rename from docs/src/content/ko/components/_meta.ts rename to docs/src/content/ko/docs/components/_meta.ts diff --git a/docs/src/content/ko/components/click.mdx b/docs/src/content/ko/docs/components/click.mdx similarity index 100% rename from docs/src/content/ko/components/click.mdx rename to docs/src/content/ko/docs/components/click.mdx diff --git a/docs/src/content/ko/components/dom-event.mdx b/docs/src/content/ko/docs/components/dom-event.mdx similarity index 92% rename from docs/src/content/ko/components/dom-event.mdx rename to docs/src/content/ko/docs/components/dom-event.mdx index 1623333..60c5994 100644 --- a/docs/src/content/ko/components/dom-event.mdx +++ b/docs/src/content/ko/docs/components/dom-event.mdx @@ -30,12 +30,12 @@ function App() { ### Props -| Props | 타입 | 설명 | 필수 | -| ------------- | ---------------- | ----------------------------------------------------- | ---- | -| DOMEventNames | DOMEventNames | 이벤트 이름 (예: `"onClick"`, `"onFocus"`) | O | -| `enabled` | `boolean | ((context: Context, params: EventParams) => boolean)` | - | -| `debounce` | `DebounceConfig` | 연속적인 이벤트 발생을 방지하는 디바운스 설정 | - | -| `throttle` | `ThrottleConfig` | 이벤트 발생 빈도를 제한하는 스로틀 설정 | - | +| Props | 타입 | 설명 | 필수 | +| ------------- | ---------------- | ------------------------------------------------------ | ---- | +| DOMEventNames | DOMEventNames | 이벤트 이름 (예: `"onClick"`, `"onFocus"`) | O | +| `enabled` | `boolean` | `((context: Context, params: EventParams) => boolean)` | - | +| `debounce` | `DebounceConfig` | 연속적인 이벤트 발생을 방지하는 디바운스 설정 | - | +| `throttle` | `ThrottleConfig` | 이벤트 발생 빈도를 제한하는 스로틀 설정 | - | 참고: `debounce`와 `throttle`은 상호 배타적이며 함께 사용할 수 없습니다. diff --git a/docs/src/content/ko/components/impression.mdx b/docs/src/content/ko/docs/components/impression.mdx similarity index 100% rename from docs/src/content/ko/components/impression.mdx rename to docs/src/content/ko/docs/components/impression.mdx diff --git a/docs/src/content/ko/components/index.mdx b/docs/src/content/ko/docs/components/index.mdx similarity index 100% rename from docs/src/content/ko/components/index.mdx rename to docs/src/content/ko/docs/components/index.mdx diff --git a/docs/src/content/ko/components/page-view.mdx b/docs/src/content/ko/docs/components/page-view.mdx similarity index 100% rename from docs/src/content/ko/components/page-view.mdx rename to docs/src/content/ko/docs/components/page-view.mdx diff --git a/docs/src/content/ko/components/set-context.mdx b/docs/src/content/ko/docs/components/set-context.mdx similarity index 100% rename from docs/src/content/ko/components/set-context.mdx rename to docs/src/content/ko/docs/components/set-context.mdx diff --git a/docs/src/content/ko/create-tracker.mdx b/docs/src/content/ko/docs/create-tracker.mdx similarity index 100% rename from docs/src/content/ko/create-tracker.mdx rename to docs/src/content/ko/docs/create-tracker.mdx diff --git a/docs/src/content/ko/data-type-validation.mdx b/docs/src/content/ko/docs/data-type-validation.mdx similarity index 100% rename from docs/src/content/ko/data-type-validation.mdx rename to docs/src/content/ko/docs/data-type-validation.mdx diff --git a/docs/src/content/ko/installation.mdx b/docs/src/content/ko/docs/installation.mdx similarity index 100% rename from docs/src/content/ko/installation.mdx rename to docs/src/content/ko/docs/installation.mdx diff --git a/docs/src/content/ko/docs/introduction.mdx b/docs/src/content/ko/docs/introduction.mdx new file mode 100644 index 0000000..03c0269 --- /dev/null +++ b/docs/src/content/ko/docs/introduction.mdx @@ -0,0 +1,89 @@ +import { Steps } from "nextra/components"; + +# 소개 + +Event Tracker 문서에 오신 것을 환영합니다. + +## Event Tracker가 무엇인가요? + +Event Tracker는 복잡한 이벤트 트래킹 구현 과정을 단순화하고, 개발자가 비즈니스 로직에 더 집중할 수 있도록 돕는 선언적 방식의 React 라이브러리입니다. 모든 규모의 애플리케이션에서 이벤트 트래킹을 쉽고 효율적으로 관리할 수 있도록 설계되었습니다. + +```tsx +import { createTracker } from "@offlegacy/event-tracker"; + +// 트래커 인스턴스 생성 +const [Track, useTracker] = createTracker({ + DOMEvents: { + onClick: (params, context) => { + log("Click event:", params, context); + }, + }, +}); + +// 앱에서 사용하기 +function App() { + return ( + + + + + + ); +} +``` + +### 주요 기능 + +Event Tracker는 개발자 경험과 애플리케이션 성능을 모두 고려한 다양한 기능을 제공합니다. + +| Feature | Description | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 타입 안정성을 갖춘 선언적 API | [TypeScript](https://www.typescriptlang.org/)를 완벽하게 지원하여 개발 과정에서의 오류를 줄이고, 자동 완성을 통해 생산성을 높입니다. | +| 강력한 데이터 타입 검증 | [Zod](https://zod.dev/)를 활용한 스키마 기반 검증으로 데이터의 신뢰성을 확보합니다. | +| 최적화된 성능 | 배칭이나 디바운스, 스로틀링 기능을 통해 네트워크 요청을 최소화하고 애플리케이션 성능에 미치는 영향을 줄입니다. | +| 실행 순서 보장 | 비동기적으로 발생하는 이벤트들에 대해서도 의도한 순서대로 처리되도록 보장합니다. | +| 애널리틱스 도구 독립성 | 특정 애널리틱스 서비스에 종속되지 않고, 원하는 모든 도구([Google Analytics](https://analytics.google.com/), [Amplitude](https://amplitude.com/) 등)와 유연하게 통합할 수 있습니다. | +| 명확한 관심사 분리 | 트래킹 로직과 비즈니스 로직을 효과적으로 분리하여 코드의 유지보수성과 확장성을 극대화합니다. | + +## 핵심 개념 + +Event Tracker를 효과적으로 사용하기 위해 알아야 할 몇 가지 핵심 개념이 있습니다. + + + +### 인스턴스 (`createTracker`) + +라이브러리의 가장 기본적인 출발점입니다. `createTracker` 함수를 사용하여 트래커 인스턴스(`Track` 컴포넌트 컬렉션과 `useTracker` 훅)를 생성합니다. 이때, DOM 이벤트 핸들러, 노출(Impression) 이벤트 핸들러, 스키마 등을 설정하여 이벤트 트래킹을 정의합니다. + +목적에 따라 구분하여 여러 가지 트래커 인스턴스를 생성할 수 있습니다. (예를 들어, Google Analytics로 보내는 이벤트와 Amplitude로 보내는 이벤트를 구분하여 생성할 수 있습니다.) + +### 프로바이더 (`Track.Provider`) + +React의 Context API를 기반으로 구현되었습니다. 애플리케이션 또는 특정 컴포넌트 트리의 최상단에서 `Track.Provider`로 감싸 하위 컴포넌트들에 트래킹에 필요한 공통 데이터(컨텍스트)를 제공합니다. 예를 들어, `userId`, `pageName` 등의 정보를 컨텍스트로 전달하면, 각 이벤트 트래킹 시 이 정보를 활용할 수 있습니다. + +### 이벤트 컴포넌트 (`Track.Click`, `Track.PageView` 등) + +선언적으로 이벤트를 트래킹할 수 있도록 제공되는 특수 컴포넌트들입니다. `createTracker` 리턴 배열의 첫 번째 요소입니다. + +- `Track.Click`: 자식 요소에서 클릭 이벤트가 발생했을 때 트래킹합니다. +- `Track.Impression`: 자식 요소가 화면에 노출되었을 때 트래킹합니다. +- `Track.PageView`: 컴포넌트가 마운트될 때 페이지 뷰 이벤트를 트래킹합니다. + +이 외에도 다양한 사용자 인터랙션 및 생명주기 이벤트에 대응하는 컴포넌트를 제공하거나 커스텀하여 사용할 수 있습니다. 각 컴포넌트는 `context`, `params` prop을 통해 해당 이벤트와 관련된 특정 데이터를 전달받고 활용할 수 있습니다. + +### 커스텀 훅 (`useTracker`) + +컴포넌트의 생명주기나 DOM 이벤트와 직접적으로 관련되지 않은, 보다 복잡하거나 조건부적인 이벤트 트래킹이 필요할 때 사용합니다. `useTracker` 훅을 사용하면 `Track.Provider`로부터 컨텍스트 정보를 가져오고, 정의된 트래킹 로직을 명령형으로 실행할 수 있습니다. + + + +## 다음 단계 + +이러한 핵심 개념들은 서로 유기적으로 작동하여 Event Tracker의 강력하고 유연한 이벤트 트래킹 환경을 구성합니다. 더 자세한 사용법과 각 기능에 대한 심층적인 내용은 아래 문서들에서 확인하실 수 있습니다. + +- [왜 Event Tracker인가요?](/docs/why-event-tracker): Event Tracker의 필요성과 주요 기능 소개 +- [`createTracker`](/docs/create-tracker): 트래커 인스턴스 생성 및 상세 설정 가이드 +- [Components](/docs/components): 사용 가능한 모든 트래킹 컴포넌트와 사용 예시 +- [`useTracker`](/docs/hook): 커스텀 훅을 활용한 사용자 지정 트래킹 기법 +- [Batching](/docs/batching): 이벤트 배칭을 통한 성능 최적화 전략 +- [Data Type Validation](/docs/data-type-validation): Zod 스키마를 활용한 데이터 유효성 검증 가이드 diff --git a/docs/src/content/ko/use-tracker.mdx b/docs/src/content/ko/docs/use-tracker.mdx similarity index 100% rename from docs/src/content/ko/use-tracker.mdx rename to docs/src/content/ko/docs/use-tracker.mdx diff --git a/docs/src/content/ko/why-event-tracker.mdx b/docs/src/content/ko/docs/why-event-tracker.mdx similarity index 100% rename from docs/src/content/ko/why-event-tracker.mdx rename to docs/src/content/ko/docs/why-event-tracker.mdx diff --git a/docs/src/content/ko/index.mdx b/docs/src/content/ko/index.mdx index 03c0269..2bfc973 100644 --- a/docs/src/content/ko/index.mdx +++ b/docs/src/content/ko/index.mdx @@ -1,89 +1,42 @@ -import { Steps } from "nextra/components"; - -# 소개 - -Event Tracker 문서에 오신 것을 환영합니다. - -## Event Tracker가 무엇인가요? - -Event Tracker는 복잡한 이벤트 트래킹 구현 과정을 단순화하고, 개발자가 비즈니스 로직에 더 집중할 수 있도록 돕는 선언적 방식의 React 라이브러리입니다. 모든 규모의 애플리케이션에서 이벤트 트래킹을 쉽고 효율적으로 관리할 수 있도록 설계되었습니다. - -```tsx -import { createTracker } from "@offlegacy/event-tracker"; - -// 트래커 인스턴스 생성 -const [Track, useTracker] = createTracker({ - DOMEvents: { - onClick: (params, context) => { - log("Click event:", params, context); +--- +title: Event Tracker | Comprehensive solution for event tracking in React applications +--- + +import HomePage from "@/components/home-page"; + + - - - - - ); -} -``` - -### 주요 기능 - -Event Tracker는 개발자 경험과 애플리케이션 성능을 모두 고려한 다양한 기능을 제공합니다. - -| Feature | Description | -| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 타입 안정성을 갖춘 선언적 API | [TypeScript](https://www.typescriptlang.org/)를 완벽하게 지원하여 개발 과정에서의 오류를 줄이고, 자동 완성을 통해 생산성을 높입니다. | -| 강력한 데이터 타입 검증 | [Zod](https://zod.dev/)를 활용한 스키마 기반 검증으로 데이터의 신뢰성을 확보합니다. | -| 최적화된 성능 | 배칭이나 디바운스, 스로틀링 기능을 통해 네트워크 요청을 최소화하고 애플리케이션 성능에 미치는 영향을 줄입니다. | -| 실행 순서 보장 | 비동기적으로 발생하는 이벤트들에 대해서도 의도한 순서대로 처리되도록 보장합니다. | -| 애널리틱스 도구 독립성 | 특정 애널리틱스 서비스에 종속되지 않고, 원하는 모든 도구([Google Analytics](https://analytics.google.com/), [Amplitude](https://amplitude.com/) 등)와 유연하게 통합할 수 있습니다. | -| 명확한 관심사 분리 | 트래킹 로직과 비즈니스 로직을 효과적으로 분리하여 코드의 유지보수성과 확장성을 극대화합니다. | - -## 핵심 개념 - -Event Tracker를 효과적으로 사용하기 위해 알아야 할 몇 가지 핵심 개념이 있습니다. - - - -### 인스턴스 (`createTracker`) - -라이브러리의 가장 기본적인 출발점입니다. `createTracker` 함수를 사용하여 트래커 인스턴스(`Track` 컴포넌트 컬렉션과 `useTracker` 훅)를 생성합니다. 이때, DOM 이벤트 핸들러, 노출(Impression) 이벤트 핸들러, 스키마 등을 설정하여 이벤트 트래킹을 정의합니다. - -목적에 따라 구분하여 여러 가지 트래커 인스턴스를 생성할 수 있습니다. (예를 들어, Google Analytics로 보내는 이벤트와 Amplitude로 보내는 이벤트를 구분하여 생성할 수 있습니다.) - -### 프로바이더 (`Track.Provider`) - -React의 Context API를 기반으로 구현되었습니다. 애플리케이션 또는 특정 컴포넌트 트리의 최상단에서 `Track.Provider`로 감싸 하위 컴포넌트들에 트래킹에 필요한 공통 데이터(컨텍스트)를 제공합니다. 예를 들어, `userId`, `pageName` 등의 정보를 컨텍스트로 전달하면, 각 이벤트 트래킹 시 이 정보를 활용할 수 있습니다. - -### 이벤트 컴포넌트 (`Track.Click`, `Track.PageView` 등) - -선언적으로 이벤트를 트래킹할 수 있도록 제공되는 특수 컴포넌트들입니다. `createTracker` 리턴 배열의 첫 번째 요소입니다. - -- `Track.Click`: 자식 요소에서 클릭 이벤트가 발생했을 때 트래킹합니다. -- `Track.Impression`: 자식 요소가 화면에 노출되었을 때 트래킹합니다. -- `Track.PageView`: 컴포넌트가 마운트될 때 페이지 뷰 이벤트를 트래킹합니다. - -이 외에도 다양한 사용자 인터랙션 및 생명주기 이벤트에 대응하는 컴포넌트를 제공하거나 커스텀하여 사용할 수 있습니다. 각 컴포넌트는 `context`, `params` prop을 통해 해당 이벤트와 관련된 특정 데이터를 전달받고 활용할 수 있습니다. - -### 커스텀 훅 (`useTracker`) - -컴포넌트의 생명주기나 DOM 이벤트와 직접적으로 관련되지 않은, 보다 복잡하거나 조건부적인 이벤트 트래킹이 필요할 때 사용합니다. `useTracker` 훅을 사용하면 `Track.Provider`로부터 컨텍스트 정보를 가져오고, 정의된 트래킹 로직을 명령형으로 실행할 수 있습니다. - - - -## 다음 단계 - -이러한 핵심 개념들은 서로 유기적으로 작동하여 Event Tracker의 강력하고 유연한 이벤트 트래킹 환경을 구성합니다. 더 자세한 사용법과 각 기능에 대한 심층적인 내용은 아래 문서들에서 확인하실 수 있습니다. - -- [왜 Event Tracker인가요?](/docs/why-event-tracker): Event Tracker의 필요성과 주요 기능 소개 -- [`createTracker`](/docs/create-tracker): 트래커 인스턴스 생성 및 상세 설정 가이드 -- [Components](/docs/components): 사용 가능한 모든 트래킹 컴포넌트와 사용 예시 -- [`useTracker`](/docs/hook): 커스텀 훅을 활용한 사용자 지정 트래킹 기법 -- [Batching](/docs/batching): 이벤트 배칭을 통한 성능 최적화 전략 -- [Data Type Validation](/docs/data-type-validation): Zod 스키마를 활용한 데이터 유효성 검증 가이드 + { + title: "애널리틱스 도구 독립성", + description: + "Google Analytics, Amplitude, Segment 등 어떤 도구든 자유롭게 연동할 수 있어, 기존 인프라를 변경하지 않고도 도입이 가능합니다.", + }, + { + title: "명확한 관심사 분리", + description: + "트래킹 로직을 비즈니스 코드에서 완전히 분리함으로써 코드의 가독성, 테스트 용이성, 유지보수성을 모두 향상시킵니다.", + }, + { + title: "최적화된 성능", + description: + "배칭, 디바운스, 스로틀링 등 네트워크 요청을 최소화하는 다양한 전략이 내장되어 있어, 성능 저하 없이 안정적으로 트래킹할 수 있습니다.", + }, + { + title: "실행 순서 보장", + description: + "비동기 상황에서도 이벤트가 의도한 순서대로 처리되도록 설계되어, 복잡한 사용자 흐름에서도 정확한 트래킹이 가능합니다.", + }, + { + title: "강력한 데이터 타입 검증", + description: + "Zod 기반의 정적 스키마 검증으로 런타임 이전에 오류를 방지하고, 수집되는 이벤트 데이터의 신뢰도를 보장합니다.", + }, + ]} +/> diff --git a/docs/mdx-components.ts b/docs/src/mdx-components.ts similarity index 80% rename from docs/mdx-components.ts rename to docs/src/mdx-components.ts index 3e463a6..a304c9d 100644 --- a/docs/mdx-components.ts +++ b/docs/src/mdx-components.ts @@ -1,5 +1,5 @@ import { useMDXComponents as getThemeComponents } from "nextra-theme-docs"; -import { MDXComponents } from "nextra/mdx-components"; +import type { MDXComponents } from "nextra/mdx-components"; const themeComponents = getThemeComponents(); diff --git a/docs/src/middleware.ts b/docs/src/middleware.ts index 9b06bd6..9cc1483 100644 --- a/docs/src/middleware.ts +++ b/docs/src/middleware.ts @@ -1,5 +1,15 @@ export { middleware } from "nextra/locales"; export const config = { - matcher: ["/((?!api|_next/static|_next/image|img|favicon.ico|icon.svg|apple-icon.png|manifest|_pagefind).*)"], + matcher: [ + /* + * Match all request paths except for the ones starting with: + * - api (API routes) + * - _next/static (static files) + * - _next/image (image optimization files) + * - favicon.ico (favicon file) + * - img (image files) + */ + "/((?!api|_next/static|_next/image|favicon.ico|img|_pagefind).*)", + ], }; diff --git a/package.json b/package.json index 88b7774..4979ed4 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "scripts": { "dev": "tsup --watch", "build": "tsup", + "build:docs": "cd docs && pnpm build", "dev:docs": "cd docs && pnpm dev", "test": "vitest --run", "test:watch": "vitest", From 60bb54c485644fa2ce1ff2ba04adf95a905be6dd Mon Sep 17 00:00:00 2001 From: gwansikk Date: Fri, 11 Jul 2025 20:27:45 +0900 Subject: [PATCH 14/24] docs: update ko --- docs/src/components/demo-button.tsx | 2 +- docs/src/components/demo-playground.tsx | 2 +- docs/src/content/en/index.mdx | 2 +- docs/src/content/ko/docs/_meta.ts | 28 + docs/src/content/ko/docs/basic-example.mdx | 759 +++++++++++- docs/src/content/ko/docs/batching.mdx | 97 +- docs/src/content/ko/docs/components/click.mdx | 132 ++- .../content/ko/docs/components/dom-event.mdx | 115 +- .../content/ko/docs/components/impression.mdx | 104 +- docs/src/content/ko/docs/components/index.mdx | 43 +- .../content/ko/docs/components/page-view.mdx | 92 +- .../ko/docs/components/set-context.mdx | 49 +- docs/src/content/ko/docs/create-tracker.mdx | 11 + .../content/ko/docs/data-type-validation.mdx | 123 +- docs/src/content/ko/docs/installation.mdx | 2 +- docs/src/content/ko/docs/instance-example.mdx | 1031 +++++++++++++++++ docs/src/content/ko/docs/introduction.mdx | 16 +- docs/src/content/ko/docs/use-tracker.mdx | 145 ++- .../src/content/ko/docs/why-event-tracker.mdx | 8 +- .../ko/docs/with-google-analytics-example.mdx | 624 ++++++++++ .../ko/docs/with-selfhost-server-example.mdx | 718 ++++++++++++ docs/src/content/ko/docs/with-zod-example.mdx | 770 ++++++++++++ docs/src/content/ko/index.mdx | 4 +- 23 files changed, 4581 insertions(+), 296 deletions(-) create mode 100644 docs/src/content/ko/docs/instance-example.mdx create mode 100644 docs/src/content/ko/docs/with-google-analytics-example.mdx create mode 100644 docs/src/content/ko/docs/with-selfhost-server-example.mdx create mode 100644 docs/src/content/ko/docs/with-zod-example.mdx diff --git a/docs/src/components/demo-button.tsx b/docs/src/components/demo-button.tsx index fd69065..b2c4a70 100644 --- a/docs/src/components/demo-button.tsx +++ b/docs/src/components/demo-button.tsx @@ -15,7 +15,7 @@ const [Track] = createTracker({ export function DemoButton({ children }: { children: React.ReactNode }) { return ( <> - +
- basic-example.tsx + example-basic.tsx
diff --git a/docs/src/content/en/index.mdx b/docs/src/content/en/index.mdx index 4c05ecc..cdc1b7c 100644 --- a/docs/src/content/en/index.mdx +++ b/docs/src/content/en/index.mdx @@ -7,7 +7,7 @@ import HomePage from "@/components/home-page"; { + // 세션 ID 생성 + const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + setContext({ ...initialContext, sessionId }); + }, + + // 이벤트 전송 함수 + send: (params, context) => { + console.log("이벤트 전송:", params, context); + // 실제 애널리틱스 서비스로 전송 + analytics.track(params.eventName, { + ...params, + userId: context.userId, + sessionId: context.sessionId, + timestamp: Date.now(), + }); + }, + + // DOM 이벤트 핸들러 DOMEvents: { onClick: (params, context) => { - log("Click event:", params, context); + console.log("클릭 이벤트:", params, context); + }, + }, + + // 노출 이벤트 설정 + impression: { + onImpression: (params, context) => { + console.log("노출 이벤트:", params, context); + }, + options: { + threshold: 0.5, // 50% 이상 보일 때 트리거 + freezeOnceVisible: true, // 한 번 보이면 더 이상 트리거하지 않음 + }, + }, + + // 페이지 뷰 설정 + pageView: { + onPageView: (params, context) => { + console.log("페이지 뷰:", params, context); }, }, }); -// 앱에서 사용하기 +export { Track, useTracker }; +``` + +### 2. 기본 사용법 + +```tsx +// App.tsx +import { Track, useTracker } from "./tracker"; + function App() { return ( - - + + + ); +} + +function HomePage() { + const { track } = useTracker(); + + const handleCustomEvent = async () => { + await track({ + eventName: "custom_action", + category: "engagement", + action: "button_click", + label: "homepage_cta", + value: 1, + }); + }; + + return ( +
+

홈페이지

+ + {/* 페이지 뷰 자동 트래킹 */} + + + {/* 버튼 클릭 트래킹 */} + + + + {/* 배너 노출 트래킹 */} + +
+

환영합니다!

+

새로운 경험을 시작해보세요.

+
+
+
+ ); +} +``` + +## 핵심 컴포넌트 + +### 1. Track.Provider + +트래킹 컨텍스트를 제공하는 최상위 컴포넌트입니다. + +```tsx + + {/* 자식 컴포넌트들 */} + +``` + +**주요 속성:** + +- `initialContext`: 초기 컨텍스트 객체 +- `enabled`: 트래킹 활성화 여부 (기본값: true) + +### 2. Track.Click + +클릭 이벤트를 자동으로 트래킹하는 컴포넌트입니다. + +```tsx + context.trackingConsent} + debounce={{ delay: 300 }} +> + + +``` + +**주요 속성:** + +- `params`: 이벤트 파라미터 (객체 또는 함수) +- `enabled`: 트래킹 활성화 조건 +- `debounce`: 디바운스 설정 +- `throttle`: 스로틀 설정 + +### 3. Track.Impression + +요소가 화면에 노출될 때 트래킹하는 컴포넌트입니다. + +```tsx + +
+ 상품 1 +

상품명

+

₩29,900

+
+
+``` + +**주요 속성:** + +- `params`: 이벤트 파라미터 +- `options`: Intersection Observer 옵션 +- `enabled`: 트래킹 활성화 조건 + +### 4. Track.PageView + +페이지 뷰를 자동으로 트래킹하는 컴포넌트입니다. + +```tsx + +``` + +## 실제 사용 시나리오 + +### 1. 전자상거래 사이트 + +```tsx +// components/ProductList.tsx +import { Track, useTracker } from "../tracker"; + +function ProductList({ products }) { + const { track } = useTracker(); + + const handleAddToCart = async (product) => { + try { + // 장바구니 추가 로직 + await addToCart(product.id); + + // 트래킹 + await track({ + eventName: "add_to_cart", + productId: product.id, + productName: product.name, + price: product.price, + currency: "KRW", + quantity: 1, + category: product.category, + }); + } catch (error) { + console.error("장바구니 추가 실패:", error); + } + }; + + return ( + + {/* 페이지 뷰 트래킹 */} + + +
+ {products.map((product, index) => ( + +
+ {product.name} +

{product.name}

+

₩{product.price.toLocaleString()}

+ + {/* 장바구니 버튼 */} + + + + + {/* 상세보기 버튼 */} + + + +
+
+ ))} +
); } ``` + +### 2. 블로그/콘텐츠 사이트 + +```tsx +// components/BlogPost.tsx +import { Track, useTracker } from "../tracker"; + +function BlogPost({ post }) { + const { track } = useTracker(); + + const handleShare = async (platform) => { + await track({ + eventName: "share_article", + articleId: post.id, + articleTitle: post.title, + platform: platform, + category: "social_sharing", + }); + }; + + const handleComment = async () => { + await track({ + eventName: "comment_article", + articleId: post.id, + category: "engagement", + }); + }; + + return ( + + {/* 페이지 뷰 트래킹 */} + + +
+

{post.title}

+

작성자: {post.author}

+ + {/* 본문 노출 트래킹 */} + +
{post.content}
+
+ + {/* 공유 버튼들 */} +
+ + + + + + + +
+ + {/* 댓글 버튼 */} + + + +
+
+ ); +} +``` + +### 3. 폼 및 사용자 입력 + +```tsx +// components/ContactForm.tsx +import { Track, useTracker } from "../tracker"; +import { useState } from "react"; + +function ContactForm() { + const { track } = useTracker(); + const [formData, setFormData] = useState({ + name: "", + email: "", + message: "", + }); + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + // 폼 제출 로직 + await submitForm(formData); + + // 성공 트래킹 + await track({ + eventName: "form_submit", + formId: "contact-form", + formType: "contact", + fieldCount: Object.keys(formData).length, + category: "conversion", + }); + } catch (error) { + // 실패 트래킹 + await track({ + eventName: "form_error", + formId: "contact-form", + errorType: error.message, + category: "error", + }); + } + }; + + return ( + +
+ {/* 이름 입력 필드 */} + + setFormData({ ...formData, name: e.target.value })} + placeholder="이름" + /> + + + {/* 이메일 입력 필드 */} + + setFormData({ ...formData, email: e.target.value })} + placeholder="이메일" + /> + + + {/* 메시지 입력 필드 */} + +