initial commit

This commit is contained in:
I-Fun
2026-04-01 08:04:56 +07:00
commit e09c8a5fa9
111 changed files with 18022 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
@extends('layouts.app')
@section('title', 'New Content Brief')
@section('page-title', 'New Content Brief 🎬')
@section('content')
<div class="max-w-2xl mx-auto">
<div class="cat-card p-4 lg:p-8">
<form action="{{ route('content.store') }}" method="POST" class="space-y-4">
@csrf
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div class="sm:col-span-2">
<label class="block text-sm font-semibold text-gray-700 mb-1">Title <span class="text-pink-400">*</span></label>
<input type="text" name="title" value="{{ old('title') }}" class="input-soft" required
placeholder="e.g. GRWM ft. Brand X Skincare">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Platform <span class="text-pink-400">*</span></label>
<select name="platform" class="input-soft" required>
<option value="instagram">📸 Instagram</option>
<option value="tiktok">🎵 TikTok</option>
<option value="youtube">▶️ YouTube</option>
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Content Type</label>
<input type="text" name="content_type" value="{{ old('content_type') }}" class="input-soft"
placeholder="Reel, Short, GRWM, Story...">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Status</label>
<select name="status" class="input-soft">
@foreach($statuses as $s)
<option value="{{ $s->value }}" {{ old('status', request('status', 'drafting')) === $s->value ? 'selected' : '' }}>
{{ $s->icon() }} {{ $s->label() }}
</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Client</label>
<select name="client_id" class="input-soft">
<option value="">— None —</option>
@foreach($clients as $client)
<option value="{{ $client->id }}">{{ $client->name }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Target Date</label>
<input type="date" name="target_date" value="{{ old('target_date') }}" class="input-soft">
</div>
</div>
{{-- Storyline Maker --}}
<div class="bg-pink-50 rounded-2xl p-4 space-y-3">
<h3 class="font-bold text-gray-700 flex items-center gap-2 text-sm">✍️ Storyline Maker</h3>
<div>
<label class="block text-xs font-semibold text-pink-600 mb-1">🎣 HOOK — Open with a bang!</label>
<textarea name="hook" rows="2" class="input-soft text-sm resize-none bg-white"
placeholder="e.g. 'POV: you finally found the moisturizer that doesn't break you out...'">{{ old('hook') }}</textarea>
</div>
<div>
<label class="block text-xs font-semibold text-pink-600 mb-1">📖 BODY — Deliver the story</label>
<textarea name="body" rows="3" class="input-soft text-sm resize-none bg-white"
placeholder="The main content — what happens, product demo, value...">{{ old('body') }}</textarea>
</div>
<div>
<label class="block text-xs font-semibold text-pink-600 mb-1">📣 CTA — Call to Action</label>
<textarea name="cta" rows="2" class="input-soft text-sm resize-none bg-white"
placeholder="e.g. 'Link in bio to shop! Use code KITTY for 15% off'">{{ old('cta') }}</textarea>
</div>
<div>
<label class="block text-xs font-semibold text-gray-500 mb-1">📝 Full Script (optional)</label>
<textarea name="script" rows="4" class="input-soft text-sm resize-none bg-white"
placeholder="Complete spoken script, timestamps, shot list...">{{ old('script') }}</textarea>
</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Caption</label>
<textarea name="caption" rows="3" class="input-soft resize-none"
placeholder="Post caption text...">{{ old('caption') }}</textarea>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Hashtags</label>
<input type="text" name="hashtags" value="{{ old('hashtags') }}" class="input-soft"
placeholder="#skincare #grwm #fyp">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Notes</label>
<textarea name="notes" rows="2" class="input-soft resize-none"
placeholder="Any extra notes, brand requirements...">{{ old('notes') }}</textarea>
</div>
<div class="flex flex-col sm:flex-row gap-3 pt-1">
<button type="submit" class="btn-sakura flex-1 py-3">🎬 Create Brief</button>
<a href="{{ route('content.index') }}"
class="flex-1 py-3 text-center rounded-full border border-pink-200 text-gray-600 hover:bg-pink-50 font-semibold text-sm">
Cancel
</a>
</div>
</form>
</div>
</div>
@endsection

View File

@@ -0,0 +1,103 @@
@extends('layouts.app')
@section('title', 'Edit Brief')
@section('page-title', 'Edit Content Brief 🎬')
@section('content')
<div class="max-w-2xl mx-auto">
<div class="cat-card p-4 lg:p-8">
<form action="{{ route('content.update', $brief) }}" method="POST" class="space-y-4">
@csrf @method('PUT')
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div class="sm:col-span-2">
<label class="block text-sm font-semibold text-gray-700 mb-1">Title</label>
<input type="text" name="title" value="{{ old('title', $brief->title) }}" class="input-soft" required>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Platform</label>
<select name="platform" class="input-soft">
@foreach(['instagram','tiktok','youtube'] as $p)
<option value="{{ $p }}" {{ old('platform', $brief->platform) === $p ? 'selected' : '' }}>{{ ucfirst($p) }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Content Type</label>
<input type="text" name="content_type" value="{{ old('content_type', $brief->content_type) }}" class="input-soft">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Status</label>
<select name="status" class="input-soft">
@foreach($statuses as $s)
<option value="{{ $s->value }}" {{ old('status', $brief->status->value) === $s->value ? 'selected' : '' }}>{{ $s->icon() }} {{ $s->label() }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Client</label>
<select name="client_id" class="input-soft">
<option value="">— None —</option>
@foreach($clients as $client)
<option value="{{ $client->id }}" {{ old('client_id', $brief->client_id) == $client->id ? 'selected' : '' }}>{{ $client->name }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Target Date</label>
<input type="date" name="target_date" value="{{ old('target_date', $brief->target_date?->toDateString()) }}" class="input-soft">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Posted Date</label>
<input type="date" name="posted_date" value="{{ old('posted_date', $brief->posted_date?->toDateString()) }}" class="input-soft">
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-semibold text-gray-700 mb-1">Posted URL</label>
<input type="url" name="posted_url" value="{{ old('posted_url', $brief->posted_url) }}" class="input-soft" placeholder="https://...">
</div>
</div>
<div class="bg-pink-50 rounded-2xl p-4 space-y-3">
<h3 class="font-bold text-gray-700 text-sm">✍️ Storyline</h3>
<div>
<label class="text-xs font-semibold text-pink-600 mb-1 block">🎣 Hook</label>
<textarea name="hook" rows="2" class="input-soft text-sm resize-none bg-white">{{ old('hook', $brief->hook) }}</textarea>
</div>
<div>
<label class="text-xs font-semibold text-pink-600 mb-1 block">📖 Body</label>
<textarea name="body" rows="3" class="input-soft text-sm resize-none bg-white">{{ old('body', $brief->body) }}</textarea>
</div>
<div>
<label class="text-xs font-semibold text-pink-600 mb-1 block">📣 CTA</label>
<textarea name="cta" rows="2" class="input-soft text-sm resize-none bg-white">{{ old('cta', $brief->cta) }}</textarea>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 mb-1 block">📝 Script</label>
<textarea name="script" rows="4" class="input-soft text-sm resize-none bg-white">{{ old('script', $brief->script) }}</textarea>
</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Caption</label>
<textarea name="caption" rows="3" class="input-soft resize-none">{{ old('caption', $brief->caption) }}</textarea>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Hashtags</label>
<input type="text" name="hashtags" value="{{ old('hashtags', $brief->hashtags) }}" class="input-soft">
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Notes</label>
<textarea name="notes" rows="2" class="input-soft resize-none">{{ old('notes', $brief->notes) }}</textarea>
</div>
<div class="flex flex-col sm:flex-row gap-3 pt-1">
<button type="submit" class="btn-sakura flex-1 py-3">💾 Save Changes</button>
<a href="{{ route('content.show', $brief) }}"
class="flex-1 py-3 text-center rounded-full border border-pink-200 text-gray-600 hover:bg-pink-50 font-semibold text-sm">
Cancel
</a>
</div>
</form>
</div>
</div>
@endsection

View File

@@ -0,0 +1,112 @@
@extends('layouts.app')
@section('title', 'Content Board')
@section('page-title', 'Content Board 🎬')
@section('page-subtitle', 'Drag cards between columns to update status')
@section('content')
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-5">
<div class="flex gap-2 flex-wrap">
@foreach(['instagram','tiktok','youtube'] as $p)
<a href="{{ request()->fullUrlWithQuery(['platform' => $p]) }}"
class="px-3 py-1.5 rounded-full text-xs font-semibold transition-colors
{{ request('platform') === $p ? 'text-white' : 'bg-white text-gray-500 hover:bg-pink-50 border border-pink-100' }}"
style="{{ request('platform') === $p ? 'background:#FFB7C5' : '' }}">
{{ ['instagram'=>'📸','tiktok'=>'🎵','youtube'=>'▶️'][$p] }} {{ ucfirst($p) }}
</a>
@endforeach
@if(request('platform'))
<a href="{{ route('content.index') }}" class="px-3 py-1.5 rounded-full text-xs text-gray-400 hover:text-gray-600 border border-gray-200 bg-white">✕ Clear</a>
@endif
</div>
<a href="{{ route('content.create') }}" class="btn-sakura text-sm shrink-0">+ New Brief</a>
</div>
{{--
Mobile : grid grid-cols-1 → columns stack vertically, each full width
Desktop : flex flex-nowrap → columns in a single horizontal row, each flex-1
x-sort:group is on the INNER items div (not the column div) to avoid
SortableJS mutating the outer layout container.
--}}
<div class="grid grid-cols-1 gap-4 lg:flex lg:flex-nowrap lg:gap-4"
x-data="kanban()">
@foreach($columns as $col)
@php $status = $col['status']; @endphp
{{-- Column shell — layout only, NOT the sort target --}}
<div class="{{ $status->colorClass() }} flex flex-col w-full lg:flex-1 lg:min-w-36 rounded-2xl p-3"
data-status="{{ $status->value }}">
{{-- Column header --}}
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-1.5">
<span class="text-base">{{ $status->icon() }}</span>
<span class="font-bold text-gray-700 text-sm">{{ $status->label() }}</span>
</div>
<span class="text-xs bg-white rounded-full px-2 py-0.5 font-bold text-gray-500 shadow-sm">{{ count($col['items']) }}</span>
</div>
{{-- Sortable items container --}}
<div class="flex flex-col gap-2 flex-1 min-h-12"
x-sort:group="kanban"
@sort="moved($event, '{{ $status->value }}')"
data-col-status="{{ $status->value }}">
@foreach($col['items'] as $brief)
<div class="bg-white rounded-2xl p-3 shadow-sm border border-pink-50 select-none
cursor-grab active:cursor-grabbing hover:shadow-md transition-shadow"
x-sort:item="{{ $brief->id }}"
data-id="{{ $brief->id }}">
<div class="flex items-start gap-2 mb-1.5">
<span class="text-sm shrink-0">{{ $brief->platform_icon }}</span>
<a href="{{ route('content.show', $brief) }}"
class="font-semibold text-gray-800 text-xs leading-tight hover:text-pink-500 line-clamp-2 flex-1">
{{ $brief->title }}
</a>
</div>
@if($brief->client)
<div class="text-xs text-gray-400 mb-1">👥 {{ $brief->client->name }}</div>
@endif
@if($brief->target_date)
<div class="text-xs {{ $brief->target_date->isPast() && $brief->status->value !== 'posted' ? 'text-red-400 font-bold' : 'text-gray-400' }}">
📅 {{ $brief->target_date->format('d M') }}
</div>
@endif
<div class="flex justify-end mt-1.5">
<a href="{{ route('content.edit', $brief) }}" class="text-xs text-gray-400 hover:text-pink-500">Edit</a>
</div>
</div>
@endforeach
</div>
{{-- Add button --}}
<a href="{{ route('content.create') }}?status={{ $status->value }}"
class="mt-3 text-xs text-gray-400 hover:text-pink-500 text-center py-2
border-2 border-dashed border-pink-100 rounded-2xl hover:border-pink-300 transition-colors block">
+ Add
</a>
</div>
@endforeach
</div>
@push('scripts')
<script>
function kanban() {
return {
moved(event, targetStatus) {
const id = event.item?.dataset?.id;
if (!id) return;
fetch(`/content/${id}/status`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
},
body: JSON.stringify({ status: targetStatus }),
}).then(r => { if (r.ok) window.KolManager.notify('Status updated! 🎬'); });
},
};
}
</script>
@endpush
@endsection

View File

@@ -0,0 +1,93 @@
@extends('layouts.app')
@section('title', $brief->title)
@section('page-title', $brief->title)
@section('page-subtitle', $brief->platform_icon . ' ' . ucfirst($brief->platform) . ' · ' . $brief->status->label())
@section('content')
<div class="max-w-3xl mx-auto">
<div class="flex gap-3 mb-5">
<a href="{{ route('content.edit', $brief) }}" class="btn-sakura">Edit</a>
<form action="{{ route('content.destroy', $brief) }}" method="POST" onsubmit="return confirm('Delete this brief?')">
@csrf @method('DELETE')
<button class="btn btn-ghost rounded-full text-error">Delete</button>
</form>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-5">
{{-- Main Brief --}}
<div class="lg:col-span-2 space-y-4">
{{-- Storyline --}}
@foreach([['🎣', 'Hook', $brief->hook], ['📖', 'Body', $brief->body], ['📣', 'CTA', $brief->cta], ['📝', 'Script', $brief->script]] as [$icon, $label, $value])
@if($value)
<div class="cat-card p-5">
<div class="flex items-center gap-2 mb-2">
<span class="text-lg">{{ $icon }}</span>
<h3 class="font-bold text-gray-700">{{ $label }}</h3>
</div>
<p class="text-sm text-gray-600 whitespace-pre-wrap leading-relaxed">{{ $value }}</p>
</div>
@endif
@endforeach
@if($brief->caption)
<div class="cat-card p-5">
<h3 class="font-bold text-gray-700 mb-2">Caption</h3>
<p class="text-sm text-gray-600 whitespace-pre-wrap">{{ $brief->caption }}</p>
@if($brief->hashtags)
<div class="mt-2 text-xs text-pink-500">{{ $brief->hashtags }}</div>
@endif
</div>
@endif
</div>
{{-- Sidebar --}}
<div class="space-y-4">
<div class="cat-card p-5 space-y-3 text-sm">
<div class="flex justify-between">
<span class="text-gray-500">Platform</span>
<span class="font-medium">{{ $brief->platform_icon }} {{ ucfirst($brief->platform) }}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500">Type</span>
<span class="font-medium">{{ $brief->content_type ?? '—' }}</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500">Status</span>
<span class="badge {{ $brief->status->badgeClass() }}">{{ $brief->status->label() }}</span>
</div>
@if($brief->client)
<div class="flex justify-between">
<span class="text-gray-500">Client</span>
<a href="{{ route('clients.show', $brief->client) }}" class="font-medium text-pink-500 hover:underline">{{ $brief->client->name }}</a>
</div>
@endif
@if($brief->target_date)
<div class="flex justify-between">
<span class="text-gray-500">Target</span>
<span class="font-medium">{{ $brief->target_date->format('d M Y') }}</span>
</div>
@endif
@if($brief->posted_date)
<div class="flex justify-between">
<span class="text-gray-500">Posted</span>
<span class="font-medium text-green-600">{{ $brief->posted_date->format('d M Y') }}</span>
</div>
@endif
@if($brief->posted_url)
<div>
<a href="{{ $brief->posted_url }}" target="_blank" class="btn-sakura text-xs px-4 py-1.5 w-full text-center block">🔗 View Post</a>
</div>
@endif
</div>
@if($brief->notes)
<div class="cat-card p-5">
<h4 class="font-bold text-gray-600 text-xs uppercase mb-2">Notes</h4>
<p class="text-sm text-gray-500">{{ $brief->notes }}</p>
</div>
@endif
</div>
</div>
</div>
@endsection