Skip to content

Commit 8180409

Browse files
chore
1 parent 7e4f0ab commit 8180409

File tree

2 files changed

+246
-72
lines changed

2 files changed

+246
-72
lines changed

docs/platform/west/referral-ko.md

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# 🤠 와일드 WEST에 오신 걸 환영합니다 - 추천 캠페인을 위한 Grida WEST
2+
3+
재미있고 유연하며 강력한 추천 엔진입니다.
4+
5+
**Grida WEST**는 개발자에게 의존하지 않고도 빠르게 성장하고 더 나은 보상을 제공하는 캠페인을 마케터가 직접 시작할 수 있도록 설계된 차세대 추천 캠페인 플랫폼입니다. 물론 개발자도 안전하고 구조화된 백엔드를 사용할 수 있습니다.
6+
7+
---
8+
9+
## ✨ 만들 수 있는 것들
10+
11+
### 🪜 마일스톤 추천
12+
13+
_예시: "친구 5명을 초대하면 $5 크레딧, 10명을 초대하면 $15 크레딧 제공"_
14+
15+
보상이 점차 커지는 캠페인을 만들어 보세요. 참여자는 진척도를 느끼며 다음 목표를 향한 기대감을 가집니다. 일종의 게이미피케이션된 추천 사다리입니다.
16+
17+
### 🚀 스타트업 사전 예약 대기 리스트
18+
19+
_예시: "친구를 추천하면 대기열 순위가 올라갑니다."_
20+
21+
런칭에 적합합니다. 초반 입소문과 바이럴 공유를 유도할 수 있습니다. 초대된 사람도 친구를 데려오면 순위가 올라갑니다.
22+
23+
### 📬 뉴스레터 추천
24+
25+
_예시: "3명을 추천하면 Pro 시리즈를 이용할 수 있어요."_
26+
27+
콘텐츠, 쿠폰, 굿즈 등으로 독자에게 보상을 제공합니다. Mailchimp, Beehiiv 등과 잘 통합됩니다.
28+
29+
### 🎁 바이럴 경품 이벤트
30+
31+
_예시: "이벤트에 참여하고 친구를 초대하면 당첨 확률이 올라갑니다."_
32+
33+
경품 이벤트의 확산력을 키워줍니다. 누가 누구를 초대했는지 추적하고, 초대 횟수에 따라 확률을 높입니다. 보상은 추첨, 디지털 아이템, 명예 등 다양합니다.
34+
35+
### 🛍️ Shopify 캠페인
36+
37+
_예시: "친구를 추천하면 두 사람 모두 10% 할인"_
38+
39+
WEST를 스토어에 설치해 양방향 추천 프로그램을 바로 시작할 수 있습니다. 포인트, 크레딧, 쿠폰 등 사용자 정의 보상 로직과 잘 작동합니다.
40+
41+
### 🤝 양방향 클래식
42+
43+
_예시: "친구를 추천하면 내가 $10, 친구는 $5 받기"_
44+
45+
공개 또는 비공개 초대 코드를 사용할 수 있습니다. WEST는 셀프 초대 및 추천인 초대 흐름을 모두 지원합니다. 공개 범위도 선택할 수 있습니다.
46+
47+
---
48+
49+
## 🧰 작동 방식
50+
51+
모든 WEST 캠페인의 중심 요소:
52+
53+
### 🎟️ 추천인
54+
55+
- 추천 코드를 공유하는 사용자
56+
- 캠페인마다 고유 코드 보유
57+
- 초대 수 제한 가능
58+
59+
### 📩 초대
60+
61+
- 추적 가능한 링크/코드
62+
- 청구되기 전까지는 새로 고칠 수 있음
63+
- 청구되면 초대받은 사람은 캠페인에 참여하게 됨
64+
65+
### 🪙 보상
66+
67+
- **마일스톤 보상**: 일정 초대 수 도달 시 추천인에게 제공
68+
- **온보딩 보상**: 초대받은 사람이 퀘스트를 완료하면 제공
69+
- 모든 보상은 _교환 토큰_ 형태로 발급되며, 나중에 청구 가능
70+
71+
### 🧩 챌린지 / 퀘스트
72+
73+
- 온보딩을 완료하기 위한 요구 작업 정의
74+
- 예시: "회원가입", "구매", "이메일 인증" 등
75+
- 의존성으로 연결 가능
76+
77+
### 📊 분석
78+
79+
- 모든 이벤트의 실시간 로그
80+
- 이벤트 빈도, 캠페인 성장, 추천 관계 시각화
81+
82+
### 🌐 내장 페이지
83+
84+
- 공유용 랜딩 페이지
85+
- 고객용 진행 현황 대시보드
86+
- 선택적 공개 리더보드
87+
88+
---
89+
90+
## 🙋 FAQ
91+
92+
### Q: 아무나 캠페인을 시작할 수 있나요?
93+
94+
네, 프로젝트 소유자라면 누구나 가능합니다. 프로젝트에 연결된 캠페인을 생성, 비활성화/활성화, 관리할 수 있습니다.
95+
96+
### Q: 초대자 또는 추천인의 정보를 숨길 수 있나요?
97+
98+
네. 이름이나 아바타 노출 여부를 설정할 수 있습니다. 기본값은 비공개입니다.
99+
100+
### Q: 사용자가 자신을 초대할 수 있나요?
101+
102+
원하신다면 가능합니다. 셀프 초대 모드에서는 공개 코드를 사용해 자신의 초대를 생성합니다.
103+
104+
### Q: 추천인이 직접 초대를 생성하게 할 수 있나요?
105+
106+
가능합니다. 추천인 초대 모드입니다. 추천인이 초대를 생성하고 직접 공유합니다.
107+
108+
### Q: 초대 링크를 새로 고칠 수 있나요?
109+
110+
예. 청구되지 않은 초대는 새 코드로 갱신할 수 있으며, 이전 코드는 무효화됩니다.
111+
112+
### Q: 보안은 어떤가요?
113+
114+
안전합니다. 모든 캠페인 로직은 Row-Level Security(RLS)로 DB 수준에서 강제됩니다. 코드는 항상 캠페인 컨텍스트에 연결됩니다.
115+
116+
---
117+
118+
## 🧠 고급 기능
119+
120+
WEST는 PostgreSQL과 Supabase 위에 구축되어 있으며, 개발자를 위한 안전하고 확장 가능한 데이터 모델을 제공합니다.
121+
122+
### 🛡️ 완전한 RLS 적용
123+
124+
모든 테이블은 RLS가 활성화되어 있습니다. 접근은 캠페인/프로젝트 단위로 범위가 설정됩니다. 다중 테넌트 및 공개 사용에도 안전합니다.
125+
126+
### 🔗 고유 코드 처리
127+
128+
모든 코드(추천인/초대자)는 캠페인 내에서 고유함이 보장됩니다. 트리거와 함께 `code` 레지스트리 테이블에 저장됩니다.
129+
130+
### 📬 `track()`을 통한 이벤트 후킹
131+
132+
`track()` 함수를 호출하여 사용자 활동을 추적하세요. `event_log` 하이퍼테이블에 로그가 기록되며 시간 기반 분석이 가능합니다.
133+
134+
### 📈 내장 분석 API
135+
136+
`analyze()`를 사용하여 이벤트 이름 기준의 시간 단위 통계 데이터를 얻을 수 있습니다. 대시보드나 퍼널 시각화에 적합합니다.
137+
138+
### 🧪 교환 토큰으로서의 보상
139+
140+
보상 정의는 실질적 교환과 분리되어 있습니다. 즉, "획득했지만 아직 전달되지 않은" 상태로, 인벤토리나 상품 관리를 유연하게 합니다.
141+
142+
### 🧱 초대 흐름 함수
143+
144+
핵심 흐름은 SQL 함수로 처리됩니다:
145+
146+
- `invite()` – 새 초대 생성
147+
- `refresh()` – 초대 코드 재생성
148+
- `claim()` – 초대 청구 처리
149+
- `flag()` – 퀘스트/챌린지 진행 기록
150+
151+
---
152+
153+
## 💼 개발자 / 연동
154+
155+
- Supabase에서 직접 SQL 함수 호출 또는 REST/RPC 엔드포인트 사용
156+
- 스키마: `grida_west_referral`
157+
- 테이블: `campaign`, `referrer`, `invitation`, `onboarding`, `event_log`
158+
- 공개용 뷰: `referrer_public_secure`, `invitation_public_secure`, `campaign_public`
159+
- 웹훅 연동 및 Zapier 호환 플로우 문서 예정
160+
161+
---
162+
163+
## 🔚 마무리
164+
165+
Grida WEST는 헤드리스 추천 엔진의 유연함, 검증된 스키마의 안전함, 그리고 코드 걱정 없이 캠페인을 설정하는 즐거움을 제공합니다.
166+
167+
**지금 바로 첫 캠페인을 시작하세요.**
168+
추적하고, 공유하고, 당신의 챔피언에게 보상하세요.
169+
170+
WEST는 거칩니다 — 하지만 이제는 당신의 것입니다.
171+
172+
🧨 달려봅시다.

editor/app/(demo)/r/[slug]/t/[code]/_invite/index.tsx

+74-72
Original file line numberDiff line numberDiff line change
@@ -254,81 +254,83 @@ export default function ReferrerPage({
254254
</Card>
255255
</Standard.Section>
256256

257-
<Standard.Section>
258-
<Card className="relative overflow-hidden rounded-xl py-2 border-0">
259-
{invitations?.map((inv, index) => (
260-
<motion.div
261-
key={inv.id}
262-
initial={{ opacity: 0, y: 20 }}
263-
animate={{ opacity: 1, y: 0 }}
264-
transition={{ duration: 0.3, delay: index * 0.1 }}
265-
>
266-
<div className="overflow-hidden transition-all">
267-
<CardContent className="px-4 py-2">
268-
<div className="flex justify-between items-center">
269-
<div className="flex items-center gap-2">
270-
<div className="max-w-[180px] font-medium truncate text-muted-foreground">
271-
{"#" + (index + 1)}
272-
</div>
273-
{inv.is_claimed ? (
274-
<div className="flex items-center gap-2">
275-
<Avatar className="size-10">
276-
<AvatarFallback>
277-
{inv.invitee_name?.charAt(0) ?? "?"}
278-
</AvatarFallback>
279-
</Avatar>
280-
<span className="text-sm font-semibold">
281-
{inv.invitee_name ?? "?"}
282-
</span>
257+
{invitations.length > 0 && (
258+
<Standard.Section>
259+
<Card className="relative overflow-hidden rounded-xl py-2 border-0">
260+
{invitations?.map((inv, index) => (
261+
<motion.div
262+
key={inv.id}
263+
initial={{ opacity: 0, y: 20 }}
264+
animate={{ opacity: 1, y: 0 }}
265+
transition={{ duration: 0.3, delay: index * 0.1 }}
266+
>
267+
<div className="overflow-hidden transition-all">
268+
<CardContent className="px-4 py-2">
269+
<div className="flex justify-between items-center">
270+
<div className="flex items-center gap-2">
271+
<div className="max-w-[180px] font-medium truncate text-muted-foreground">
272+
{"#" + (index + 1)}
283273
</div>
284-
) : (
285-
<div className="flex items-center gap-2">
286-
<Avatar className="size-10">
287-
<AvatarFallback>?</AvatarFallback>
288-
</Avatar>
289-
<Button
290-
variant="outline"
291-
size="sm"
292-
onClick={() => {
293-
reshare({
294-
campaign_slug: campaign.slug,
295-
referrer_code: code!,
296-
referrer_name,
297-
invitation_id: inv.id,
298-
}).then((sharable) => {
299-
share_or_copy(sharable).then(
300-
({ type }) => {
301-
//
302-
switch (type) {
303-
case "share":
304-
toast.success(
305-
"초대권이 재전송 되었습니다!"
306-
);
307-
break;
308-
case "clipboard":
309-
toast.success(
310-
"초대권이 복사되었습니다!"
311-
);
312-
break;
274+
{inv.is_claimed ? (
275+
<div className="flex items-center gap-2">
276+
<Avatar className="size-10">
277+
<AvatarFallback>
278+
{inv.invitee_name?.charAt(0) ?? "?"}
279+
</AvatarFallback>
280+
</Avatar>
281+
<span className="text-sm font-semibold">
282+
{inv.invitee_name ?? "?"}
283+
</span>
284+
</div>
285+
) : (
286+
<div className="flex items-center gap-2">
287+
<Avatar className="size-10">
288+
<AvatarFallback>?</AvatarFallback>
289+
</Avatar>
290+
<Button
291+
variant="outline"
292+
size="sm"
293+
onClick={() => {
294+
reshare({
295+
campaign_slug: campaign.slug,
296+
referrer_code: code!,
297+
referrer_name,
298+
invitation_id: inv.id,
299+
}).then((sharable) => {
300+
share_or_copy(sharable).then(
301+
({ type }) => {
302+
//
303+
switch (type) {
304+
case "share":
305+
toast.success(
306+
"초대권이 재전송 되었습니다!"
307+
);
308+
break;
309+
case "clipboard":
310+
toast.success(
311+
"초대권이 복사되었습니다!"
312+
);
313+
break;
314+
}
313315
}
314-
}
315-
);
316-
});
317-
}}
318-
>
319-
다시 전송
320-
</Button>
321-
</div>
322-
)}
316+
);
317+
});
318+
}}
319+
>
320+
다시 전송
321+
</Button>
322+
</div>
323+
)}
324+
</div>
325+
<StatusIndicator invitation={inv} />
323326
</div>
324-
<StatusIndicator invitation={inv} />
325-
</div>
326-
</CardContent>
327-
</div>
328-
</motion.div>
329-
))}
330-
</Card>
331-
</Standard.Section>
327+
</CardContent>
328+
</div>
329+
</motion.div>
330+
))}
331+
</Card>
332+
</Standard.Section>
333+
)}
332334

333335
<Standard.Section>
334336
<header className="border-b py-2 my-4 text-sm text-muted-foreground">

0 commit comments

Comments
 (0)