Add frontend campaign create/list to backlinkOutreachApi + store + component

This commit is contained in:
ajaysi
2026-05-23 15:18:04 +05:30
parent 020b237e57
commit 3f287d85d8
3 changed files with 100 additions and 2 deletions

View File

@@ -71,6 +71,29 @@ export interface BacklinkDiscoveryResponse {
opportunities: BacklinkOpportunity[]; opportunities: BacklinkOpportunity[];
} }
export interface BacklinkCampaignRecord {
campaign_id: string;
name: string;
status: string;
created_at?: string;
}
export interface BacklinkCampaignCreateRequest {
user_id: string;
workspace_id: string;
name: string;
}
export interface BacklinkCampaignCreateResponse {
campaign_id: string;
name: string;
status: string;
}
export interface BacklinkCampaignListResponse {
campaigns: BacklinkCampaignRecord[];
}
export const fetchBacklinkModuleRegistry = async (): Promise<BacklinkModuleRegistryResponse> => (await apiClient.get('/api/backlink-outreach/modules')).data; export const fetchBacklinkModuleRegistry = async (): Promise<BacklinkModuleRegistryResponse> => (await apiClient.get('/api/backlink-outreach/modules')).data;
export const fetchBacklinkMigrationCoverage = async (): Promise<BacklinkCoverageResponse> => (await apiClient.get('/api/backlink-outreach/migration-coverage')).data; export const fetchBacklinkMigrationCoverage = async (): Promise<BacklinkCoverageResponse> => (await apiClient.get('/api/backlink-outreach/migration-coverage')).data;
export const fetchBacklinkQueryTemplates = async (keyword: string): Promise<BacklinkQueryTemplatesResponse> => (await apiClient.get('/api/backlink-outreach/query-templates', { params: { keyword } })).data; export const fetchBacklinkQueryTemplates = async (keyword: string): Promise<BacklinkQueryTemplatesResponse> => (await apiClient.get('/api/backlink-outreach/query-templates', { params: { keyword } })).data;
@@ -78,3 +101,6 @@ export const discoverBacklinkOpportunities = async (payload: BacklinkDiscoveryRe
export const validateBacklinkPolicy = async (payload: BacklinkPolicyValidationRequest): Promise<BacklinkPolicyValidationResponse> => (await apiClient.post('/api/backlink-outreach/policy-validate', payload)).data; export const validateBacklinkPolicy = async (payload: BacklinkPolicyValidationRequest): Promise<BacklinkPolicyValidationResponse> => (await apiClient.post('/api/backlink-outreach/policy-validate', payload)).data;
export const fetchBacklinkReportingSnapshot = async (): Promise<BacklinkReportingSnapshot> => (await apiClient.get('/api/backlink-outreach/reporting')).data; export const fetchBacklinkReportingSnapshot = async (): Promise<BacklinkReportingSnapshot> => (await apiClient.get('/api/backlink-outreach/reporting')).data;
export const createBacklinkCampaign = async (payload: BacklinkCampaignCreateRequest): Promise<BacklinkCampaignCreateResponse> => (await apiClient.post('/api/backlink-outreach/campaigns', payload)).data;
export const listBacklinkCampaigns = async (user_id: string, workspace_id: string): Promise<BacklinkCampaignListResponse> => (await apiClient.get('/api/backlink-outreach/campaigns', { params: { user_id, workspace_id } })).data;

View File

@@ -1,11 +1,12 @@
import React, { useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { fetchBacklinkQueryTemplates } from '../../api/backlinkOutreachApi'; import { fetchBacklinkQueryTemplates } from '../../api/backlinkOutreachApi';
import { useBacklinkOutreachStore } from '../../stores/backlinkOutreachStore'; import { useBacklinkOutreachStore } from '../../stores/backlinkOutreachStore';
const BacklinkOutreachModuleList: React.FC = () => { const BacklinkOutreachModuleList: React.FC = () => {
const { modules, coverage, isLoading, error, refreshBacklinkRegistry } = useBacklinkOutreachStore(); const { modules, coverage, campaigns, isLoading, error, refreshBacklinkRegistry, fetchCampaigns, createCampaign } = useBacklinkOutreachStore();
const [queryPreview, setQueryPreview] = useState<string[]>([]); const [queryPreview, setQueryPreview] = useState<string[]>([]);
const [newCampaignName, setNewCampaignName] = useState('');
useEffect(() => { useEffect(() => {
refreshBacklinkRegistry(); refreshBacklinkRegistry();
@@ -19,6 +20,16 @@ const BacklinkOutreachModuleList: React.FC = () => {
loadPreview().catch(() => setQueryPreview([])); loadPreview().catch(() => setQueryPreview([]));
}, []); }, []);
useEffect(() => {
fetchCampaigns('default', 'default').catch(() => {});
}, [fetchCampaigns]);
const handleCreateCampaign = useCallback(async () => {
if (!newCampaignName.trim()) return;
await createCampaign('default', 'default', newCampaignName.trim());
setNewCampaignName('');
}, [newCampaignName, createCampaign]);
return ( return (
<section> <section>
<h3>Backlink Outreach Modules</h3> <h3>Backlink Outreach Modules</h3>
@@ -51,6 +62,31 @@ const BacklinkOutreachModuleList: React.FC = () => {
</ul> </ul>
</> </>
)} )}
<h4>Campaigns</h4>
{campaigns.length === 0 ? (
<p>No campaigns yet. Create one below.</p>
) : (
<ul>
{campaigns.map((c) => (
<li key={c.campaign_id}>
<strong>{c.name}</strong> ({c.status})
{c.created_at && <span> {new Date(c.created_at).toLocaleDateString()}</span>}
</li>
))}
</ul>
)}
<div>
<input
type="text"
value={newCampaignName}
onChange={(e) => setNewCampaignName(e.target.value)}
placeholder="Campaign name"
/>
<button onClick={handleCreateCampaign} disabled={!newCampaignName.trim()}>
Create Campaign
</button>
</div>
</> </>
)} )}
</section> </section>

View File

@@ -1,23 +1,30 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { import {
BacklinkCampaignRecord,
BacklinkCoverageResponse, BacklinkCoverageResponse,
BacklinkModuleRecord, BacklinkModuleRecord,
createBacklinkCampaign,
fetchBacklinkMigrationCoverage, fetchBacklinkMigrationCoverage,
fetchBacklinkModuleRegistry, fetchBacklinkModuleRegistry,
listBacklinkCampaigns,
} from '../api/backlinkOutreachApi'; } from '../api/backlinkOutreachApi';
interface BacklinkOutreachStore { interface BacklinkOutreachStore {
modules: BacklinkModuleRecord[]; modules: BacklinkModuleRecord[];
coverage: BacklinkCoverageResponse | null; coverage: BacklinkCoverageResponse | null;
campaigns: BacklinkCampaignRecord[];
isLoading: boolean; isLoading: boolean;
error: string | null; error: string | null;
refreshBacklinkRegistry: () => Promise<void>; refreshBacklinkRegistry: () => Promise<void>;
fetchCampaigns: (userId: string, workspaceId: string) => Promise<void>;
createCampaign: (userId: string, workspaceId: string, name: string) => Promise<string | null>;
} }
export const useBacklinkOutreachStore = create<BacklinkOutreachStore>((set) => ({ export const useBacklinkOutreachStore = create<BacklinkOutreachStore>((set) => ({
modules: [], modules: [],
coverage: null, coverage: null,
campaigns: [],
isLoading: false, isLoading: false,
error: null, error: null,
refreshBacklinkRegistry: async () => { refreshBacklinkRegistry: async () => {
@@ -35,4 +42,33 @@ export const useBacklinkOutreachStore = create<BacklinkOutreachStore>((set) => (
}); });
} }
}, },
fetchCampaigns: async (userId: string, workspaceId: string) => {
set({ isLoading: true, error: null });
try {
const response = await listBacklinkCampaigns(userId, workspaceId);
set({ campaigns: response.campaigns, isLoading: false });
} catch (error: any) {
set({
isLoading: false,
error: error?.message ?? 'Failed to load campaigns',
});
}
},
createCampaign: async (userId: string, workspaceId: string, name: string) => {
set({ isLoading: true, error: null });
try {
const result = await createBacklinkCampaign({ user_id: userId, workspace_id: workspaceId, name });
set((state) => ({
campaigns: [...state.campaigns, { campaign_id: result.campaign_id, name: result.name, status: result.status }],
isLoading: false,
}));
return result.campaign_id;
} catch (error: any) {
set({
isLoading: false,
error: error?.message ?? 'Failed to create campaign',
});
return null;
}
},
})); }));