review-card.php
281 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Course Review Card single item component. |
| 4 | * |
| 5 | * @package Tutor\Templates |
| 6 | * @author Themeum <support@themeum.com> |
| 7 | * @link https://themeum.com |
| 8 | * @since 4.0.0 |
| 9 | */ |
| 10 | |
| 11 | defined( 'ABSPATH' ) || exit; |
| 12 | |
| 13 | use TUTOR\Icon; |
| 14 | use Tutor\Components\SvgIcon; |
| 15 | use Tutor\Components\Avatar; |
| 16 | use Tutor\Components\Button; |
| 17 | use Tutor\Components\Badge; |
| 18 | use Tutor\Components\InputField; |
| 19 | use Tutor\Components\Popover; |
| 20 | use Tutor\Components\StarRating; |
| 21 | use Tutor\Components\StarRatingInput; |
| 22 | use Tutor\Components\Constants\Size; |
| 23 | use Tutor\Components\Constants\Color; |
| 24 | use Tutor\Components\Constants\Positions; |
| 25 | use Tutor\Components\Constants\Variant; |
| 26 | use Tutor\Helpers\DateTimeHelper; |
| 27 | |
| 28 | $default_review = array( |
| 29 | 'comment_ID' => '', |
| 30 | 'comment_post_ID' => '', |
| 31 | 'course_title' => '', |
| 32 | 'comment_date' => '', |
| 33 | 'rating' => 0, |
| 34 | 'is_bundle' => false, |
| 35 | 'comment_content' => '', |
| 36 | 'is_editable' => false, |
| 37 | 'user_avatar' => '', |
| 38 | 'display_name' => '', |
| 39 | 'comment_status' => '', |
| 40 | ); |
| 41 | |
| 42 | $review = wp_parse_args( $review, $default_review ); |
| 43 | |
| 44 | $review['comment_content'] = wp_kses_post( htmlspecialchars( stripslashes( $review['comment_content'] ?? '' ) ) ); |
| 45 | |
| 46 | $form_id = "review-form-{$review['comment_ID']}"; |
| 47 | $delete_modal_id = 'review-delete-modal'; |
| 48 | ?> |
| 49 | |
| 50 | <div class="tutor-review-card" x-data="tutorReviewCard('<?php echo esc_attr( $review['comment_ID'] ); ?>')"> |
| 51 | |
| 52 | <!-- Review Display (Hidden in Edit Mode) --> |
| 53 | <div x-show="!isEditMode"> |
| 54 | <!-- Header Section --> |
| 55 | <div class="tutor-review-header"> |
| 56 | <div class="tutor-flex tutor-align-center"> |
| 57 | <!-- Type Badge with Icon --> |
| 58 | <?php if ( ! empty( $review['is_bundle'] ) ) : ?> |
| 59 | <?php |
| 60 | Badge::make() |
| 61 | ->variant( Badge::HIGHLIGHT ) |
| 62 | ->icon( Icon::BUNDLE ) |
| 63 | ->label( __( 'Bundle', 'tutor' ) ) |
| 64 | ->render(); |
| 65 | ?> |
| 66 | <?php else : ?> |
| 67 | <?php |
| 68 | Badge::make() |
| 69 | ->variant( Badge::INFO ) |
| 70 | ->icon( Icon::COURSES ) |
| 71 | ->label( __( 'Course', 'tutor' ) ) |
| 72 | ->render(); |
| 73 | ?> |
| 74 | <?php endif; ?> |
| 75 | </div> |
| 76 | |
| 77 | <!-- Course Title --> |
| 78 | <div class="tutor-review-title"> |
| 79 | <?php echo esc_html( $review['course_title'] ?? '' ); ?> |
| 80 | </div> |
| 81 | |
| 82 | <!-- Review Date --> |
| 83 | <div class="tutor-review-date"> |
| 84 | <?php |
| 85 | printf( |
| 86 | // translators: %s - Review date. |
| 87 | esc_html__( 'Reviewed on: %s', 'tutor' ), |
| 88 | esc_html( DateTimeHelper::get_gmt_to_user_timezone_date( $review['comment_date'], get_option( 'date_format' ) ) ?? '' ) |
| 89 | ); |
| 90 | ?> |
| 91 | </div> |
| 92 | </div> |
| 93 | |
| 94 | <!-- Divider --> |
| 95 | <hr class="tutor-section-separator" /> |
| 96 | |
| 97 | <!-- Review Content --> |
| 98 | <div class="tutor-review-content"> |
| 99 | <!-- Actions and Rating --> |
| 100 | <div class="tutor-review-rating-wrapper"> |
| 101 | <!-- Rating --> |
| 102 | <div class="tutor-flex tutor-items-center tutor-gap-5"> |
| 103 | <?php |
| 104 | StarRating::make()->rating( (float) ( $review['rating'] ?? 0 ) )->render(); |
| 105 | |
| 106 | // Show pending badge for on hold reviews. |
| 107 | if ( 'hold' === $review['comment_status'] ) { |
| 108 | Badge::make()->label( __( 'Pending', 'tutor' ) )->variant( Badge::WARNING )->render(); |
| 109 | } |
| 110 | ?> |
| 111 | </div> |
| 112 | |
| 113 | <!-- Actions --> |
| 114 | <?php if ( $review['is_editable'] ) : ?> |
| 115 | <div class="tutor-flex tutor-review-actions tutor-sm-hidden"> |
| 116 | <?php |
| 117 | Button::make() |
| 118 | ->label( __( 'Edit Review', 'tutor' ) ) |
| 119 | ->variant( Variant::SECONDARY ) |
| 120 | ->size( Size::X_SMALL ) |
| 121 | ->icon( SvgIcon::make()->name( Icon::EDIT_2 )->get() ) |
| 122 | ->icon_only() |
| 123 | ->attr( 'x-ref', 'edit' ) |
| 124 | ->render(); |
| 125 | ?> |
| 126 | <?php |
| 127 | Button::make() |
| 128 | ->label( __( 'Delete Review', 'tutor' ) ) |
| 129 | ->variant( Variant::SECONDARY ) |
| 130 | ->size( Size::X_SMALL ) |
| 131 | ->icon( SvgIcon::make()->name( Icon::DELETE_2 )->get() ) |
| 132 | ->icon_only() |
| 133 | ->attr( 'onclick', 'TutorCore.modal.showModal(' . wp_json_encode( $delete_modal_id ) . ', { id: ' . esc_js( $review['comment_ID'] ) . ' })' ) |
| 134 | ->render(); |
| 135 | ?> |
| 136 | </div> |
| 137 | <div class="tutor-review-actions tutor-hidden tutor-sm-flex"> |
| 138 | <?php |
| 139 | Popover::make() |
| 140 | ->placement( Positions::BOTTOM_END ) |
| 141 | ->menu_min_width( '104px' ) |
| 142 | ->menu_item( |
| 143 | array( |
| 144 | 'tag' => 'button', |
| 145 | 'icon' => SvgIcon::make()->name( Icon::EDIT_2 )->size( 20 )->get(), |
| 146 | 'content' => __( 'Edit', 'tutor' ), |
| 147 | 'attr' => array( |
| 148 | '@click' => '$refs.edit.click(); hide()', |
| 149 | ), |
| 150 | ) |
| 151 | ) |
| 152 | ->menu_item( |
| 153 | array( |
| 154 | 'tag' => 'button', |
| 155 | 'icon' => SvgIcon::make()->name( Icon::DELETE_2 )->size( 20 )->get(), |
| 156 | 'content' => __( 'Delete', 'tutor' ), |
| 157 | 'attr' => array( |
| 158 | '@click' => "hide(); TutorCore.modal.showModal('{$delete_modal_id}', { id: " . (int) $review['comment_ID'] . ' })', |
| 159 | ), |
| 160 | ) |
| 161 | ) |
| 162 | ->trigger( |
| 163 | Button::make() |
| 164 | ->label( __( 'Review Actions', 'tutor' ) ) |
| 165 | ->variant( Variant::GHOST ) |
| 166 | ->size( Size::X_SMALL ) |
| 167 | ->icon( SvgIcon::make()->name( Icon::ELLIPSES )->size( 16 )->color( Color::SECONDARY )->get() ) |
| 168 | ->icon_only() |
| 169 | ->attr( 'x-ref', 'trigger' ) |
| 170 | ->attr( '@click', 'toggle()' ) |
| 171 | ->attr( 'type', 'button' ) |
| 172 | ->get() |
| 173 | ) |
| 174 | ->render(); |
| 175 | ?> |
| 176 | </div> |
| 177 | <?php endif; ?> |
| 178 | </div> |
| 179 | |
| 180 | <!-- Review Text --> |
| 181 | <div class="tutor-review-text"> |
| 182 | <?php echo esc_textarea( html_entity_decode( $review['comment_content'] ?? '' ) ); ?> |
| 183 | </div> |
| 184 | |
| 185 | <?php if ( ! $review['is_editable'] ) : ?> |
| 186 | <!-- Student Information --> |
| 187 | <div class="tutor-review-student"> |
| 188 | <?php |
| 189 | Avatar::make() |
| 190 | ->src( $review['user_avatar'] ) |
| 191 | ->size( Size::SIZE_40 ) |
| 192 | ->initials( $review['display_name'] ) |
| 193 | ->render(); |
| 194 | ?> |
| 195 | <div class="tutor-flex tutor-flex-column tutor-tiny"> |
| 196 | <div class="tutor-font-medium"> |
| 197 | <?php echo esc_html( $review['display_name'] ); ?> |
| 198 | </div> |
| 199 | <div class="tutor-text-subdued"> |
| 200 | <!-- This is hard coded as only enrolled students can submit reviews --> |
| 201 | <?php esc_html_e( 'Student', 'tutor' ); ?> |
| 202 | </div> |
| 203 | </div> |
| 204 | </div> |
| 205 | <?php endif; ?> |
| 206 | </div> |
| 207 | </div> |
| 208 | |
| 209 | <!-- Edit Form (Shown in Edit Mode) --> |
| 210 | <?php if ( $review['is_editable'] ) : ?> |
| 211 | <div class="tutor-review-form" x-show="isEditMode" x-cloak> |
| 212 | <form |
| 213 | class="tutor-review-form-fields" |
| 214 | id="<?php echo esc_attr( $form_id ); ?>" |
| 215 | x-data='tutorForm({ |
| 216 | id: "<?php echo esc_attr( $form_id ); ?>", |
| 217 | mode: "onChange", |
| 218 | defaultValues: <?php echo wp_json_encode( $review ); ?>, |
| 219 | })' |
| 220 | x-bind="getFormBindings()" |
| 221 | @submit.prevent="handleSubmit( |
| 222 | (data) => handleReviewSubmit(data), |
| 223 | )($event)" |
| 224 | > |
| 225 | <?php |
| 226 | StarRatingInput::make() |
| 227 | ->field_name( 'rating' ) |
| 228 | ->current_rating( $review['rating'] ?? 0 ) |
| 229 | ->render(); |
| 230 | ?> |
| 231 | |
| 232 | <?php |
| 233 | InputField::make() |
| 234 | ->type( 'textarea' ) |
| 235 | ->name( 'comment_content' ) |
| 236 | ->required() |
| 237 | ->clearable() |
| 238 | ->attr( 'x-bind', "register('comment_content', { required: '" . esc_js( __( 'Review content is required', 'tutor' ) ) . "' })" ) |
| 239 | ->attr( '@keydown.meta.enter.prevent', 'handleSubmit((data) => handleReviewSubmit(data))' ) |
| 240 | ->attr( '@keydown.ctrl.enter.prevent', 'handleSubmit((data) => handleReviewSubmit(data))' ) |
| 241 | ->render(); |
| 242 | ?> |
| 243 | </form> |
| 244 | <div class="tutor-flex tutor-justify-between tutor-sm-justify-end tutor-gap-3"> |
| 245 | <div class="tutor-flex tutor-gap-3 tutor-tiny tutor-text-subdued tutor-items-center tutor-sm-hidden"> |
| 246 | <span> |
| 247 | <?php SvgIcon::make()->name( Icon::COMMAND )->size( 12 )->render(); ?> |
| 248 | </span> |
| 249 | <?php esc_html_e( 'Cmd/Ctrl +', 'tutor' ); ?> |
| 250 | <span> |
| 251 | <?php SvgIcon::make()->name( Icon::ENTER )->size( 12 )->render(); ?> |
| 252 | </span> |
| 253 | <?php esc_html_e( 'Enter to Save', 'tutor' ); ?> |
| 254 | </div> |
| 255 | <div class="tutor-flex tutor-gap-3"> |
| 256 | <?php |
| 257 | Button::make() |
| 258 | ->label( __( 'Cancel', 'tutor' ) ) |
| 259 | ->variant( Variant::GHOST ) |
| 260 | ->size( Size::X_SMALL ) |
| 261 | ->attr( 'x-ref', 'cancel' ) |
| 262 | ->attr( 'type', 'button' ) |
| 263 | ->render(); |
| 264 | ?> |
| 265 | <?php |
| 266 | Button::make() |
| 267 | ->label( __( 'Save and Continue', 'tutor' ) ) |
| 268 | ->variant( Variant::PRIMARY_SOFT ) |
| 269 | ->size( Size::X_SMALL ) |
| 270 | ->attr( 'type', 'submit' ) |
| 271 | ->attr( 'form', $form_id ) |
| 272 | ->attr( ':class', '{ \'tutor-btn-loading\': saveRatingMutation.isPending }' ) |
| 273 | ->attr( ':disabled', 'saveRatingMutation.isPending' ) |
| 274 | ->render(); |
| 275 | ?> |
| 276 | </div> |
| 277 | </div> |
| 278 | </div> |
| 279 | <?php endif; ?> |
| 280 | </div> |
| 281 |