@@ -4,19 +4,19 @@ import DashboardContent from "@components/admin/dashboard-content";
44import { isDateInFuture } from "@/lib/utils" ;
55import { AddressContext } from "@components/contexts" ;
66import { BROADCASTS } from "@ui-config/strings" ;
7- import { useContext } from "react" ;
7+ import { useContext , useState } from "react" ;
88import { PaperPlane , Clock } from "@courselit/icons" ;
99import {
1010 Form ,
1111 FormField ,
1212 Dialog2 ,
1313 useToast ,
14+ Tabbs ,
1415} from "@courselit/components-library" ;
1516import {
1617 ChangeEvent ,
1718 FormEvent ,
1819 useEffect ,
19- useState ,
2020 useRef ,
2121 useCallback ,
2222 useMemo ,
@@ -48,6 +48,8 @@ import { useGraphQLFetch } from "@/hooks/use-graphql-fetch";
4848import FilterContainer from "@components/admin/users/filter-container" ;
4949import EmailViewer from "@components/admin/mails/email-viewer" ;
5050import { Button } from "@components/ui/button" ;
51+ import { truncate } from "@courselit/utils" ;
52+ import EmailAnalytics from "@components/admin/mails/email-analytics" ;
5153
5254const breadcrumbs = [
5355 { label : BROADCASTS , href : "/dashboard/mails?tab=Broadcasts" } ,
@@ -77,6 +79,7 @@ export default function Page({
7779 const [ confirmationDialogOpen , setConfirmationDialogOpen ] = useState ( false ) ;
7880 const [ report , setReport ] = useState < SequenceReport > ( ) ;
7981 const [ status , setStatus ] = useState < SequenceStatus | null > ( null ) ;
82+ const [ activeTab , setActiveTab ] = useState ( "Compose" ) ;
8083
8184 // Refs to track initial values and prevent saving during load
8285 const initialValues = useRef ( {
@@ -501,147 +504,181 @@ export default function Page({
501504
502505 return (
503506 < DashboardContent breadcrumbs = { breadcrumbs } >
504- < div className = "flex flex-col gap-4" >
505- < div className = "flex items-center justify-between" >
506- < h1 className = "text-4xl font-semibold mb-4" >
507- { PAGE_HEADER_EDIT_MAIL }
508- </ h1 >
509- </ div >
510- < fieldset >
511- < label className = "mb-1 font-medium" > To</ label >
512- { ! isInitialLoad . current && (
513- < FilterContainer
514- filter = { { aggregator : filtersAggregator , filters } }
515- onChange = { onFilterChange }
516- disabled = { ! isEditable }
517- address = { address }
518- />
519- ) }
520- </ fieldset >
521- < Form className = "flex flex-col gap-4" onSubmit = { onSubmit } >
522- < FormField
523- value = { subject }
524- disabled = { ! isEditable }
525- label = { MAIL_SUBJECT_PLACEHOLDER }
526- onChange = { ( e : ChangeEvent < HTMLInputElement > ) =>
527- setSubject ( e . target . value )
528- }
529- />
530- < EmailViewer
531- content = { content }
532- emailEditorLink = {
533- isEditable
534- ? `/dashboard/mail/sequence/${ id } /${ emailId } ?redirectTo=/dashboard/mails/broadcast/${ id } `
535- : ""
536- }
537- />
538- { showScheduleInput && (
539- < FormField
540- value = { new Date (
541- ( delay || new Date ( ) . getTime ( ) ) -
542- new Date ( ) . getTimezoneOffset ( ) * 60000 ,
543- )
544- . toISOString ( )
545- . slice ( 0 , 16 ) }
546- type = "datetime-local"
547- label = { FORM_MAIL_SCHEDULE_TIME_LABEL }
548- min = { new Date ( ) . toISOString ( ) . slice ( 0 , 16 ) }
549- disabled = { ! isEditable }
550- onChange = { ( e : ChangeEvent < HTMLInputElement > ) => {
551- const selectedDate = new Date ( e . target . value ) ;
552- setDelay ( selectedDate . getTime ( ) ) ;
553- } }
554- />
555- ) }
556- { isEditable && (
557- < div className = "flex gap-2" >
558- { ! showScheduleInput && (
507+ < div className = "flex items-center justify-between mb-4" >
508+ < h1 className = "text-4xl font-semibold" >
509+ { truncate ( subject || PAGE_HEADER_EDIT_MAIL , 50 ) }
510+ </ h1 >
511+ </ div >
512+ < Tabbs
513+ items = { [ "Compose" , "Analytics" ] }
514+ value = { activeTab }
515+ onChange = { setActiveTab }
516+ >
517+ < div className = "mt-4" >
518+ < div className = "flex flex-col gap-4" >
519+ < fieldset >
520+ < label className = "mb-1 font-medium" > To</ label >
521+ { ! isInitialLoad . current && (
522+ < FilterContainer
523+ filter = { {
524+ aggregator : filtersAggregator ,
525+ filters,
526+ } }
527+ onChange = { onFilterChange }
528+ disabled = { ! isEditable }
529+ address = { address }
530+ />
531+ ) }
532+ </ fieldset >
533+ < Form
534+ className = "flex flex-col gap-4"
535+ onSubmit = { onSubmit }
536+ >
537+ < FormField
538+ value = { subject }
539+ disabled = { ! isEditable }
540+ label = { MAIL_SUBJECT_PLACEHOLDER }
541+ onChange = { ( e : ChangeEvent < HTMLInputElement > ) =>
542+ setSubject ( e . target . value )
543+ }
544+ />
545+ < EmailViewer
546+ content = { content }
547+ emailEditorLink = {
548+ isEditable
549+ ? `/dashboard/mail/sequence/${ id } /${ emailId } ?redirectTo=/dashboard/mails/broadcast/${ id } `
550+ : ""
551+ }
552+ />
553+ { showScheduleInput && (
554+ < FormField
555+ value = { new Date (
556+ ( delay || new Date ( ) . getTime ( ) ) -
557+ new Date ( ) . getTimezoneOffset ( ) *
558+ 60000 ,
559+ )
560+ . toISOString ( )
561+ . slice ( 0 , 16 ) }
562+ type = "datetime-local"
563+ label = { FORM_MAIL_SCHEDULE_TIME_LABEL }
564+ min = { new Date ( ) . toISOString ( ) . slice ( 0 , 16 ) }
565+ disabled = { ! isEditable }
566+ onChange = { (
567+ e : ChangeEvent < HTMLInputElement > ,
568+ ) => {
569+ const selectedDate = new Date (
570+ e . target . value ,
571+ ) ;
572+ setDelay ( selectedDate . getTime ( ) ) ;
573+ } }
574+ />
575+ ) }
576+ { isEditable && (
559577 < div className = "flex gap-2" >
560- < Dialog2
561- open = { confirmationDialogOpen }
562- onOpenChange = { setConfirmationDialogOpen }
563- title = { `${ DIALOG_SEND_HEADER } to ${ filteredUsersCount } contacts?` }
564- trigger = {
565- < Button >
566- < div className = "flex items-center gap-2" >
567- < PaperPlane />
568- { BTN_SEND }
578+ { ! showScheduleInput && (
579+ < div className = "flex gap-2" >
580+ < Dialog2
581+ open = { confirmationDialogOpen }
582+ onOpenChange = {
583+ setConfirmationDialogOpen
584+ }
585+ title = { `${ DIALOG_SEND_HEADER } to ${ filteredUsersCount } contacts?` }
586+ trigger = {
587+ < Button >
588+ < div className = "flex items-center gap-2" >
589+ < PaperPlane />
590+ { BTN_SEND }
591+ </ div >
592+ </ Button >
593+ }
594+ onClick = { onSubmit }
595+ >
596+ < p >
597+ Are you sure you want to
598+ send this email to{ " " }
599+ { filteredUsersCount } { " " }
600+ contacts?
601+ </ p >
602+ </ Dialog2 >
603+ < Button
604+ variant = "ghost"
605+ className = "gap-2"
606+ onClick = { ( ) => {
607+ setShowScheduleInput ( true ) ;
608+ } }
609+ >
610+ < Clock />
611+ { BTN_SCHEDULE }
612+ </ Button >
613+ </ div >
614+ ) }
615+ { showScheduleInput && (
616+ < >
617+ < Dialog2
618+ title = { `${ DIALOG_SEND_HEADER } to ${ filteredUsersCount } contacts?` }
619+ open = { confirmationDialogOpen }
620+ onOpenChange = {
621+ setConfirmationDialogOpen
622+ }
623+ trigger = {
624+ < Button >
625+ < div className = "flex items-center gap-2" >
626+ < Clock />
627+ { BTN_SCHEDULE }
628+ </ div >
629+ </ Button >
630+ }
631+ onClick = { ( e ) =>
632+ onSubmit ( e , true )
633+ }
634+ >
635+ < div className = "p-4" >
636+ < p >
637+ Are you sure you want to
638+ schedule this email to{ " " }
639+ { filteredUsersCount } { " " }
640+ contacts?
641+ </ p >
569642 </ div >
643+ </ Dialog2 >
644+ < Button
645+ variant = "secondary"
646+ onClick = { ( e ) => {
647+ e . preventDefault ( ) ;
648+ setShowScheduleInput ( false ) ;
649+ setDelay ( 0 ) ;
650+ } }
651+ >
652+ { BUTTON_CANCEL_TEXT }
570653 </ Button >
571- }
572- onClick = { onSubmit }
573- >
574- < p >
575- Are you sure you want to send this
576- email to { filteredUsersCount } { " " }
577- contacts?
578- </ p >
579- </ Dialog2 >
580- < Button
581- variant = "ghost"
582- className = "gap-2"
583- onClick = { ( ) => {
584- setShowScheduleInput ( true ) ;
585- } }
586- >
587- < Clock />
588- { BTN_SCHEDULE }
589- </ Button >
654+ </ >
655+ ) }
590656 </ div >
591657 ) }
592- { showScheduleInput && (
593- < >
594- < Dialog2
595- title = { `${ DIALOG_SEND_HEADER } to ${ filteredUsersCount } contacts?` }
596- open = { confirmationDialogOpen }
597- onOpenChange = { setConfirmationDialogOpen }
598- trigger = {
599- < Button >
600- < div className = "flex items-center gap-2" >
601- < Clock />
602- { BTN_SCHEDULE }
603- </ div >
604- </ Button >
605- }
606- onClick = { ( e ) => onSubmit ( e , true ) }
607- >
608- < div className = "p-4" >
609- < p >
610- Are you sure you want to
611- schedule this email to{ " " }
612- { filteredUsersCount } contacts?
613- </ p >
614- </ div >
615- </ Dialog2 >
658+ </ Form >
659+ { status === "active" &&
660+ isDateInFuture ( new Date ( delay ) ) &&
661+ ! report ?. broadcast ?. lockedAt && (
662+ < div >
663+ < p className = "flex items-center gap-2 text-sm mb-4 font-semibold text-slate-600" >
664+ < Clock /> Scheduled for{ " " }
665+ { new Date ( delay ) . toLocaleString ( ) }
666+ </ p >
616667 < Button
617668 variant = "secondary"
618- onClick = { ( e ) => {
619- e . preventDefault ( ) ;
620- setShowScheduleInput ( false ) ;
621- setDelay ( 0 ) ;
622- } }
669+ onClick = { cancelSending }
623670 >
624- { BUTTON_CANCEL_TEXT }
671+ { BUTTON_CANCEL_SCHEDULED_MAIL }
625672 </ Button >
626- </ >
673+ </ div >
627674 ) }
628- </ div >
629- ) }
630- </ Form >
631- { status === "active" &&
632- isDateInFuture ( new Date ( delay ) ) &&
633- ! report ?. broadcast ?. lockedAt && (
634- < div >
635- < p className = "flex items-center gap-2 text-sm mb-4 font-semibold text-slate-600" >
636- < Clock /> Scheduled for{ " " }
637- { new Date ( delay ) . toLocaleString ( ) }
638- </ p >
639- < Button variant = "secondary" onClick = { cancelSending } >
640- { BUTTON_CANCEL_SCHEDULED_MAIL }
641- </ Button >
642- </ div >
643- ) }
644- </ div >
675+ </ div >
676+ </ div >
677+ < div className = "mt-4" >
678+ < h2 className = "text-2xl font-semibold mb-4" > Analytics</ h2 >
679+ < EmailAnalytics sequence = { sequence } report = { report } />
680+ </ div >
681+ </ Tabbs >
645682 </ DashboardContent >
646683 ) ;
647684}
0 commit comments