diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index b83fcd1a..489f82d7 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -89,9 +89,9 @@ export default function LoginPage() {
- Influencer Assistant + ERP Base - Logga in med din e-post för att hantera din verksamhet + Logga in med din e-post för att hantera din ekonomi diff --git a/app/(dashboard)/analytics/page.tsx b/app/(dashboard)/analytics/page.tsx deleted file mode 100644 index 6a58d0a4..00000000 --- a/app/(dashboard)/analytics/page.tsx +++ /dev/null @@ -1,333 +0,0 @@ -'use client' - -import { useState, useEffect } from 'react' -import { useRouter } from 'next/navigation' -import { createClient } from '@/lib/supabase/client' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { - TikTokConnectButton, - TikTokAccountCard, - TikTokStatsWidget, - TikTokGrowthChart, - TikTokVideoList, - VideoLinkModal, - TikTokROITable, -} from '@/components/tiktok' -import type { TikTokAccount, TikTokStatsSummary, TikTokVideo, TikTokCampaignROI } from '@/types' -import { - Loader2, - TrendingUp, - Video, - Target, - BarChart3, -} from 'lucide-react' - -export default function AnalyticsPage() { - const router = useRouter() - const supabase = createClient() - - const [isLoading, setIsLoading] = useState(true) - const [accounts, setAccounts] = useState([]) - const [stats, setStats] = useState(null) - const [roiData, setRoiData] = useState([]) - const [selectedVideo, setSelectedVideo] = useState(null) - const [isLinkModalOpen, setIsLinkModalOpen] = useState(false) - const [isSyncing, setIsSyncing] = useState(false) - - useEffect(() => { - checkAuth() - }, []) - - const checkAuth = async () => { - const { data: { user } } = await supabase.auth.getUser() - if (!user) { - router.push('/login') - return - } - fetchData() - } - - const fetchData = async () => { - setIsLoading(true) - await Promise.all([ - fetchAccounts(), - fetchStats(), - fetchROI(), - ]) - setIsLoading(false) - } - - const fetchAccounts = async () => { - try { - const response = await fetch('/api/tiktok/accounts') - const data = await response.json() - setAccounts(data.accounts || []) - } catch (error) { - console.error('Failed to fetch accounts:', error) - } - } - - const fetchStats = async () => { - try { - const response = await fetch('/api/tiktok/stats') - const data = await response.json() - setStats(data.summary || null) - } catch (error) { - console.error('Failed to fetch stats:', error) - } - } - - const fetchROI = async () => { - // Fetch campaigns with TikTok videos for ROI calculation - try { - const response = await fetch('/api/tiktok/videos?limit=100') - const data = await response.json() - - // Group videos by campaign and calculate ROI - // This is a simplified version - the actual calculation is in the API - const campaignVideos = new Map() - for (const video of data.videos || []) { - if (video.campaign_id) { - if (!campaignVideos.has(video.campaign_id)) { - campaignVideos.set(video.campaign_id, []) - } - campaignVideos.get(video.campaign_id)!.push(video) - } - } - - // For now, just set empty - actual ROI data would come from a dedicated endpoint - setRoiData([]) - } catch (error) { - console.error('Failed to fetch ROI data:', error) - } - } - - const handleSync = async () => { - if (accounts.length === 0) return - - setIsSyncing(true) - try { - const activeAccount = accounts.find(a => a.status === 'active') - if (activeAccount) { - await fetch('/api/tiktok/sync', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ account_id: activeAccount.id, sync_type: 'full' }), - }) - await fetchData() - } - } catch (error) { - console.error('Sync failed:', error) - } - setIsSyncing(false) - } - - const handleVideoLinkClick = (video: TikTokVideo) => { - setSelectedVideo(video) - setIsLinkModalOpen(true) - } - - if (isLoading) { - return ( -
- -
- ) - } - - const activeAccount = accounts.find(a => a.status === 'active') - - return ( -
-
-

Analytics

-

- Analysera din sociala medieprestanda och kampanj-ROI -

-
- - {/* No connected account */} - {accounts.length === 0 && ( - - - Koppla TikTok - - Anslut ditt TikTok-konto för att se statistik och analysera kampanjprestanda - - - - - - - )} - - {/* Connected account */} - {activeAccount && ( - <> - {/* Stats overview */} -
- - -
- - Följare -
-

- {stats?.currentFollowers.toLocaleString('sv-SE') || '0'} -

- {stats?.followerChange7d !== undefined && ( -

= 0 ? 'text-success' : 'text-destructive'}`}> - {stats.followerChange7d >= 0 ? '+' : ''}{stats.followerChange7d.toLocaleString('sv-SE')} senaste 7 dagar -

- )} -
-
- - - -
-
-

- {stats?.totalVideos || 0} -

-

- {stats?.totalLikes.toLocaleString('sv-SE') || '0'} totala likes -

-
-
- - - -
- - Engagement Rate -
-

- {stats?.engagementRate.toFixed(1) || '0'}% -

-

- Genomsnitt senaste videor -

-
-
- - - -
- - 30-dagars tillväxt -
-

- {stats?.followerChange30d !== undefined ? ( - <> - {stats.followerChange30d >= 0 ? '+' : ''} - {stats.followerChange30d.toLocaleString('sv-SE')} - - ) : '0'} -

-

- nya följare -

-
-
-
- - {/* Tabs for different views */} - - - Tillväxt - Videor - Kampanj-ROI - - - - - - {/* Recent videos with metrics */} - - - Senaste videor - - Prestanda för dina senaste publiceringar - - - - - - - - - - - -
-
- Alla videor - - Klicka på länk-ikonen för att koppla en video till en kampanj - -
-
-
- - - -
-
- - - - - Kampanj-ROI - - Analysera avkastningen på dina influencer-kampanjer baserat på TikTok-prestanda - - - - - - - -
- - {/* Account info at bottom */} - - - Kopplat konto - - - - - - - )} - - {/* Video link modal */} - { - setIsLinkModalOpen(false) - setSelectedVideo(null) - }} - onSuccess={fetchData} - /> -
- ) -} diff --git a/app/(dashboard)/campaigns/[id]/page.tsx b/app/(dashboard)/campaigns/[id]/page.tsx deleted file mode 100644 index 72b7c3ef..00000000 --- a/app/(dashboard)/campaigns/[id]/page.tsx +++ /dev/null @@ -1,103 +0,0 @@ -'use client' - -import { useState, useEffect, use } from 'react' -import { createClient } from '@/lib/supabase/client' -import { Campaign, Customer } from '@/types' -import { CampaignDetail, CampaignForm } from '@/components/campaigns' -import { useToast } from '@/components/ui/use-toast' -import { Skeleton } from '@/components/ui/skeleton' - -interface PageProps { - params: Promise<{ id: string }> -} - -export default function CampaignDetailPage({ params }: PageProps) { - const { id } = use(params) - const supabase = createClient() - const { toast } = useToast() - const [campaign, setCampaign] = useState(null) - const [customers, setCustomers] = useState([]) - const [loading, setLoading] = useState(true) - const [editFormOpen, setEditFormOpen] = useState(false) - - const fetchCampaign = async () => { - try { - const response = await fetch(`/api/campaigns/${id}`) - if (response.ok) { - const { data } = await response.json() - setCampaign(data) - } else { - toast({ - title: 'Fel', - description: 'Samarbetet hittades inte', - variant: 'destructive', - }) - } - } catch (error) { - toast({ - title: 'Fel', - description: 'Kunde inte ladda samarbetet', - variant: 'destructive', - }) - } finally { - setLoading(false) - } - } - - const fetchCustomers = async () => { - const { data } = await supabase - .from('customers') - .select('*') - .order('name') - setCustomers(data || []) - } - - useEffect(() => { - fetchCampaign() - fetchCustomers() - }, [id]) - - if (loading) { - return ( -
- - -
- {[1, 2, 3, 4].map(i => ( - - ))} -
- -
- ) - } - - if (!campaign) { - return ( -
-

Samarbetet hittades inte

-
- ) - } - - return ( - <> - setEditFormOpen(true)} - /> - - { - setEditFormOpen(false) - fetchCampaign() - }} - /> - - ) -} diff --git a/app/(dashboard)/campaigns/import/page.tsx b/app/(dashboard)/campaigns/import/page.tsx deleted file mode 100644 index 95bf8f95..00000000 --- a/app/(dashboard)/campaigns/import/page.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { createClient } from '@/lib/supabase/server' -import { redirect } from 'next/navigation' -import { ContractImportWizard } from '@/components/contracts/ContractImportWizard' - -export const metadata = { - title: 'Importera avtal | Samarbeten', - description: 'Importera och analysera avtal med AI', -} - -export default async function CampaignImportPage() { - const supabase = await createClient() - - const { - data: { user }, - } = await supabase.auth.getUser() - - if (!user) { - redirect('/login') - } - - // Fetch customers for matching - const { data: customers } = await supabase - .from('customers') - .select('*') - .eq('user_id', user.id) - .order('name') - - return ( -
-
-

Importera avtal

-

- Ladda upp ett avtal och låt AI extrahera samarbetsinformation automatiskt -

-
- - -
- ) -} diff --git a/app/(dashboard)/campaigns/new/page.tsx b/app/(dashboard)/campaigns/new/page.tsx deleted file mode 100644 index d2c17712..00000000 --- a/app/(dashboard)/campaigns/new/page.tsx +++ /dev/null @@ -1,351 +0,0 @@ -'use client' - -import { useState, useEffect } from 'react' -import { useRouter } from 'next/navigation' -import { createClient } from '@/lib/supabase/client' -import { Customer, CreateCampaignInput, CampaignType, BillingFrequency } from '@/types' -import { - CAMPAIGN_TYPE_LABELS, - BILLING_FREQUENCY_LABELS, -} from '@/types' -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Textarea } from '@/components/ui/textarea' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select' -import { Switch } from '@/components/ui/switch' -import { useToast } from '@/components/ui/use-toast' -import { ArrowLeft } from 'lucide-react' -import Link from 'next/link' - -const CURRENCIES = ['SEK', 'EUR', 'USD', 'GBP', 'NOK', 'DKK'] - -export default function NewCampaignPage() { - const router = useRouter() - const supabase = createClient() - const { toast } = useToast() - const [customers, setCustomers] = useState([]) - const [isLoading, setIsLoading] = useState(false) - - const [formData, setFormData] = useState({ - name: '', - description: '', - customer_id: '', - brand_name: '', - campaign_type: 'influencer', - total_value: undefined, - currency: 'SEK', - vat_included: false, - payment_terms: 30, - billing_frequency: undefined, - publication_date: '', - draft_deadline: '', - notes: '', - }) - - useEffect(() => { - const fetchCustomers = async () => { - const { data } = await supabase - .from('customers') - .select('*') - .order('name') - setCustomers(data || []) - } - fetchCustomers() - }, []) - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - - if (!formData.name) { - toast({ title: 'Namn krävs', variant: 'destructive' }) - return - } - - setIsLoading(true) - - try { - const response = await fetch('/api/campaigns', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - ...formData, - customer_id: formData.customer_id || null, - brand_name: formData.brand_name || null, - total_value: formData.total_value || null, - payment_terms: formData.payment_terms || null, - billing_frequency: formData.billing_frequency || null, - publication_date: formData.publication_date || null, - draft_deadline: formData.draft_deadline || null, - }), - }) - - if (!response.ok) { - const error = await response.json() - throw new Error(error.error || 'Failed to create campaign') - } - - const { data } = await response.json() - - toast({ - title: 'Samarbete skapat', - description: formData.name, - }) - - router.push(`/campaigns/${data.id}`) - } catch (error) { - toast({ - title: 'Fel', - description: error instanceof Error ? error.message : 'Något gick fel', - variant: 'destructive', - }) - } finally { - setIsLoading(false) - } - } - - return ( -
-
- - - Tillbaka till samarbeten - -

Nytt samarbete

-

- Skapa ett nytt samarbete för att spåra innehåll, avtal och betalningar -

-
- -
- - - Grundläggande information - - -
- - setFormData({ ...formData, name: e.target.value })} - placeholder="T.ex. Sommarkampanj 2025" - required - /> -
- -
- -