feat(stats): pro personalized handoff 추가

This commit is contained in:
2026-03-14 19:45:55 +09:00
parent c8b00905cd
commit 5d3a5ac8ac
8 changed files with 47 additions and 9 deletions

View File

@@ -80,6 +80,8 @@ export interface WeeklyReviewViewModel {
completionQuality: WeeklyReviewSection;
carryForward: {
hintKey: ReviewCarryHint;
presetId: string;
presetLabel: string;
keepDoing: string;
tryNext: string;
ctaLabel: string;
@@ -176,6 +178,8 @@ const buildCarryForward = (summary: FocusStatsSummary): WeeklyReviewViewModel['c
return {
hintKey,
presetId: 'forest-50-10',
presetLabel: 'Forest · 50/10 · Forest Birds',
keepDoing,
tryNext,
ctaLabel: copy.stats.reviewCarryCta,

View File

@@ -73,8 +73,10 @@ export const app = {
reviewCarryTryClosure: '시작은 있었지만 마무리가 약했어요. 다음 주에는 완료 직전에 다른 블록으로 넘어가지 않는 흐름을 한 번 만들어 보세요.',
reviewCarryTryStart: '시작 횟수가 적었어요. 다음 주에는 길이를 늘리기보다 첫 세션을 한 번 더 여는 것에 집중해 보세요.',
reviewCarryCta: '이 흐름으로 다음 세션 시작',
reviewCarryCtaPro: '가장 잘 맞은 ritual로 /app 돌아가기',
reviewCarryKeepTitle: '다음 주에 유지할 것',
reviewCarryTryTitle: '다음 주에 바꿔볼 것',
reviewCarryPresetLabel: '추천 ritual',
today: '오늘',
last7Days: '최근 7일',
chartTitle: '집중 흐름 그래프',

View File

@@ -42,6 +42,9 @@ const entryCopy = {
reviewTitle: '이번 주 review를 잠깐 보고 갈까요?',
reviewCta: '주간 review 보기',
reviewHelper: '다음 세션 전에 가볍게 보고 갈 수 있어요.',
reviewTitlePro: '나에게 잘 맞았던 흐름을 다시 보고 갈까요?',
reviewCtaPro: '나에게 맞는 흐름 보기',
reviewHelperPro: '가장 잘 맞았던 ritual과 carry-forward를 보고 돌아올 수 있어요.',
reviewReturnEyebrow: '방금 본 review 기준',
reviewReturnTitleSteady: '이번 주에 잘 맞았던 흐름을 그대로 가져가 보세요.',
reviewReturnTitleSmaller: '이번엔 목표를 더 작게 잡아보세요.',
@@ -148,7 +151,11 @@ export const FocusDashboardWidget = () => {
const reviewReturnCopy =
normalizedReviewCarryHint !== null ? reviewCarryCopyByHint[normalizedReviewCarryHint] : null;
const reviewReturnRitualLabel =
reviewEntryPreset === 'forest-50-10' ? entryCopy.reviewReturnRitualLabel : null;
isPro && reviewEntryPreset === 'forest-50-10' ? entryCopy.reviewReturnRitualLabel : null;
const reviewTeaserTitle = isPro ? entryCopy.reviewTitlePro : entryCopy.reviewTitle;
const reviewTeaserSummary = isPro ? review.carryForward.keepDoing : review.snapshotSummary;
const reviewTeaserHelper = isPro ? entryCopy.reviewHelperPro : entryCopy.reviewHelper;
const reviewTeaserCta = isPro ? entryCopy.reviewCtaPro : entryCopy.reviewCta;
useEffect(() => {
let cancelled = false;
@@ -417,15 +424,15 @@ export const FocusDashboardWidget = () => {
{entryCopy.reviewEyebrow}
</p>
<p className="mt-2 text-[1rem] font-medium tracking-[-0.02em] text-white/88">
{entryCopy.reviewTitle}
{reviewTeaserTitle}
</p>
<p className="mt-2 max-w-[34rem] text-[13px] leading-[1.6] text-white/62">
{review.snapshotSummary}
{reviewTeaserSummary}
</p>
<p className="mt-2 text-[12px] text-white/44">{entryCopy.reviewHelper}</p>
<p className="mt-2 text-[12px] text-white/44">{reviewTeaserHelper}</p>
</div>
<span className="inline-flex shrink-0 items-center text-[12px] font-medium tracking-[0.04em] text-white/74">
{entryCopy.reviewCta}
{reviewTeaserCta}
</span>
</div>
</Link>

View File

@@ -1,6 +1,7 @@
'use client';
import Link from 'next/link';
import { usePlanTier } from '@/entities/plan';
import { useFocusStats } from '@/features/stats';
import { copy } from '@/shared/i18n';
import { cn } from '@/shared/lib/cn';
@@ -79,7 +80,9 @@ const ReviewSection = ({
export const StatsOverviewWidget = () => {
const { stats } = copy;
const { isPro } = usePlanTier();
const { review, isLoading, error, source, refetch } = useFocusStats();
const carryForwardCtaLabel = isPro ? stats.reviewCarryCtaPro : review.carryForward.ctaLabel;
return (
<div className="min-h-screen bg-[radial-gradient(circle_at_14%_0%,rgba(198,219,244,0.52),transparent_42%),radial-gradient(circle_at_88%_12%,rgba(232,240,249,0.8),transparent_34%),linear-gradient(180deg,#f8fbff_0%,#f2f7fc_46%,#edf3f9_100%)] text-brand-dark">
@@ -193,11 +196,22 @@ export const StatsOverviewWidget = () => {
</p>
</div>
{isPro ? (
<div className="mt-5 rounded-2xl border border-brand-dark/8 bg-white/56 px-4 py-3">
<p className="text-[11px] font-medium tracking-[0.08em] text-brand-dark/42">
{stats.reviewCarryPresetLabel}
</p>
<p className="mt-2 text-[13px] font-medium text-brand-dark/82">
{review.carryForward.presetLabel}
</p>
</div>
) : null}
<Link
href={review.carryForward.ctaHref}
className="mt-6 inline-flex rounded-full border border-brand-dark/14 bg-brand-dark px-4 py-2.5 text-[12px] font-medium tracking-[0.04em] text-white transition hover:bg-brand-dark/92"
>
{review.carryForward.ctaLabel}
{carryForwardCtaLabel}
</Link>
</div>
</section>