learn-how
2 months ago
BsfLearn.js
2 months ago
BsfLearnChapters.js
2 months ago
BsfLearnSkeleton.js
2 months ago
BsfLearnStep.js
2 months ago
LearnHowDialog.js
2 months ago
BsfLearnStep.js
161 lines
| 1 | import { __ } from '@wordpress/i18n'; |
| 2 | import { Button, Text, Tooltip, Badge } from '@bsf/force-ui'; |
| 3 | import { useState, useEffect } from 'react'; |
| 4 | import { ArrowUpRight, Check, Info, Video } from 'lucide-react'; |
| 5 | |
| 6 | const BsfLearnStep = ( { step, chapterId, isLast = false, onCompletionChange, onLearnHowClick } ) => { |
| 7 | const { |
| 8 | id, |
| 9 | title, |
| 10 | description, |
| 11 | learn, |
| 12 | action, |
| 13 | completed = false, |
| 14 | isPro = false, |
| 15 | } = step; |
| 16 | |
| 17 | const [ isCompleted, setIsCompleted ] = useState( completed ); |
| 18 | |
| 19 | // Update local state when completed prop changes |
| 20 | useEffect( () => { |
| 21 | setIsCompleted( completed ); |
| 22 | }, [ completed ] ); |
| 23 | |
| 24 | if ( ! id || ! title ) { |
| 25 | return null; |
| 26 | } |
| 27 | |
| 28 | const handleCompletion = () => { |
| 29 | const newStatus = ! isCompleted; |
| 30 | setIsCompleted( newStatus ); |
| 31 | |
| 32 | // Call parent callback to update state |
| 33 | if ( onCompletionChange ) { |
| 34 | onCompletionChange( chapterId, id, newStatus ); |
| 35 | } |
| 36 | }; |
| 37 | |
| 38 | const LearnIcon = () => { |
| 39 | switch ( learn?.type ) { |
| 40 | case 'video': |
| 41 | return <Video size={ 14 } />; |
| 42 | case 'link': |
| 43 | default: |
| 44 | return <Info size={ 14 } />; |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | const handleLearnClick = () => { |
| 49 | if ( learn?.type === 'link' ) { |
| 50 | window.open( learn?.url, '_blank', 'noopener,noreferrer' ); |
| 51 | return; |
| 52 | } |
| 53 | |
| 54 | // Open learn how dialog for other types (video, content, etc.) |
| 55 | if ( onLearnHowClick && learn ) { |
| 56 | onLearnHowClick( step ); |
| 57 | } |
| 58 | }; |
| 59 | |
| 60 | const handleActionClick = () => { |
| 61 | if ( action?.url ) { |
| 62 | if ( action?.isExternal ) { |
| 63 | window.open( action?.url, '_blank', 'noopener,noreferrer' ); |
| 64 | } else { |
| 65 | // Internal navigation |
| 66 | window.location.href = action?.url; |
| 67 | } |
| 68 | return; |
| 69 | } |
| 70 | |
| 71 | console.info( 'Empty or missing URL!!!' ); |
| 72 | }; |
| 73 | |
| 74 | const statusText = isCompleted |
| 75 | ? __( 'Mark as incomplete', 'presto-player' ) |
| 76 | : __( 'Mark as complete', 'presto-player' ); |
| 77 | |
| 78 | return ( |
| 79 | <div className={`py-4 sm:py-5 flex items-center gap-2 sm:gap-3 border-solid border-0 border-border-subtle ${ isLast ? '' : 'border-b-0.5' }`}> |
| 80 | <Tooltip arrow content={ statusText } placement="top" variant="dark"> |
| 81 | <span |
| 82 | className={ `self-start mt-[1px] flex justify-center items-center w-5 h-5 rounded-full cursor-pointer border-[1.25px] border-solid ${ |
| 83 | isCompleted |
| 84 | ? 'bg-support-success p-[3px] sm:p-[4px] border-support-success-inverse' |
| 85 | : 'border-border-strong [&:hover>svg]:text-border-strong' |
| 86 | }` } |
| 87 | tabIndex={ 0 } |
| 88 | aria-label={ statusText } |
| 89 | onClick={ handleCompletion } |
| 90 | onKeyDown={ ( e ) => { |
| 91 | if ( e.key === 'Enter' || e.key === ' ' ) { |
| 92 | e.preventDefault(); |
| 93 | handleCompletion(); |
| 94 | } |
| 95 | } } |
| 96 | > |
| 97 | <Check |
| 98 | className="text-icon-on-color sm:w-3.5 sm:h-3.5 transition-all duration-200" |
| 99 | size={ 12 } |
| 100 | strokeWidth={ 1.5 } |
| 101 | /> |
| 102 | </span> |
| 103 | </Tooltip> |
| 104 | |
| 105 | <div className="flex-1 flex items-center gap-2"> |
| 106 | <div className="flex flex-col gap-1.5"> |
| 107 | <Text |
| 108 | className="flex-1" |
| 109 | size={ 14 } |
| 110 | weight={ 500 } |
| 111 | color="primary" |
| 112 | > |
| 113 | { title } |
| 114 | </Text> |
| 115 | <Text |
| 116 | className="flex-1 hidden sm:block" |
| 117 | size={ 14 } |
| 118 | color="secondary" |
| 119 | > |
| 120 | { description } |
| 121 | </Text> |
| 122 | </div> |
| 123 | { isPro && ( |
| 124 | <Badge |
| 125 | label={ __( 'Pro', 'presto-player' ) } |
| 126 | size="xs" |
| 127 | type="pill" |
| 128 | variant="inverse" |
| 129 | className="uppercase" |
| 130 | /> |
| 131 | ) } |
| 132 | </div> |
| 133 | |
| 134 | { learn && ( |
| 135 | <Tooltip arrow content={ learn?.label || __( 'Learn how', 'presto-player' ) } placement="top" variant="dark"> |
| 136 | <Button |
| 137 | size="xs" |
| 138 | variant="ghost" |
| 139 | icon={ <LearnIcon /> } |
| 140 | onClick={ handleLearnClick } |
| 141 | className="text-button-primary hover:bg-transparent outline-none" |
| 142 | /> |
| 143 | </Tooltip> |
| 144 | ) } |
| 145 | |
| 146 | <Button |
| 147 | className="px-3 gap-0.5 min-w-40 text-button-primary hover:bg-background-button-hover hover:outline-button-secondary" |
| 148 | size="sm" |
| 149 | variant="secondary" |
| 150 | icon={ <ArrowUpRight size={ 14 } /> } |
| 151 | iconPosition="right" |
| 152 | onClick={ handleActionClick } |
| 153 | > |
| 154 | { action?.label || __( 'Set Up', 'presto-player' ) } |
| 155 | </Button> |
| 156 | </div> |
| 157 | ); |
| 158 | }; |
| 159 | |
| 160 | export default BsfLearnStep; |
| 161 |