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:
AutonetSellCar Deploy
2026-01-03 18:21:17 +09:00
parent 718c5b0474
commit 7c943d8553
17 changed files with 1414 additions and 6 deletions

View File

@@ -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;