constlabels: Record<string,string>={draft:'Draft',scheduled:'Scheduled',queued:'Queued',complete:'Finished',skipped:'Skipped',preparing:'Preparing email',sending:'Sending',sent:'Accepted by mail server',retry:'Retry scheduled',failed:'Failed',unknown:'Needs review',cancelled:'Cancelled'}
if(edition)voidaction('save',`/editions/${edition.id}`,'PUT',{revision: edition.revision,...editableFields(edition)},(row: Edition)=>{remember(row);setNotice('Draft saved. Preview this version before sending.')})
}
constpublish=(scheduled: boolean)=>{
if(!edition||!validPreview||!hasContent)return
voidaction('publish',`/editions/${edition.id}/publish`,'POST',{revision: edition.revision,send_at: scheduled?`${sendAt}:00Z`:null},(row: Edition)=>{remember(row);setNotice(`Edition scheduled for ${dateLabel(row.send_at)}. The saved content is now fixed.`)})
return<AdminShelltitle="Newsletters"subtitle="New arrivals, fresh episodes and a little inspiration for the next watch."actions={<aclassName="ghost-button"href="/admin/notifications">Emailsettings↗</a>}>
<divclassName="recap-overview-strip"><div><spanclassName={`recap-pill ${data.settings.enabled?'is-enabled':''}`}>{data.settings.enabled?'Weekly sending is on':'Weekly sending is paused'}</span><p>{data.settings.enabled?`Next edition ${dateLabel(data.settings.next_send_at)}`:'Create a one-off edition or set a weekly rhythm.'}</p></div><divclassName="recap-subscriber-count"><strong>{data.subscribers}</strong><span>confirmed{data.subscribers===1?'subscriber':'subscribers'}</span></div></div>
<sectionclassName="admin-panel recap-panel"><divclassName="recap-section-heading"><div><spanclassName="recap-eyebrow">Afreshedition</span><h2>What’snewonGrizzlyflix</h2><p>CollectarrivalsfromJellyfin,chooseyourpicksandaddanotetoyourcommunity.</p></div><divclassName="newsletter-create"><labelclassName="recap-month-label"htmlFor="arrival-period">Arrivalperiod<selectid="arrival-period"value={days}disabled={!!busy||dirty}onChange={(event)=>setDays(Number(event.target.value))}>{[7,14,30].map((value)=><optionkey={value}value={value}>Last{value}days</option>)}</select></label><buttontype="button"className="account-primary"disabled={!!busy||dirty}onClick={()=>voidaction('create','/drafts','POST',{days},(row: Edition)=>{remember(row);setNotice('Arrivals collected. Choose the titles you want to include.')})}>{busy==='create'?'Collecting arrivals…':'Create draft'}</button></div></div>
{edition&&<sectionclassName="admin-panel recap-panel newsletter-editor"><divclassName="recap-section-heading"><div><spanclassName="recap-eyebrow">{isDraft?'Make it yours':'Saved edition'}</span><h2>{isDraft?'Edit your newsletter':edition.subject}</h2><p>Arrivalsfrom{edition.content.period_start.slice(0,10)}to{edition.content.period_end.slice(0,10)}(UTC).TVadditionsaregroupedbyshow.</p></div><spanclassName="recap-pill">{labels[edition.state]||edition.state}</span></div>
<formclassName="recap-schedule-form"onSubmit={saveDraft}><labelhtmlFor="newsletter-subject">Emailsubject<inputid="newsletter-subject"maxLength={150}requiredvalue={edition.subject}disabled={!!busy||!isDraft}onChange={(event)=>{setEdition({...edition,subject: event.target.value});setPreview(null)}}/></label><labelhtmlFor="newsletter-intro">Announcement<spanclassName="newsletter-optional">Optional</span><textareaid="newsletter-intro"rows={4}maxLength={2000}placeholder="A welcome, a weekend recommendation, or a quick update…"disabled={!!busy||!isDraft}value={edition.intro}onChange={(event)=>{setEdition({...edition,intro: event.target.value});setPreview(null)}}/><small>Plaintext,sharedwitheverysubscriberreceivingthisedition.</small></label>
{edition.state!=='cancelled'&&<divclassName="newsletter-send"><h3>{isDraft?'Ready for the inbox?':'Delivery controls'}</h3><p>Atestgoestoyourownconfirmednewsletteremail.<ahref="/profile#newsletters">Manageyoursubscription↗</a></p><divclassName="recap-actions"><buttontype="button"className="account-secondary"disabled={!!busy||!validPreview||!hasContent||!data.ready||settingsDirty}onClick={sendTest}>{busy==='test'?'Queuing test…':'Send newsletter test to me'}</button></div>{isDraft?<><labelclassName="recap-month-label"htmlFor="newsletter-send-time">Schedulefor(UTC)<inputid="newsletter-send-time"type="datetime-local"value={sendAt}disabled={!!busy}onChange={(event)=>setSendAt(event.target.value)}/></label><divclassName="recap-actions"><buttontype="button"className="account-primary"disabled={!!busy||!validPreview||!hasContent||!data.ready||settingsDirty||!sendAt}onClick={()=>publish(true)}>Scheduleedition</button><buttontype="button"className="account-secondary"disabled={!!busy||!validPreview||!hasContent||!data.ready||settingsDirty||!data.subscribers}onClick={()=>publish(false)}>Sendnowto{data.subscribers}{data.subscribers===1?'subscriber':'subscribers'}</button></div><pclassName="recap-muted">Previewthesavededitionbeforesending.Schedulingfixesthecontentforthisedition.Usersmustbesubscribedbyitssendtime.</p></>:<p>{dateLabel(edition.send_at)}·CheckDeliveryhistoryforindividualresults.</p>}{['draft','scheduled','queued'].includes(edition.state)&&<buttontype="button"className="ghost-button newsletter-cancel"disabled={!!busy||dirty}onClick={()=>voidaction('cancel',`/editions/${edition.id}/cancel`,'POST',{},(row: Edition)=>{remember(row);setNotice('Edition cancelled. Pending emails have been stopped.')})}>Canceledition</button>}</div>}
{tab==='history'&&<sectionclassName="admin-panel recap-panel"><divclassName="recap-section-heading"><div><spanclassName="recap-eyebrow">Fromqueuetoinbox</span><h2>Deliveryhistory</h2><p>Serveracceptanceisrecordedhere.Inboxplacementdependsonyourmailprovider.</p></div><buttontype="button"className="ghost-button"disabled={!!busy}onClick={()=>setRefresh((value)=>value+1)}>Refreshhistory</button></div>{data.deliveries.length?<><divclassName="recap-history-scroll"><tableclassName="recap-history"><thead><tr><thscope="col">Recipient</th><thscope="col">Edition</th><thscope="col">Delivery</th><thscope="col">Updated</th></tr></thead><tbody>{data.deliveries.map((row)=><trkey={row.id}><td><strong>{row.username||'Removed account'}</strong><small>{row.email}</small></td><td>{row.subject}<small>{row.kind==='test'?'Test email':'Newsletter'}</small></td><td><spanclassName={`recap-pill ${row.state==='sent'?'is-enabled':['failed','unknown'].includes(row.state)?'is-attention':''}`}>{labels[row.state]||row.state}</span><small>{row.attempts}{row.attempts===1?'attempt':'attempts'}·{row.detail||'Waiting for the next worker check.'}</small>{row.state==='retry'&&<small>Nextattempt{dateLabel(row.next_attempt_at)}</small>}{row.state==='unknown'&&<small>Automaticretriesarestoppedtoavoidaduplicateemail.</small>}</td><td>{dateLabel(row.updated_at)}</td></tr>)}</tbody></table></div><divclassName="recap-pagination"><span>{offset+1}–{Math.min(offset+50,data.total)}of{data.total}</span><divclassName="recap-actions"><buttontype="button"className="ghost-button"disabled={!offset}onClick={()=>setOffset(Math.max(0,offset-50))}>Previous</button><buttontype="button"className="ghost-button"disabled={offset+50>=data.total}onClick={()=>setOffset(offset+50)}>Next</button></div></div></>:<divclassName="recap-empty"><spanaria-hidden="true">✉</span><h3>Yourfirsteditionstartshere</h3><p>Previewadraftandsendyourselfatest.Deliveryresultswillappearhere.</p></div>}</section>}
<h1>{state==='enabled'?'You’re on the list.':state==='off'?'Newsletters are turned off.':state==='loading'?'Checking your email link':state==='error'?'This link needs another look':link?.action==='unsubscribe'?'Unsubscribe from newsletters?':'Your next watch starts here.'}</h1>
<p>{state==='enabled'?'Your email is confirmed. You’ll receive your personal viewing recap when the monthly schedule runs.':state==='off'?'You won’t receive further monthly recaps. You can turn them back on in Profile.':state==='ready'&&link?.action==='unsubscribe'?'This turns off new-arrival newsletters. Your personal monthly recaps are managed separately.':state==='ready'?'Confirm to receive new movies, TV updates and featured picks, with posters and links to watch.':''}</p>
{state==='ready'&&<buttontype="button"className="account-primary"disabled={busy}onClick={()=>voidapply()}>{busy?'Updating…':link?.action==='unsubscribe'?'Unsubscribe from newsletters':'Confirm newsletter subscription'}</button>}
<divclassName="recap-section-heading"><div><spanclassName="recap-eyebrow">Yournextwatch</span><h2id="newsletter-preference-title">NewonGrizzlyflix.</h2></div>{data&&<spanclassName={`recap-pill ${data.state==='enabled'?'is-enabled':''}`}>{({off:'Off',pending:'Check your inbox',expired:'Confirmation expired',enabled:'Subscribed'})[data.state]}</span>}</div>
{data.state==='enabled'?<pclassName="recap-delivery-address">Newsletterswillgoto<strong>{data.email}</strong>.{data.schedule_enabled&&data.next_send_at?`Next scheduled send: ${scheduled(data.next_send_at)}.`:'Weekly sending is paused. You may still receive editions scheduled by your administrator.'}</p>:<p>{data.state==='pending'?`Check ${data.email} for a confirmation link. It expires after 24 hours. Your newsletter subscription starts after you confirm.`:data.state==='expired'?'Request a new confirmation link to turn on newsletters.':'Subscribe with your profile email. If you already confirmed it for monthly recaps, you can turn this on straight away. Otherwise, we?ll send a confirmation link.'}</p>}
{data.state!=='enabled'&&<buttontype="button"className="account-primary"disabled={busy||!data.can_subscribe||cooldown>0}onClick={()=>voidsave(true)}>{busy?'Sending confirmation…':data.state==='off'?'Email me new arrivals':'Resend newsletter confirmation'}</button>}
{data.state!=='off'&&<buttontype="button"className="account-secondary"disabled={busy}onClick={()=>voidsave(false)}>{busy?'Updating…':data.state==='enabled'?'Turn off newsletters':'Cancel newsletter subscription'}</button>}
@@ -199,6 +200,7 @@ export default function ProfilePage() {
</form>
<divclassName="account-connected"><spanclassName="account-connection-dot"aria-hidden="true"/><span>{user.auth_provider==='jellyfin'?'Connected with your Grizzlyflix account':user.auth_provider==='local'?'Signed in with a Magent account':'Signed in with your media account'}</span></div>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.