From 6a1319784da42988e1a6ce6101eeb04bf64b6055 Mon Sep 17 00:00:00 2001 From: Ruben Ramirez Date: Thu, 3 Apr 2025 21:11:53 -0500 Subject: [PATCH] feat: Add theme templates and Job Management UI (Steps 8 & 9) --- content-single.php | 102 +++++++++++++++++++++ functions.php | 154 ++++++++++++++++++++++++++++++++ js/quiztech-theme.js | 145 ++++++++++++++++++++++++++++++ single.php | 54 +++++++++++ template-manage-assessments.php | 42 +++++++++ template-manage-credits.php | 42 +++++++++ template-manage-jobs.php | 138 ++++++++++++++++++++++++++++ template-manage-questions.php | 42 +++++++++ template-quiztech-dashboard.php | 42 +++++++++ template-view-results.php | 42 +++++++++ 10 files changed, 803 insertions(+) create mode 100644 content-single.php create mode 100644 js/quiztech-theme.js create mode 100644 single.php create mode 100644 template-manage-assessments.php create mode 100644 template-manage-credits.php create mode 100644 template-manage-jobs.php create mode 100644 template-manage-questions.php create mode 100644 template-quiztech-dashboard.php create mode 100644 template-view-results.php diff --git a/content-single.php b/content-single.php new file mode 100644 index 0000000..cf90f74 --- /dev/null +++ b/content-single.php @@ -0,0 +1,102 @@ + + +
> +
+ +
> + +
+ + +
> + '', + ) + ); + ?> +
+ + +
+
diff --git a/functions.php b/functions.php index 7734ac3..c3a602f 100644 --- a/functions.php +++ b/functions.php @@ -4,4 +4,158 @@ * * Add your custom PHP in this file. * Only edit this file if you have direct access to it on your server (to fix errors if they happen). + + +function quiztech_theme_enqueue_scripts() { + // List of Quiztech custom page templates + $quiztech_templates = [ + 'template-quiztech-dashboard.php', + 'template-manage-jobs.php', + 'template-manage-assessments.php', + 'template-manage-questions.php', + 'template-manage-credits.php', + 'template-view-results.php', + ]; + + // Check if the current page is using one of our templates + $is_quiztech_template = false; + foreach ( $quiztech_templates as $template ) { + if ( is_page_template( $template ) ) { + $is_quiztech_template = true; + break; + } + } + + // Only enqueue the script on our specific template pages + if ( $is_quiztech_template ) { + $script_path = get_stylesheet_directory() . '/js/quiztech-theme.js'; + $script_url = get_stylesheet_directory_uri() . '/js/quiztech-theme.js'; + $version = file_exists( $script_path ) ? filemtime( $script_path ) : '1.0'; + + wp_enqueue_script( + 'quiztech-theme-script', + $script_url, + array( 'jquery' ), // Depends on jQuery + $version, + true // Load in footer + ); + + // Localize script with necessary data for AJAX calls + wp_localize_script( 'quiztech-theme-script', 'quiztechThemeData', [ + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'add_job_nonce' => wp_create_nonce( 'quiztech_add_new_job_action' ), + 'send_invite_nonce' => wp_create_nonce( 'quiztech_send_job_invite_action' ), + 'error_generic' => esc_html__( 'An error occurred. Please try again.', 'quiztech' ), + 'error_permissions' => esc_html__( 'You do not have permission to perform this action.', 'quiztech' ), + 'error_missing_assessment' => esc_html__( 'Error: No assessment is linked to this job.', 'quiztech' ), + 'invite_sent_success' => esc_html__( 'Invite sent successfully!', 'quiztech' ), + 'job_added_success' => esc_html__( 'Job added successfully!', 'quiztech' ), + ]); + } +} +add_action( 'wp_enqueue_scripts', 'quiztech_theme_enqueue_scripts' ); + + +/** + * AJAX handler for adding a new job post from the frontend. */ +function quiztech_ajax_add_new_job() { + // 1. Verify Nonce + check_ajax_referer( 'quiztech_add_new_job_action', 'nonce' ); + + // 2. Check Capabilities (adjust capability if needed) + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( [ 'message' => esc_html__( 'Insufficient permissions.', 'quiztech' ) ], 403 ); + } + + // 3. Sanitize Input + $job_title = isset( $_POST['job_title'] ) ? sanitize_text_field( wp_unslash( $_POST['job_title'] ) ) : ''; + $job_description = isset( $_POST['job_description'] ) ? sanitize_textarea_field( wp_unslash( $_POST['job_description'] ) ) : ''; + + if ( empty( $job_title ) ) { + wp_send_json_error( [ 'message' => esc_html__( 'Job title cannot be empty.', 'quiztech' ) ], 400 ); + } + + // 4. Create Post + $post_data = [ + 'post_title' => $job_title, + 'post_content' => $job_description, + 'post_status' => 'publish', // Or 'draft' if preferred + 'post_author' => get_current_user_id(), + 'post_type' => 'job', + ]; + + $post_id = wp_insert_post( $post_data, true ); // Pass true to return WP_Error on failure + + // 5. Send Response + if ( is_wp_error( $post_id ) ) { + wp_send_json_error( [ 'message' => $post_id->get_error_message() ], 500 ); + } else { + // Optionally return HTML for the new row, or just success and let JS handle refresh/update + wp_send_json_success( [ + 'message' => esc_html__( 'Job created successfully.', 'quiztech' ), + 'post_id' => $post_id + ] ); + } +} +add_action( 'wp_ajax_add_new_job', 'quiztech_ajax_add_new_job' ); + + +/** + * AJAX handler for sending a job invite from the frontend. + */ +function quiztech_ajax_send_job_invite() { + // 1. Verify Nonce + check_ajax_referer( 'quiztech_send_job_invite_action', 'nonce' ); + + // 2. Check Capabilities (adjust capability if needed) + if ( ! current_user_can( 'manage_options' ) ) { + wp_send_json_error( [ 'message' => esc_html__( 'Insufficient permissions.', 'quiztech' ) ], 403 ); + } + + // 3. Sanitize Input + $job_id = isset( $_POST['job_id'] ) ? absint( $_POST['job_id'] ) : 0; + $applicant_email = isset( $_POST['applicant_email'] ) ? sanitize_email( wp_unslash( $_POST['applicant_email'] ) ) : ''; + + if ( ! $job_id || ! is_email( $applicant_email ) ) { + wp_send_json_error( [ 'message' => esc_html__( 'Invalid job ID or email address.', 'quiztech' ) ], 400 ); + } + + // 4. Check if Job exists and belongs to user (optional extra check) + $job_post = get_post( $job_id ); + if ( ! $job_post || $job_post->post_type !== 'job' || $job_post->post_author != get_current_user_id() ) { + wp_send_json_error( [ 'message' => esc_html__( 'Invalid job specified.', 'quiztech' ) ], 404 ); + } + + // 5. Get Linked Assessment ID + $assessment_id = get_post_meta( $job_id, '_quiztech_linked_assessment_id', true ); + if ( empty( $assessment_id ) ) { + wp_send_json_error( [ 'message' => esc_html__( 'No assessment is linked to this job. Please edit the job and link an assessment.', 'quiztech' ) ], 400 ); + } + $assessment_id = absint( $assessment_id ); + + // 6. Use the Invitation Class (ensure plugin is active and class exists) + if ( ! class_exists( 'Quiztech\\AssessmentPlatform\\Includes\\Invitations' ) ) { + wp_send_json_error( [ 'message' => esc_html__( 'Invitation system is unavailable.', 'quiztech' ) ], 500 ); + } + + try { + $invitations = new \Quiztech\AssessmentPlatform\Includes\Invitations(); + $result = $invitations->create_invitation( $job_id, $assessment_id, $applicant_email ); + + if ( is_wp_error( $result ) ) { + wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 ); + } elseif ( $result === false ) { + // Generic failure from create_invitation if no WP_Error + wp_send_json_error( [ 'message' => esc_html__( 'Failed to create invitation.', 'quiztech' ) ], 500 ); + } else { + // Success! $result might contain the token or true + wp_send_json_success( [ 'message' => esc_html__( 'Invitation sent successfully.', 'quiztech' ) ] ); + } + } catch ( \Exception $e ) { + // Catch any unexpected exceptions + error_log( 'Quiztech Send Invite Error: ' . $e->getMessage() ); + wp_send_json_error( [ 'message' => esc_html__( 'An unexpected error occurred while sending the invitation.', 'quiztech' ) ], 500 ); + } +} +add_action( 'wp_ajax_send_job_invite', 'quiztech_ajax_send_job_invite' ); diff --git a/js/quiztech-theme.js b/js/quiztech-theme.js new file mode 100644 index 0000000..82f14c3 --- /dev/null +++ b/js/quiztech-theme.js @@ -0,0 +1,145 @@ +jQuery(document).ready(function($) { + + // --- Add New Job Form --- + + // Show the "Add New Job" form when the button is clicked + $('.add-new-job-button').on('click', function(e) { + e.preventDefault(); + $('#add-new-job-form').slideDown(); + $(this).hide(); // Hide the "Add New Job" button itself + }); + + // Hide the "Add New Job" form when the cancel button is clicked + $('#add-new-job-form .cancel-add-job').on('click', function(e) { + e.preventDefault(); + $('#add-new-job-form').slideUp(function() { + // Optional: Clear form fields on cancel + // $('#new-job-form')[0].reset(); + // $('.add-job-status').empty(); + }); + $('.add-new-job-button').show(); // Show the "Add New Job" button again + }); + + // --- Send Invite Form --- + + // Show the specific "Send Invite" form row when the link is clicked + $('.send-job-invite').on('click', function(e) { + e.preventDefault(); + var jobId = $(this).data('job-id'); + var inviteRow = $('#send-invite-row-' + jobId); + + // Hide any other open invite forms first + $('.send-invite-form-row').not(inviteRow).slideUp(); + + // Toggle the clicked one + inviteRow.slideToggle(); + }); + + // Hide the "Send Invite" form row when its cancel button is clicked + $('.send-invite-form-row .cancel-invite').on('click', function(e) { + e.preventDefault(); + $(this).closest('.send-invite-form-row').slideUp(); + }); + + // --- AJAX Form Submissions --- + + // Add New Job AJAX + $('#new-job-form').on('submit', function(e) { + e.preventDefault(); + var $form = $(this); + var $submitButton = $form.find('input[type="submit"]'); + var $spinner = $form.find('.spinner'); + var $statusDiv = $form.find('.add-job-status'); + + $statusDiv.empty().removeClass('error success'); + $spinner.css('visibility', 'visible'); + $submitButton.prop('disabled', true); + + var formData = { + action: 'add_new_job', + nonce: quiztechThemeData.add_job_nonce, + job_title: $form.find('#job_title').val(), + job_description: $form.find('#job_description').val() + }; + + $.post(quiztechThemeData.ajax_url, formData) + .done(function(response) { + if (response.success) { + $statusDiv.text(quiztechThemeData.job_added_success).addClass('success'); + // Optionally: Clear form, hide it, refresh job list via another AJAX call or page reload + $form[0].reset(); + $('#add-new-job-form').slideUp(); + $('.add-new-job-button').show(); + // Consider adding the new job row dynamically instead of full reload + location.reload(); // Simple reload for now + } else { + $statusDiv.text(response.data.message || quiztechThemeData.error_generic).addClass('error'); + } + }) + .fail(function() { + $statusDiv.text(quiztechThemeData.error_generic).addClass('error'); + }) + .always(function() { + $spinner.css('visibility', 'hidden'); + $submitButton.prop('disabled', false); + }); + }); + + // Send Invite AJAX + $('.send-invite-submit').on('click', function(e) { + e.preventDefault(); + var $button = $(this); + var jobId = $button.data('job-id'); + var $wrapper = $button.closest('.send-invite-form-wrapper'); + var $emailInput = $wrapper.find('input[name="applicant_email"]'); + var $spinner = $wrapper.find('.spinner'); + var $statusDiv = $wrapper.find('.invite-status'); + var applicantEmail = $emailInput.val(); + + if (!applicantEmail) { + $emailInput.focus(); + // Maybe add a small visual cue that email is required + return; + } + + $statusDiv.empty().removeClass('error success'); + $spinner.css('visibility', 'visible'); + $button.prop('disabled', true).siblings('.cancel-invite').prop('disabled', true); + + var formData = { + action: 'send_job_invite', + nonce: quiztechThemeData.send_invite_nonce, + job_id: jobId, + applicant_email: applicantEmail + }; + + $.post(quiztechThemeData.ajax_url, formData) + .done(function(response) { + if (response.success) { + $statusDiv.text(quiztechThemeData.invite_sent_success).addClass('success'); + $emailInput.val(''); // Clear email field on success + // Optionally hide the form row after a delay + setTimeout(function() { + $button.closest('.send-invite-form-row').slideUp(); + }, 2000); + } else { + var errorMessage = quiztechThemeData.error_generic; + if (response.data && response.data.message) { + errorMessage = response.data.message; + } else if (response.data && response.data === 'error_missing_assessment') { + // Example of handling specific error code if needed + errorMessage = quiztechThemeData.error_missing_assessment; + } + $statusDiv.text(errorMessage).addClass('error'); + } + }) + .fail(function() { + $statusDiv.text(quiztechThemeData.error_generic).addClass('error'); + }) + .always(function() { + $spinner.css('visibility', 'hidden'); + $button.prop('disabled', false).siblings('.cancel-invite').prop('disabled', false); + }); + }); + +}); \ No newline at end of file diff --git a/single.php b/single.php new file mode 100644 index 0000000..4ace211 --- /dev/null +++ b/single.php @@ -0,0 +1,54 @@ + + +
> +
> + +
+
+ + + +
+
+ +
> +
+ ', '' ); ?> +
+ +
+

+ +
+ +
+ +
+
+ + + +
+
+ +
> +
+ ', '' ); ?> +
+ +
+

+ +
+ +
+ +
+
+ + + +
+
+ +
> +
+ ', '' ); ?> +
+ +
+

+ 'job', + 'posts_per_page' => -1, // Get all jobs + 'author' => $current_user_id, + 'post_status' => 'any', // Include drafts, pending, etc. + 'orderby' => 'date', + 'order' => 'DESC', + ); + + $user_jobs_query = new WP_Query( $args ); + + if ( $user_jobs_query->have_posts() ) : + ?> + + + + + + + + + + have_posts() ) : $user_jobs_query->the_post(); ?> + + + + + + + + + + +
+ + + + + | + | + +
+ ' . esc_html__( 'You have not created any jobs yet.', 'quiztech' ) . '

'; + endif; + + // Restore original Post Data + wp_reset_postdata(); + ?> + +

+ + + +
+ +
+ +
+
+ + + +
+
+ +
> +
+ ', '' ); ?> +
+ +
+

+ +
+ +
+ +
+
+ + + +
+
+ +
> +
+ ', '' ); ?> +
+ +
+

+ +
+ +
+ +
+
+ + + +
+
+ +
> +
+ ', '' ); ?> +
+ +
+

+ +
+ +
+ +
+
+ +