forked from admin/zeropost-tool
fix: Link2 undefined crash + goal multi-select + custom goal
ChannelView.js:
- Добавлен Link2 в import lucide-react (ReferenceError при открытии канала)
- Отображение goal учитывает множественные значения через split(',')
app/page.js:
- Аналогичный фикс отображения goal (split → map → join)
channels/new/page.js:
- Цель канала: single-select → multi-select (можно выбрать несколько)
- Кастомная цель: поле + кнопка «+», Enter, чипы с удалением
- Сохраняется как CSV строка (goal: goals.join(','))
DB:
- channels.goal varchar(50) → varchar(255) для длинных кастомных значений
This commit is contained in:
+62
-16
@@ -49,7 +49,8 @@ export default function NewChannelPage() {
|
||||
const [name, setName] = useState('');
|
||||
const [niche, setNiche] = useState('');
|
||||
const [audience, setAudience] = useState('');
|
||||
const [goal, setGoal] = useState('educational');
|
||||
const [goals, setGoals] = useState(['educational']); // multi-select, отправляем как CSV
|
||||
const [customGoal, setCustomGoal] = useState(''); // поле для своей цели
|
||||
const [language, setLanguage] = useState('ru');
|
||||
|
||||
// Шаг 2 — стиль
|
||||
@@ -70,7 +71,7 @@ export default function NewChannelPage() {
|
||||
setBusy(true);
|
||||
setError('');
|
||||
const data = {
|
||||
name, niche, audience, goal, language, region: 'ru',
|
||||
name, niche, audience, goal: goals.join(','), language, region: 'ru',
|
||||
style: {
|
||||
tone, formality, humor,
|
||||
post_length: postLength,
|
||||
@@ -150,22 +151,67 @@ export default function NewChannelPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Цель канала</label>
|
||||
<label className="label">Цель канала <span className="text-gray-500 font-normal">(можно несколько)</span></label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-5 gap-2">
|
||||
{GOALS.map(g => (
|
||||
<button
|
||||
key={g.v}
|
||||
type="button"
|
||||
onClick={() => setGoal(g.v)}
|
||||
className={`p-2.5 rounded-lg border text-left transition-colors ${
|
||||
goal === g.v ? 'border-accent bg-accent/10' : 'border-border bg-surface2 hover:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
<div className="text-sm font-medium">{g.label}</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">{g.desc}</div>
|
||||
</button>
|
||||
))}
|
||||
{GOALS.map(g => {
|
||||
const on = goals.includes(g.v);
|
||||
return (
|
||||
<button
|
||||
key={g.v}
|
||||
type="button"
|
||||
onClick={() => setGoals(on ? goals.filter(x => x !== g.v) : [...goals, g.v])}
|
||||
className={`p-2.5 rounded-lg border text-left transition-colors ${
|
||||
on ? 'border-accent bg-accent/10' : 'border-border bg-surface2 hover:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
<div className="text-sm font-medium">{g.label}</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">{g.desc}</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* Своя цель */}
|
||||
<div className="flex gap-2 mt-2">
|
||||
<input
|
||||
className="input text-sm flex-1"
|
||||
placeholder="Своя цель — введи и нажми +"
|
||||
value={customGoal}
|
||||
onChange={e => setCustomGoal(e.target.value)}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const v = customGoal.trim();
|
||||
if (v && !goals.includes(v)) setGoals([...goals, v]);
|
||||
setCustomGoal('');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const v = customGoal.trim();
|
||||
if (v && !goals.includes(v)) setGoals([...goals, v]);
|
||||
setCustomGoal('');
|
||||
}}
|
||||
disabled={!customGoal.trim()}
|
||||
className="btn-primary px-3 disabled:opacity-40"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
{/* Выбранные кастомные цели — чипы */}
|
||||
{goals.filter(g => !GOALS.find(x => x.v === g)).length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5 mt-2">
|
||||
{goals.filter(g => !GOALS.find(x => x.v === g)).map(g => (
|
||||
<span key={g} className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-accent/15 border border-accent/40 text-xs">
|
||||
{g}
|
||||
<button type="button" onClick={() => setGoals(goals.filter(x => x !== g))} className="hover:text-red-400">
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Язык постов</label>
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ export default async function HomePage() {
|
||||
{ch.name}
|
||||
</h3>
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-surface2 text-gray-400">
|
||||
{GOAL_LABELS[ch.goal] || ch.goal}
|
||||
{(ch.goal || '').split(',').map(g => GOAL_LABELS[g.trim()] || g.trim()).join(' · ')}
|
||||
</span>
|
||||
</div>
|
||||
{ch.niche && (
|
||||
|
||||
@@ -4,7 +4,7 @@ import Link from 'next/link';
|
||||
import {
|
||||
ArrowLeft, Sparkles, Wand2, Copy, Check, Loader2, Settings,
|
||||
Image as ImageIcon, RefreshCw, Scissors, Maximize2, Zap, Heart,
|
||||
MessageSquare, Pencil, X, Send, Clock, Search, Camera, ExternalLink
|
||||
MessageSquare, Pencil, X, Send, Clock, Search, Camera, ExternalLink, Link2
|
||||
} from 'lucide-react';
|
||||
import PhotoSearchModal from './PhotoSearchModal';
|
||||
import PostPreview from './PostPreview';
|
||||
@@ -330,7 +330,7 @@ export default function ChannelView({ channel }) {
|
||||
<Sparkles className="w-5 h-5 text-accent" />
|
||||
<h1 className="text-2xl font-bold">{channel.name}</h1>
|
||||
<span className="text-xs px-2 py-0.5 rounded-full bg-surface2 text-gray-400">
|
||||
{GOAL_LABELS[channel.goal] || channel.goal}
|
||||
{(channel.goal || '').split(',').map(g => GOAL_LABELS[g.trim()] || g.trim()).join(' · ')}
|
||||
</span>
|
||||
</div>
|
||||
{channel.niche && <p className="text-sm text-gray-500 max-w-2xl">{channel.niche}</p>}
|
||||
|
||||
Reference in New Issue
Block a user