Initial commit: Portfolio Website
Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,784 @@
|
||||
# Customer Success Automation
|
||||
|
||||
**Meta-Description:** Customer Success automatisieren. Health Scores, Lifecycle Emails, Churn Prediction und Proactive Outreach für SaaS.
|
||||
|
||||
**Keywords:** Customer Success, Health Score, Churn Prediction, Lifecycle Marketing, Customer Retention, Automation, NPS
|
||||
|
||||
---
|
||||
|
||||
## Einführung
|
||||
|
||||
**Customer Success** ist der Schlüssel zu Retention und Expansion. Mit **Automation** können Health Scores berechnet, Risiko-Kunden identifiziert und proaktive Maßnahmen ergriffen werden – skalierbar für tausende Kunden.
|
||||
|
||||
---
|
||||
|
||||
## Customer Success Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ CUSTOMER SUCCESS AUTOMATION │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Customer Health Score: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Score Components (0-100): │ │
|
||||
│ │ ├── Product Usage (40%) │ │
|
||||
│ │ │ ├── Login frequency │ │
|
||||
│ │ │ ├── Feature adoption │ │
|
||||
│ │ │ └── Active users/seats │ │
|
||||
│ │ ├── Engagement (30%) │ │
|
||||
│ │ │ ├── Support tickets │ │
|
||||
│ │ │ ├── NPS score │ │
|
||||
│ │ │ └── Training completion │ │
|
||||
│ │ └── Business Value (30%) │ │
|
||||
│ │ ├── ROI achieved │ │
|
||||
│ │ ├── Expansion potential │ │
|
||||
│ │ └── Contract value │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Lifecycle Stages: │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Onboarding → Adoption → Growth → Renewal │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ ↓ ↓ ↓ ↓ │ │
|
||||
│ │ Welcome Training Upsell Review │ │
|
||||
│ │ Emails Content Offers Campaign │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Risk Triggers: │
|
||||
│ ├── Login decrease > 50% │
|
||||
│ ├── Support tickets spike │
|
||||
│ ├── Key user departure │
|
||||
│ ├── Contract renewal in 60 days │
|
||||
│ └── Low NPS score (< 7) │
|
||||
│ │
|
||||
│ Automated Actions: │
|
||||
│ ├── Email sequences │
|
||||
│ ├── In-app messages │
|
||||
│ ├── CSM alerts │
|
||||
│ ├── Scheduled calls │
|
||||
│ └── Personalized offers │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Health Score Calculation
|
||||
|
||||
```typescript
|
||||
// lib/customer-success/health-score.ts
|
||||
import { db } from '@/lib/db';
|
||||
import { subDays, differenceInDays } from 'date-fns';
|
||||
|
||||
interface HealthScoreWeights {
|
||||
usage: number;
|
||||
engagement: number;
|
||||
business: number;
|
||||
}
|
||||
|
||||
interface HealthScoreComponents {
|
||||
usage: {
|
||||
loginScore: number;
|
||||
featureAdoptionScore: number;
|
||||
activeUsersScore: number;
|
||||
};
|
||||
engagement: {
|
||||
supportScore: number;
|
||||
npsScore: number;
|
||||
trainingScore: number;
|
||||
};
|
||||
business: {
|
||||
contractValueScore: number;
|
||||
expansionScore: number;
|
||||
tenureScore: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface HealthScore {
|
||||
score: number;
|
||||
trend: 'up' | 'down' | 'stable';
|
||||
riskLevel: 'healthy' | 'attention' | 'at-risk' | 'critical';
|
||||
components: HealthScoreComponents;
|
||||
lastUpdated: Date;
|
||||
}
|
||||
|
||||
const DEFAULT_WEIGHTS: HealthScoreWeights = {
|
||||
usage: 0.4,
|
||||
engagement: 0.3,
|
||||
business: 0.3
|
||||
};
|
||||
|
||||
export async function calculateHealthScore(
|
||||
customerId: string,
|
||||
weights: HealthScoreWeights = DEFAULT_WEIGHTS
|
||||
): Promise<HealthScore> {
|
||||
const now = new Date();
|
||||
const thirtyDaysAgo = subDays(now, 30);
|
||||
|
||||
// Get customer data
|
||||
const customer = await db.customer.findUnique({
|
||||
where: { id: customerId },
|
||||
include: {
|
||||
subscription: true,
|
||||
users: true,
|
||||
supportTickets: {
|
||||
where: { createdAt: { gte: thirtyDaysAgo } }
|
||||
},
|
||||
npsResponses: {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!customer) {
|
||||
throw new Error('Customer not found');
|
||||
}
|
||||
|
||||
// Calculate usage scores
|
||||
const loginScore = await calculateLoginScore(customerId, thirtyDaysAgo);
|
||||
const featureAdoptionScore = await calculateFeatureAdoptionScore(customerId);
|
||||
const activeUsersScore = calculateActiveUsersScore(customer);
|
||||
|
||||
const usageScore = (loginScore + featureAdoptionScore + activeUsersScore) / 3;
|
||||
|
||||
// Calculate engagement scores
|
||||
const supportScore = calculateSupportScore(customer.supportTickets);
|
||||
const npsScore = calculateNPSHealthScore(customer.npsResponses[0]?.score);
|
||||
const trainingScore = await calculateTrainingScore(customerId);
|
||||
|
||||
const engagementScore = (supportScore + npsScore + trainingScore) / 3;
|
||||
|
||||
// Calculate business scores
|
||||
const contractValueScore = calculateContractValueScore(customer.subscription);
|
||||
const expansionScore = await calculateExpansionScore(customerId);
|
||||
const tenureScore = calculateTenureScore(customer.createdAt);
|
||||
|
||||
const businessScore = (contractValueScore + expansionScore + tenureScore) / 3;
|
||||
|
||||
// Weighted total
|
||||
const totalScore = Math.round(
|
||||
usageScore * weights.usage +
|
||||
engagementScore * weights.engagement +
|
||||
businessScore * weights.business
|
||||
);
|
||||
|
||||
// Calculate trend
|
||||
const previousScore = await getPreviousHealthScore(customerId);
|
||||
const trend = getTrend(totalScore, previousScore);
|
||||
|
||||
// Determine risk level
|
||||
const riskLevel = getRiskLevel(totalScore);
|
||||
|
||||
const healthScore: HealthScore = {
|
||||
score: totalScore,
|
||||
trend,
|
||||
riskLevel,
|
||||
components: {
|
||||
usage: { loginScore, featureAdoptionScore, activeUsersScore },
|
||||
engagement: { supportScore, npsScore, trainingScore },
|
||||
business: { contractValueScore, expansionScore, tenureScore }
|
||||
},
|
||||
lastUpdated: now
|
||||
};
|
||||
|
||||
// Store for history
|
||||
await db.healthScoreHistory.create({
|
||||
data: {
|
||||
customerId,
|
||||
score: totalScore,
|
||||
components: healthScore.components,
|
||||
calculatedAt: now
|
||||
}
|
||||
});
|
||||
|
||||
return healthScore;
|
||||
}
|
||||
|
||||
async function calculateLoginScore(
|
||||
customerId: string,
|
||||
since: Date
|
||||
): Promise<number> {
|
||||
const logins = await db.userActivity.count({
|
||||
where: {
|
||||
user: { customerId },
|
||||
activityType: 'login',
|
||||
createdAt: { gte: since }
|
||||
}
|
||||
});
|
||||
|
||||
// Expected: at least 20 logins per month for healthy usage
|
||||
return Math.min(100, (logins / 20) * 100);
|
||||
}
|
||||
|
||||
async function calculateFeatureAdoptionScore(
|
||||
customerId: string
|
||||
): Promise<number> {
|
||||
const totalFeatures = await db.feature.count({
|
||||
where: { isCore: true }
|
||||
});
|
||||
|
||||
const usedFeatures = await db.featureUsage.groupBy({
|
||||
by: ['featureId'],
|
||||
where: {
|
||||
user: { customerId }
|
||||
}
|
||||
});
|
||||
|
||||
return Math.round((usedFeatures.length / totalFeatures) * 100);
|
||||
}
|
||||
|
||||
function calculateActiveUsersScore(customer: any): number {
|
||||
const totalSeats = customer.subscription?.seats || 1;
|
||||
const activeUsers = customer.users.filter((u: any) =>
|
||||
u.lastActiveAt && differenceInDays(new Date(), u.lastActiveAt) < 7
|
||||
).length;
|
||||
|
||||
return Math.round((activeUsers / totalSeats) * 100);
|
||||
}
|
||||
|
||||
function calculateSupportScore(tickets: any[]): number {
|
||||
if (tickets.length === 0) return 100;
|
||||
|
||||
// Fewer tickets = higher score, but some engagement is good
|
||||
const ticketCount = tickets.length;
|
||||
if (ticketCount <= 2) return 100;
|
||||
if (ticketCount <= 5) return 80;
|
||||
if (ticketCount <= 10) return 60;
|
||||
return 40;
|
||||
}
|
||||
|
||||
function calculateNPSHealthScore(npsScore?: number): number {
|
||||
if (npsScore === undefined) return 50; // Neutral if no data
|
||||
if (npsScore >= 9) return 100;
|
||||
if (npsScore >= 7) return 75;
|
||||
if (npsScore >= 5) return 50;
|
||||
return 25;
|
||||
}
|
||||
|
||||
function getRiskLevel(score: number): HealthScore['riskLevel'] {
|
||||
if (score >= 80) return 'healthy';
|
||||
if (score >= 60) return 'attention';
|
||||
if (score >= 40) return 'at-risk';
|
||||
return 'critical';
|
||||
}
|
||||
|
||||
function getTrend(current: number, previous?: number): HealthScore['trend'] {
|
||||
if (!previous) return 'stable';
|
||||
const diff = current - previous;
|
||||
if (diff > 5) return 'up';
|
||||
if (diff < -5) return 'down';
|
||||
return 'stable';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lifecycle Email Automation
|
||||
|
||||
```typescript
|
||||
// lib/customer-success/lifecycle.ts
|
||||
import { db } from '@/lib/db';
|
||||
import { addDays, differenceInDays } from 'date-fns';
|
||||
|
||||
interface LifecycleStage {
|
||||
id: string;
|
||||
name: string;
|
||||
triggers: TriggerCondition[];
|
||||
actions: Action[];
|
||||
}
|
||||
|
||||
interface TriggerCondition {
|
||||
type: 'days_since_signup' | 'event' | 'health_score' | 'subscription';
|
||||
value: any;
|
||||
}
|
||||
|
||||
interface Action {
|
||||
type: 'email' | 'in_app' | 'slack_notify' | 'task';
|
||||
template: string;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
const lifecycleStages: LifecycleStage[] = [
|
||||
{
|
||||
id: 'onboarding',
|
||||
name: 'Onboarding',
|
||||
triggers: [{ type: 'days_since_signup', value: { min: 0, max: 14 } }],
|
||||
actions: [
|
||||
{ type: 'email', template: 'welcome', delay: 0 },
|
||||
{ type: 'email', template: 'getting-started', delay: 1 },
|
||||
{ type: 'email', template: 'first-project', delay: 3 },
|
||||
{ type: 'email', template: 'invite-team', delay: 7 },
|
||||
{ type: 'email', template: 'onboarding-check-in', delay: 14 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'adoption',
|
||||
name: 'Adoption',
|
||||
triggers: [
|
||||
{ type: 'days_since_signup', value: { min: 14, max: 60 } },
|
||||
{ type: 'event', value: 'onboarding_completed' }
|
||||
],
|
||||
actions: [
|
||||
{ type: 'email', template: 'feature-spotlight-1', delay: 0 },
|
||||
{ type: 'email', template: 'success-story', delay: 7 },
|
||||
{ type: 'email', template: 'feature-spotlight-2', delay: 14 },
|
||||
{ type: 'email', template: 'tips-tricks', delay: 21 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'growth',
|
||||
name: 'Growth',
|
||||
triggers: [
|
||||
{ type: 'days_since_signup', value: { min: 60 } },
|
||||
{ type: 'health_score', value: { min: 70 } }
|
||||
],
|
||||
actions: [
|
||||
{ type: 'email', template: 'upgrade-benefits', delay: 0 },
|
||||
{ type: 'email', template: 'case-study', delay: 14 },
|
||||
{ type: 'in_app', template: 'upgrade-prompt', delay: 7 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'renewal',
|
||||
name: 'Renewal',
|
||||
triggers: [
|
||||
{ type: 'subscription', value: 'renewal_in_60_days' }
|
||||
],
|
||||
actions: [
|
||||
{ type: 'email', template: 'renewal-reminder-60', delay: 0 },
|
||||
{ type: 'task', template: 'csm-outreach', delay: 0 },
|
||||
{ type: 'email', template: 'renewal-reminder-30', delay: 30 },
|
||||
{ type: 'email', template: 'renewal-reminder-7', delay: 53 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'at-risk',
|
||||
name: 'At Risk',
|
||||
triggers: [
|
||||
{ type: 'health_score', value: { max: 40 } }
|
||||
],
|
||||
actions: [
|
||||
{ type: 'slack_notify', template: 'at-risk-alert', delay: 0 },
|
||||
{ type: 'email', template: 'we-miss-you', delay: 1 },
|
||||
{ type: 'task', template: 'csm-intervention', delay: 0 },
|
||||
{ type: 'email', template: 'how-can-we-help', delay: 3 }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Process lifecycle for all customers
|
||||
export async function processLifecycleAutomation(): Promise<void> {
|
||||
const customers = await db.customer.findMany({
|
||||
include: {
|
||||
subscription: true,
|
||||
healthScore: true,
|
||||
lifecycleState: true
|
||||
}
|
||||
});
|
||||
|
||||
for (const customer of customers) {
|
||||
await processCustomerLifecycle(customer);
|
||||
}
|
||||
}
|
||||
|
||||
async function processCustomerLifecycle(customer: any): Promise<void> {
|
||||
// Determine current stage
|
||||
const currentStage = determineLifecycleStage(customer);
|
||||
|
||||
if (!currentStage) return;
|
||||
|
||||
// Check if stage changed
|
||||
const previousStage = customer.lifecycleState?.stageId;
|
||||
|
||||
if (currentStage.id !== previousStage) {
|
||||
// Update stage
|
||||
await db.customerLifecycleState.upsert({
|
||||
where: { customerId: customer.id },
|
||||
create: {
|
||||
customerId: customer.id,
|
||||
stageId: currentStage.id,
|
||||
enteredAt: new Date()
|
||||
},
|
||||
update: {
|
||||
stageId: currentStage.id,
|
||||
enteredAt: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
// Execute stage entry actions
|
||||
for (const action of currentStage.actions) {
|
||||
await scheduleAction(customer.id, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function determineLifecycleStage(customer: any): LifecycleStage | null {
|
||||
const daysSinceSignup = differenceInDays(new Date(), customer.createdAt);
|
||||
const healthScore = customer.healthScore?.score || 50;
|
||||
const renewalDays = customer.subscription?.currentPeriodEnd
|
||||
? differenceInDays(customer.subscription.currentPeriodEnd, new Date())
|
||||
: null;
|
||||
|
||||
// Check stages in priority order
|
||||
if (healthScore < 40) {
|
||||
return lifecycleStages.find(s => s.id === 'at-risk')!;
|
||||
}
|
||||
|
||||
if (renewalDays !== null && renewalDays <= 60) {
|
||||
return lifecycleStages.find(s => s.id === 'renewal')!;
|
||||
}
|
||||
|
||||
if (daysSinceSignup < 14) {
|
||||
return lifecycleStages.find(s => s.id === 'onboarding')!;
|
||||
}
|
||||
|
||||
if (daysSinceSignup < 60) {
|
||||
return lifecycleStages.find(s => s.id === 'adoption')!;
|
||||
}
|
||||
|
||||
return lifecycleStages.find(s => s.id === 'growth')!;
|
||||
}
|
||||
|
||||
async function scheduleAction(
|
||||
customerId: string,
|
||||
action: Action
|
||||
): Promise<void> {
|
||||
const scheduledFor = action.delay
|
||||
? addDays(new Date(), action.delay)
|
||||
: new Date();
|
||||
|
||||
await db.scheduledAction.create({
|
||||
data: {
|
||||
customerId,
|
||||
actionType: action.type,
|
||||
template: action.template,
|
||||
scheduledFor,
|
||||
status: 'pending'
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Churn Prediction
|
||||
|
||||
```typescript
|
||||
// lib/customer-success/churn-prediction.ts
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
interface ChurnPrediction {
|
||||
customerId: string;
|
||||
churnProbability: number;
|
||||
riskFactors: RiskFactor[];
|
||||
recommendedActions: string[];
|
||||
}
|
||||
|
||||
interface RiskFactor {
|
||||
factor: string;
|
||||
impact: 'high' | 'medium' | 'low';
|
||||
currentValue: number;
|
||||
threshold: number;
|
||||
}
|
||||
|
||||
export async function predictChurn(customerId: string): Promise<ChurnPrediction> {
|
||||
const riskFactors: RiskFactor[] = [];
|
||||
let churnScore = 0;
|
||||
|
||||
// Factor 1: Login frequency decline
|
||||
const loginDecline = await calculateLoginDecline(customerId);
|
||||
if (loginDecline > 50) {
|
||||
riskFactors.push({
|
||||
factor: 'Login frequency decreased',
|
||||
impact: 'high',
|
||||
currentValue: loginDecline,
|
||||
threshold: 50
|
||||
});
|
||||
churnScore += 25;
|
||||
}
|
||||
|
||||
// Factor 2: Feature usage decline
|
||||
const featureDecline = await calculateFeatureUsageDecline(customerId);
|
||||
if (featureDecline > 30) {
|
||||
riskFactors.push({
|
||||
factor: 'Feature usage decreased',
|
||||
impact: 'medium',
|
||||
currentValue: featureDecline,
|
||||
threshold: 30
|
||||
});
|
||||
churnScore += 15;
|
||||
}
|
||||
|
||||
// Factor 3: Support sentiment
|
||||
const supportSentiment = await analyzeSupportSentiment(customerId);
|
||||
if (supportSentiment < 0.3) {
|
||||
riskFactors.push({
|
||||
factor: 'Negative support interactions',
|
||||
impact: 'high',
|
||||
currentValue: supportSentiment,
|
||||
threshold: 0.3
|
||||
});
|
||||
churnScore += 20;
|
||||
}
|
||||
|
||||
// Factor 4: NPS score
|
||||
const npsScore = await getLatestNPS(customerId);
|
||||
if (npsScore !== null && npsScore < 7) {
|
||||
riskFactors.push({
|
||||
factor: 'Low NPS score',
|
||||
impact: npsScore < 5 ? 'high' : 'medium',
|
||||
currentValue: npsScore,
|
||||
threshold: 7
|
||||
});
|
||||
churnScore += npsScore < 5 ? 25 : 15;
|
||||
}
|
||||
|
||||
// Factor 5: Key user departure
|
||||
const keyUserLeft = await checkKeyUserDeparture(customerId);
|
||||
if (keyUserLeft) {
|
||||
riskFactors.push({
|
||||
factor: 'Key user left organization',
|
||||
impact: 'high',
|
||||
currentValue: 1,
|
||||
threshold: 0
|
||||
});
|
||||
churnScore += 20;
|
||||
}
|
||||
|
||||
// Factor 6: Contract value change
|
||||
const contractDecreased = await checkContractDecrease(customerId);
|
||||
if (contractDecreased) {
|
||||
riskFactors.push({
|
||||
factor: 'Contract value decreased',
|
||||
impact: 'medium',
|
||||
currentValue: 1,
|
||||
threshold: 0
|
||||
});
|
||||
churnScore += 15;
|
||||
}
|
||||
|
||||
const churnProbability = Math.min(100, churnScore);
|
||||
|
||||
// Generate recommended actions
|
||||
const recommendedActions = generateRecommendations(riskFactors);
|
||||
|
||||
return {
|
||||
customerId,
|
||||
churnProbability,
|
||||
riskFactors,
|
||||
recommendedActions
|
||||
};
|
||||
}
|
||||
|
||||
function generateRecommendations(riskFactors: RiskFactor[]): string[] {
|
||||
const recommendations: string[] = [];
|
||||
|
||||
const factorTypes = riskFactors.map(f => f.factor);
|
||||
|
||||
if (factorTypes.includes('Login frequency decreased')) {
|
||||
recommendations.push('Schedule re-engagement call');
|
||||
recommendations.push('Send personalized win-back email');
|
||||
}
|
||||
|
||||
if (factorTypes.includes('Feature usage decreased')) {
|
||||
recommendations.push('Offer feature training session');
|
||||
recommendations.push('Share relevant use case examples');
|
||||
}
|
||||
|
||||
if (factorTypes.includes('Negative support interactions')) {
|
||||
recommendations.push('Escalate to senior support');
|
||||
recommendations.push('CSM personal outreach');
|
||||
}
|
||||
|
||||
if (factorTypes.includes('Low NPS score')) {
|
||||
recommendations.push('Schedule NPS follow-up call');
|
||||
recommendations.push('Address specific feedback');
|
||||
}
|
||||
|
||||
if (factorTypes.includes('Key user left organization')) {
|
||||
recommendations.push('Identify and onboard new champion');
|
||||
recommendations.push('Executive sponsor outreach');
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
// Batch process all customers
|
||||
export async function runChurnPredictions(): Promise<void> {
|
||||
const customers = await db.customer.findMany({
|
||||
where: { subscription: { status: 'active' } }
|
||||
});
|
||||
|
||||
for (const customer of customers) {
|
||||
const prediction = await predictChurn(customer.id);
|
||||
|
||||
// Store prediction
|
||||
await db.churnPrediction.upsert({
|
||||
where: { customerId: customer.id },
|
||||
create: {
|
||||
customerId: customer.id,
|
||||
probability: prediction.churnProbability,
|
||||
riskFactors: prediction.riskFactors,
|
||||
predictedAt: new Date()
|
||||
},
|
||||
update: {
|
||||
probability: prediction.churnProbability,
|
||||
riskFactors: prediction.riskFactors,
|
||||
predictedAt: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
// Alert if high risk
|
||||
if (prediction.churnProbability > 70) {
|
||||
await createChurnAlert(customer.id, prediction);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## NPS Survey Automation
|
||||
|
||||
```tsx
|
||||
// components/NPS/NPSSurvey.tsx
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
interface NPSSurveyProps {
|
||||
onSubmit: (score: number, feedback?: string) => void;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export function NPSSurvey({ onSubmit, onDismiss }: NPSSurveyProps) {
|
||||
const [score, setScore] = useState<number | null>(null);
|
||||
const [feedback, setFeedback] = useState('');
|
||||
const [step, setStep] = useState<'score' | 'feedback'>('score');
|
||||
|
||||
const handleScoreSelect = (value: number) => {
|
||||
setScore(value);
|
||||
setStep('feedback');
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (score !== null) {
|
||||
onSubmit(score, feedback || undefined);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 right-4 w-96 bg-white rounded-lg shadow-xl p-6">
|
||||
<button
|
||||
onClick={onDismiss}
|
||||
className="absolute top-2 right-2 text-gray-400"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
|
||||
{step === 'score' ? (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
How likely are you to recommend us?
|
||||
</h3>
|
||||
|
||||
<div className="flex gap-1 mb-4">
|
||||
{Array.from({ length: 11 }, (_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => handleScoreSelect(i)}
|
||||
className={`w-8 h-8 rounded text-sm font-medium transition ${
|
||||
i <= 6 ? 'hover:bg-red-100' :
|
||||
i <= 8 ? 'hover:bg-yellow-100' : 'hover:bg-green-100'
|
||||
} ${score === i ? 'ring-2 ring-blue-500' : 'bg-gray-100'}`}
|
||||
>
|
||||
{i}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between text-xs text-gray-500">
|
||||
<span>Not at all likely</span>
|
||||
<span>Extremely likely</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold mb-2">
|
||||
{score! >= 9 ? "That's great! What do you love most?" :
|
||||
score! >= 7 ? "Thanks! How can we improve?" :
|
||||
"We're sorry to hear that. What went wrong?"}
|
||||
</h3>
|
||||
|
||||
<textarea
|
||||
value={feedback}
|
||||
onChange={(e) => setFeedback(e.target.value)}
|
||||
placeholder="Your feedback helps us improve..."
|
||||
className="w-full p-3 border rounded-lg mb-4"
|
||||
rows={3}
|
||||
/>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setStep('score')}
|
||||
className="px-4 py-2 text-gray-600"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
| Area | Recommendation |
|
||||
|------|----------------|
|
||||
| **Health Score** | Update daily, weight factors appropriately |
|
||||
| **Lifecycle** | Personalize based on segment and behavior |
|
||||
| **Churn** | Act on predictions within 48 hours |
|
||||
| **NPS** | Survey at key moments, not randomly |
|
||||
| **Automation** | Always allow human override |
|
||||
|
||||
---
|
||||
|
||||
## Fazit
|
||||
|
||||
Customer Success Automation ermöglicht:
|
||||
|
||||
1. **Health Scores**: Frühzeitige Risiko-Erkennung
|
||||
2. **Lifecycle Emails**: Skalierbare Kommunikation
|
||||
3. **Churn Prediction**: Proaktive Intervention
|
||||
4. **NPS**: Kontinuierliches Feedback
|
||||
|
||||
Automatisierung skaliert Customer Success ohne Qualitätsverlust.
|
||||
|
||||
---
|
||||
|
||||
## Bildprompts
|
||||
|
||||
1. "Customer health score dashboard, gauge visualization with risk levels"
|
||||
2. "Lifecycle email automation flow, triggered sequences"
|
||||
3. "Churn prediction model, risk factors highlighted"
|
||||
|
||||
---
|
||||
|
||||
## Quellen
|
||||
|
||||
- [Gainsight Customer Success](https://www.gainsight.com/customer-success/)
|
||||
- [ChurnZero Best Practices](https://churnzero.com/resources/)
|
||||
- [Customer Success Metrics](https://www.custify.com/blog/customer-success-metrics/)
|
||||
- [NPS Best Practices](https://www.netpromoter.com/know/)
|
||||
Reference in New Issue
Block a user