Ignore:
Timestamp:
03/22/26 17:58:40 (4 months ago)
Author:
kikisrbinoska <srbinoskakristina07@…>
Branches:
main
Children:
73b69b2
Parents:
b62cefc
Message:

Added fixes for the login,stories management and reading lists

Location:
chapterx-frontend/src/pages/writer
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • chapterx-frontend/src/pages/writer/CreateChapterPage.tsx

    rb62cefc racf690c  
    5656      is_published: isPublished,
    5757    }
    58     addChapter(chapter)
     58    try {
     59      await addChapter(chapter)
     60    } catch (err: any) {
     61      const msg = err?.response?.data?.message || err?.message || 'Failed to save chapter.'
     62      addToast(msg, 'error')
     63      setLoading(false)
     64      return
     65    }
    5966    addToast(isPublished ? 'Chapter published!' : 'Chapter saved as draft!')
    60     navigate(`/writer/edit-story/${storyId}`)
     67    navigate(isPublished ? `/story/${storyId}` : `/writer/edit-story/${storyId}`)
    6168    setLoading(false)
    6269  }
  • chapterx-frontend/src/pages/writer/CreateStoryPage.tsx

    rb62cefc racf690c  
    1 import React, { useState } from 'react'
     1import React, { useState, useRef } from 'react'
    22import { useNavigate } from 'react-router-dom'
    33import { Feather, ArrowLeft, X } from 'lucide-react'
     
    66import { useUIStore } from '../../store/uiStore'
    77import { Button } from '../../components/ui/Button'
     8import { StoryCreationAIPanel, StoryCreationAIPanelRef } from '../../components/writer/StoryCreationAIPanel'
    89import { Story } from '../../types'
    910
     
    1314  const navigate = useNavigate()
    1415  const { currentUser } = useAuthStore()
    15   const { addStory } = useStoryStore()
     16  const { addStory, addSuggestion } = useStoryStore()
    1617  const { addToast } = useUIStore()
     18  const aiPanelRef = useRef<StoryCreationAIPanelRef>(null)
    1719  const [form, setForm] = useState({
    1820    title: '',
     
    5355      total_views: 0,
    5456    }
    55     addStory(story)
     57    let realId: number
     58    try {
     59      realId = await addStory(story)
     60    } catch (err: any) {
     61      const msg = err?.response?.data?.message || err?.message || 'Failed to save story.'
     62      addToast(msg, 'error')
     63      setLoading(false)
     64      return
     65    }
     66
    5667    addToast(status === 'published' ? 'Story published!' : 'Draft saved!')
    57     navigate(`/writer/edit-story/${story.story_id}`)
     68    navigate(`/writer/edit-story/${realId}`)
    5869    setLoading(false)
    5970  }
     
    7384
    7485  return (
    75     <div className="max-w-3xl mx-auto px-4 py-8">
     86    <div className="max-w-6xl mx-auto px-4 py-8">
    7687      <div className="flex items-center gap-3 mb-8">
    7788        <button onClick={() => navigate('/writer')} className="text-slate-400 hover:text-white transition-colors">
     
    8495      </div>
    8596
    86       <div className="space-y-6">
    87         {/* Title */}
    88         <div>
    89           <label className="block text-sm font-medium text-slate-300 mb-1.5">Story Title *</label>
    90           <input
    91             value={form.title}
    92             onChange={e => setField('title', e.target.value)}
    93             placeholder="The Chronicles of Eldoria..."
    94             className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 text-lg font-serif ${errors.title ? 'border-rose-500' : 'border-slate-700'}`}
    95           />
    96           {errors.title && <p className="text-rose-400 text-xs mt-1">{errors.title}</p>}
    97         </div>
    98 
    99         {/* Short description */}
    100         <div>
    101           <label className="block text-sm font-medium text-slate-300 mb-1.5">Short Description * <span className="text-slate-500 font-normal">(shown on story cards)</span></label>
    102           <textarea
    103             value={form.short_description}
    104             onChange={e => setField('short_description', e.target.value)}
    105             placeholder="When the last dragon awakens..."
    106             rows={3}
    107             maxLength={280}
    108             className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-none ${errors.short_description ? 'border-rose-500' : 'border-slate-700'}`}
    109           />
    110           <div className="flex justify-between items-center mt-1">
    111             {errors.short_description ? <p className="text-rose-400 text-xs">{errors.short_description}</p> : <span />}
    112             <span className="text-slate-600 text-xs">{form.short_description.length}/280</span>
    113           </div>
    114         </div>
    115 
    116         {/* Content */}
    117         <div>
    118           <label className="block text-sm font-medium text-slate-300 mb-1.5">Story Content *</label>
    119           <textarea
    120             value={form.content}
    121             onChange={e => setField('content', e.target.value)}
    122             placeholder="Begin your story here. This is the introduction that readers will see..."
    123             rows={8}
    124             className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-y font-serif ${errors.content ? 'border-rose-500' : 'border-slate-700'}`}
    125           />
    126           {errors.content && <p className="text-rose-400 text-xs mt-1">{errors.content}</p>}
    127         </div>
    128 
    129         {/* Genres */}
    130         <div>
    131           <label className="block text-sm font-medium text-slate-300 mb-2">Genres * <span className="text-slate-500 font-normal">(select 1-3)</span></label>
    132           <div className="flex flex-wrap gap-2 mb-2">
    133             {ALL_GENRES.map(g => (
    134               <button
    135                 key={g}
    136                 type="button"
    137                 onClick={() => toggleGenre(g)}
    138                 className={`px-3 py-1.5 rounded-full text-sm border transition-all ${
    139                   form.genres.includes(g)
    140                     ? 'bg-indigo-500/30 text-indigo-300 border-indigo-500/50'
    141                     : 'bg-slate-800 text-slate-400 border-slate-700 hover:border-slate-500'
    142                 }`}
    143               >
    144                 {form.genres.includes(g) && <span className="mr-1">✓</span>}
    145                 {g}
    146               </button>
    147             ))}
    148           </div>
    149           {form.genres.length > 0 && (
    150             <div className="flex flex-wrap gap-1 mt-1">
    151               {form.genres.map(g => (
    152                 <span key={g} className="flex items-center gap-1 text-xs px-2 py-0.5 bg-indigo-500/20 text-indigo-300 rounded-full">
     97      <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
     98        <div className="lg:col-span-2 space-y-6">
     99          {/* Title */}
     100          <div>
     101            <label className="block text-sm font-medium text-slate-300 mb-1.5">Story Title *</label>
     102            <input
     103              value={form.title}
     104              onChange={e => setField('title', e.target.value)}
     105              placeholder="The Chronicles of Eldoria..."
     106              className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 text-lg font-serif ${errors.title ? 'border-rose-500' : 'border-slate-700'}`}
     107            />
     108            {errors.title && <p className="text-rose-400 text-xs mt-1">{errors.title}</p>}
     109          </div>
     110
     111          {/* Short description */}
     112          <div>
     113            <label className="block text-sm font-medium text-slate-300 mb-1.5">Short Description * <span className="text-slate-500 font-normal">(shown on story cards)</span></label>
     114            <textarea
     115              value={form.short_description}
     116              onChange={e => setField('short_description', e.target.value)}
     117              placeholder="When the last dragon awakens..."
     118              rows={3}
     119              maxLength={280}
     120              className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-none ${errors.short_description ? 'border-rose-500' : 'border-slate-700'}`}
     121            />
     122            <div className="flex justify-between items-center mt-1">
     123              {errors.short_description ? <p className="text-rose-400 text-xs">{errors.short_description}</p> : <span />}
     124              <span className="text-slate-600 text-xs">{form.short_description.length}/280</span>
     125            </div>
     126          </div>
     127
     128          {/* Content */}
     129          <div>
     130            <label className="block text-sm font-medium text-slate-300 mb-1.5">Story Content *</label>
     131            <textarea
     132              value={form.content}
     133              onChange={e => setField('content', e.target.value)}
     134              placeholder="Begin your story here. This is the introduction that readers will see..."
     135              rows={8}
     136              className={`w-full px-4 py-3 bg-slate-800 border rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-y font-serif ${errors.content ? 'border-rose-500' : 'border-slate-700'}`}
     137            />
     138            {errors.content && <p className="text-rose-400 text-xs mt-1">{errors.content}</p>}
     139          </div>
     140
     141          {/* Genres */}
     142          <div>
     143            <label className="block text-sm font-medium text-slate-300 mb-2">Genres * <span className="text-slate-500 font-normal">(select 1-3)</span></label>
     144            <div className="flex flex-wrap gap-2 mb-2">
     145              {ALL_GENRES.map(g => (
     146                <button
     147                  key={g}
     148                  type="button"
     149                  onClick={() => toggleGenre(g)}
     150                  className={`px-3 py-1.5 rounded-full text-sm border transition-all ${
     151                    form.genres.includes(g)
     152                      ? 'bg-indigo-500/30 text-indigo-300 border-indigo-500/50'
     153                      : 'bg-slate-800 text-slate-400 border-slate-700 hover:border-slate-500'
     154                  }`}
     155                >
     156                  {form.genres.includes(g) && <span className="mr-1">✓</span>}
    153157                  {g}
    154                   <button onClick={() => toggleGenre(g)}><X size={10} /></button>
    155                 </span>
     158                </button>
    156159              ))}
    157160            </div>
    158           )}
    159           {errors.genres && <p className="text-rose-400 text-xs mt-1">{errors.genres}</p>}
     161            {form.genres.length > 0 && (
     162              <div className="flex flex-wrap gap-1 mt-1">
     163                {form.genres.map(g => (
     164                  <span key={g} className="flex items-center gap-1 text-xs px-2 py-0.5 bg-indigo-500/20 text-indigo-300 rounded-full">
     165                    {g}
     166                    <button onClick={() => toggleGenre(g)}><X size={10} /></button>
     167                  </span>
     168                ))}
     169              </div>
     170            )}
     171            {errors.genres && <p className="text-rose-400 text-xs mt-1">{errors.genres}</p>}
     172          </div>
     173
     174          {/* Mature content */}
     175          <div className="flex items-center gap-3">
     176            <button
     177              type="button"
     178              onClick={() => setField('mature_content', !form.mature_content)}
     179              className={`w-11 h-6 rounded-full transition-colors ${form.mature_content ? 'bg-rose-500' : 'bg-slate-600'} relative flex-shrink-0`}
     180            >
     181              <div className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-transform ${form.mature_content ? 'translate-x-6' : 'translate-x-1'}`} />
     182            </button>
     183            <div>
     184              <p className="text-white text-sm font-medium">Mature Content (18+)</p>
     185              <p className="text-slate-500 text-xs">Enable for stories with adult themes</p>
     186            </div>
     187          </div>
     188
     189          {/* Actions */}
     190          <div className="flex gap-3 pt-4 border-t border-slate-700">
     191            <Button variant="secondary" className="flex-1" onClick={() => handleSubmit('draft')} loading={loading}>
     192              Save as Draft
     193            </Button>
     194            <Button className="flex-1" onClick={() => handleSubmit('published')} loading={loading}>
     195              <Feather size={16} />
     196              Publish Story
     197            </Button>
     198          </div>
    160199        </div>
    161200
    162         {/* Mature content */}
    163         <div className="flex items-center gap-3">
    164           <button
    165             type="button"
    166             onClick={() => setField('mature_content', !form.mature_content)}
    167             className={`w-11 h-6 rounded-full transition-colors ${form.mature_content ? 'bg-rose-500' : 'bg-slate-600'} relative flex-shrink-0`}
    168           >
    169             <div className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-transform ${form.mature_content ? 'translate-x-6' : 'translate-x-1'}`} />
    170           </button>
    171           <div>
    172             <p className="text-white text-sm font-medium">Mature Content (18+)</p>
    173             <p className="text-slate-500 text-xs">Enable for stories with adult themes</p>
    174           </div>
    175         </div>
    176 
    177         {/* Actions */}
    178         <div className="flex gap-3 pt-4 border-t border-slate-700">
    179           <Button variant="secondary" className="flex-1" onClick={() => handleSubmit('draft')} loading={loading}>
    180             Save as Draft
    181           </Button>
    182           <Button className="flex-1" onClick={() => handleSubmit('published')} loading={loading}>
    183             <Feather size={16} />
    184             Publish Story
    185           </Button>
     201        {/* AI Suggestions Sidebar */}
     202        <div className="lg:col-span-1">
     203          <StoryCreationAIPanel
     204            ref={aiPanelRef}
     205            title={form.title}
     206            description={form.short_description}
     207            content={form.content}
     208            genres={form.genres}
     209          />
    186210        </div>
    187211      </div>
  • chapterx-frontend/src/pages/writer/EditStoryPage.tsx

    rb62cefc racf690c  
    6363    addToast('Story updated!')
    6464    setSaving(false)
     65    navigate(`/story/${story.story_id}`)
    6566  }
    6667
Note: See TracChangeset for help on using the changeset viewer.