Add SNS Marketing Campaign feature
- Add cash_cc_balance to User model (withdrawable CC) - Create SnsShareSubmission model for SNS share verification - Add marketing campaign settings to SystemSettings - Add reward_type to ReferralReward model - Create /api/sns-share endpoints for submission and verification - Add referral signup reward logic (10CC on signup) - Create /sns-share user page for SNS sharing - Create /admin/sns-shares management page - Add marketing settings UI to admin settings page - Add SNS Shares menu to admin sidebar 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1668,4 +1668,94 @@ export const visitorApi = {
|
||||
},
|
||||
};
|
||||
|
||||
// SNS Share API
|
||||
export interface SnsShareSubmission {
|
||||
id: number;
|
||||
user_id: number;
|
||||
car_id: number;
|
||||
platform: string;
|
||||
sns_url: string;
|
||||
status: string;
|
||||
rejected_reason?: string;
|
||||
reward_cc: number;
|
||||
rewarded_at?: string;
|
||||
submitted_at: string;
|
||||
verified_at?: string;
|
||||
verified_by?: number;
|
||||
car_name?: string;
|
||||
car_image?: string;
|
||||
}
|
||||
|
||||
export interface SnsShareListResponse {
|
||||
submissions: SnsShareSubmission[];
|
||||
total: number;
|
||||
pending_count: number;
|
||||
approved_count: number;
|
||||
rejected_count: number;
|
||||
}
|
||||
|
||||
export interface SnsShareStats {
|
||||
total_submissions: number;
|
||||
pending_submissions: number;
|
||||
approved_submissions: number;
|
||||
rejected_submissions: number;
|
||||
total_rewarded_cc: number;
|
||||
}
|
||||
|
||||
export interface CampaignStatus {
|
||||
enabled: boolean;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
sns_share_reward_cc?: number;
|
||||
referral_signup_reward_cc?: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export const snsShareApi = {
|
||||
// Public: 캠페인 상태 조회
|
||||
getCampaignStatus: async (): Promise<CampaignStatus> => {
|
||||
const { data } = await api.get('/sns-share/campaign-status');
|
||||
return data;
|
||||
},
|
||||
|
||||
// User: SNS 공유 URL 제출
|
||||
submit: async (carId: number, platform: string, snsUrl: string): Promise<SnsShareSubmission> => {
|
||||
const { data } = await api.post('/sns-share/submit', {
|
||||
car_id: carId,
|
||||
platform,
|
||||
sns_url: snsUrl,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
// User: 내 제출 목록 조회
|
||||
getMySubmissions: async (): Promise<SnsShareListResponse> => {
|
||||
const { data } = await api.get('/sns-share/my-submissions');
|
||||
return data;
|
||||
},
|
||||
|
||||
// Admin: 전체 제출 목록 조회
|
||||
getAdminList: async (statusFilter?: string, skip: number = 0, limit: number = 50): Promise<SnsShareListResponse> => {
|
||||
const params: any = { skip, limit };
|
||||
if (statusFilter) params.status_filter = statusFilter;
|
||||
const { data } = await api.get('/sns-share/admin/list', { params });
|
||||
return data;
|
||||
},
|
||||
|
||||
// Admin: 통계 조회
|
||||
getAdminStats: async (): Promise<SnsShareStats> => {
|
||||
const { data } = await api.get('/sns-share/admin/stats');
|
||||
return data;
|
||||
},
|
||||
|
||||
// Admin: 검증 (승인/거부)
|
||||
verify: async (submissionId: number, approved: boolean, rejectedReason?: string): Promise<SnsShareSubmission> => {
|
||||
const { data } = await api.put(`/sns-share/admin/${submissionId}/verify`, {
|
||||
approved,
|
||||
rejected_reason: rejectedReason,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
Reference in New Issue
Block a user