diff --git a/functions.php b/functions.php index 863ff55..159d70c 100644 --- a/functions.php +++ b/functions.php @@ -71,15 +71,23 @@ function quiztech_theme_enqueue_scripts() { // 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' ), + 'save_job_nonce' => wp_create_nonce( 'quiztech_save_job_action' ), // Renamed from add_job_nonce + 'get_job_nonce' => wp_create_nonce( 'quiztech_get_job_action' ), // Nonce for fetching job data 'send_invite_nonce' => wp_create_nonce( 'quiztech_send_job_invite_action' ), - 'save_question_nonce' => wp_create_nonce( 'quiztech_save_question_action' ), // Nonce for saving - 'get_question_nonce' => wp_create_nonce( 'quiztech_get_question_action' ), // Nonce for fetching + 'save_question_nonce' => wp_create_nonce( 'quiztech_save_question_action' ), // Nonce for saving question + 'get_question_nonce' => wp_create_nonce( 'quiztech_get_question_action' ), // Nonce for fetching question '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 translatable strings for JS + 'l10n' => [ + 'addNewJobTitle' => esc_html__( 'Add New Job Details', 'quiztech' ), + 'editJobTitle' => esc_html__( 'Edit Job Details', 'quiztech' ), + 'saveJobButton' => esc_html__( 'Save Job', 'quiztech' ), + 'updateJobButton'=> esc_html__( 'Update Job', 'quiztech' ), + 'loading' => esc_html__( 'Loading...', 'quiztech' ), + ], ]); } @@ -113,49 +121,131 @@ function quiztech_theme_enqueue_scripts() { 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' ) ) { +/** + * AJAX handler for fetching job data for editing. + */ +function quiztech_ajax_get_job() { + // 1. Verify Nonce + check_ajax_referer( 'quiztech_get_job_action', 'nonce' ); + + // 2. Check Capabilities + $capability = 'manage_options'; // Or a more specific capability like 'edit_jobs' + if ( ! current_user_can( $capability ) ) { 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; + if ( ! $job_id ) { + wp_send_json_error( [ 'message' => esc_html__( 'Invalid job ID provided.', 'quiztech' ) ], 400 ); + } + + // 4. Fetch Post + $post = get_post( $job_id ); + + // 5. Validate Post + if ( ! $post || $post->post_type !== 'job' ) { + wp_send_json_error( [ 'message' => esc_html__( 'Job not found.', 'quiztech' ) ], 404 ); + } + + // Optional: Check if the current user is the author or has higher privileges + if ( $post->post_author != get_current_user_id() && ! current_user_can( 'edit_others_posts' ) ) { // Adjust capability check if needed + wp_send_json_error( [ 'message' => esc_html__( 'You do not have permission to view this job\'s details.', 'quiztech' ) ], 403 ); + } + + // 6. Send Response + wp_send_json_success( [ + 'id' => $post->ID, + 'title' => $post->post_title, + 'description' => $post->post_content, + ] ); +} +add_action( 'wp_ajax_quiztech_get_job', 'quiztech_ajax_get_job' ); + + +/** + * AJAX handler for saving (adding or updating) a job post from the frontend. + */ +function quiztech_ajax_save_job() { + // 1. Verify Nonce + check_ajax_referer( 'quiztech_save_job_action', 'nonce' ); // Use new nonce action + + // 2. Check Capabilities (adjust capability if needed) + $capability = 'manage_options'; // Or a more specific capability like 'edit_jobs' + if ( ! current_user_can( $capability ) ) { + 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; $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'] ) ) : ''; + $current_user_id = get_current_user_id(); + // Basic Validation if ( empty( $job_title ) ) { wp_send_json_error( [ 'message' => esc_html__( 'Job title cannot be empty.', 'quiztech' ) ], 400 ); } - // 4. Create Post + // 4. Prepare Post Data $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_status' => 'publish', // Default to publish, adjust if needed ]; - $post_id = wp_insert_post( $post_data, true ); // Pass true to return WP_Error on failure + $result = 0; + $is_update = false; + + // 5. Determine if Update or Insert + if ( $job_id > 0 ) { + // --- Update Existing Job --- + $is_update = true; + $existing_post = get_post( $job_id ); + + // Validation for update + if ( ! $existing_post || $existing_post->post_type !== 'job' ) { + wp_send_json_error( [ 'message' => esc_html__( 'Job not found.', 'quiztech' ) ], 404 ); + } + // Optional: Check if the current user is the author or has higher privileges + if ( $existing_post->post_author != $current_user_id && ! current_user_can( 'edit_others_posts' ) ) { // Adjust capability check if needed + wp_send_json_error( [ 'message' => esc_html__( 'You do not have permission to edit this job.', 'quiztech' ) ], 403 ); + } + + $post_data['ID'] = $job_id; // Set ID for update + $result = wp_update_post( $post_data, true ); - // 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 + // --- Insert New Job --- + $post_data['post_author'] = $current_user_id; // Set author only for new posts + $result = wp_insert_post( $post_data, true ); + } + + // 6. Send Response + if ( is_wp_error( $result ) ) { + wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 ); + } elseif ( $result === 0 ) { + // wp_update_post returns 0 if nothing changed, which isn't an error here. + // Treat it as success but maybe with a different message? For now, same success message. + wp_send_json_success( [ + 'message' => esc_html__( 'Job details saved (no changes detected).', 'quiztech' ), + 'job_id' => $job_id // Return the existing ID + ] ); + } else { + // Success (Insert or Update) + $success_message = $is_update + ? esc_html__( 'Job updated successfully.', 'quiztech' ) + : esc_html__( 'Job created successfully.', 'quiztech' ); + wp_send_json_success( [ - 'message' => esc_html__( 'Job created successfully.', 'quiztech' ), - 'post_id' => $post_id + 'message' => $success_message, + 'job_id' => $result // $result is the post ID on success ] ); } } -add_action( 'wp_ajax_add_new_job', 'quiztech_ajax_add_new_job' ); +add_action( 'wp_ajax_quiztech_save_job', 'quiztech_ajax_save_job' ); // Use new action hook /** diff --git a/js/quiztech-theme.js b/js/quiztech-theme.js index 82f14c3..fdf51e1 100644 --- a/js/quiztech-theme.js +++ b/js/quiztech-theme.js @@ -1,26 +1,146 @@ jQuery(document).ready(function($) { - // --- Add New Job Form --- + // --- Job Add/Edit Form Elements --- + const formWrapper = $('#add-new-job-form'); + const formElement = $('#new-job-form'); + const jobIdField = $('#quiztech-job-id'); + const jobTitleField = $('#job_title'); + const jobDescriptionField = $('#job_description'); + const formTitle = formWrapper.find('h2'); // Target the h2 inside the form div + const submitButton = formElement.find('input[type="submit"]'); + const cancelButton = formElement.find('.cancel-add-job'); + const addButton = $('.add-new-job-button'); + const statusDiv = formWrapper.find('.add-job-status'); + const spinner = formWrapper.find('.spinner'); + const jobTableBody = $('.quiztech-manage-table tbody'); // For event delegation - // Show the "Add New Job" form when the button is clicked - $('.add-new-job-button').on('click', function(e) { + // --- Helper: Reset and Prepare Form --- + function resetAndPrepareForm(mode = 'add') { + formElement[0].reset(); // Reset native form fields + jobIdField.val(''); // Clear hidden ID + statusDiv.empty().removeClass('error success'); // Clear status messages + spinner.css('visibility', 'hidden'); + + if (mode === 'add') { + formTitle.text(quiztechThemeData.l10n.addNewJobTitle || 'Add New Job Details'); // Use localized string + submitButton.val(quiztechThemeData.l10n.saveJobButton || 'Save Job'); + submitButton.prop('disabled', false); + } else { // 'edit' mode + formTitle.text(quiztechThemeData.l10n.editJobTitle || 'Edit Job Details'); // Use localized string + submitButton.val(quiztechThemeData.l10n.updateJobButton || 'Update Job'); + submitButton.prop('disabled', false); + } + } + + // --- Add New Job Button --- + addButton.on('click', function(e) { e.preventDefault(); - $('#add-new-job-form').slideDown(); + resetAndPrepareForm('add'); // Prepare form for adding + formWrapper.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) { + // --- Cancel Button (in Add/Edit Form) --- + cancelButton.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(); + formWrapper.slideUp(function() { + resetAndPrepareForm('add'); // Reset to add mode visuals when hiding }); - $('.add-new-job-button').show(); // Show the "Add New Job" button again + addButton.show(); // Show the "Add New Job" button again }); - // --- Send Invite Form --- + // --- Edit Job Button Click (Event Delegation) --- + jobTableBody.on('click', '.quiztech-edit-job-btn', function() { + const $button = $(this); + const jobId = $button.data('job-id'); + const originalButtonText = $button.text(); + + // Prevent double clicks and show loading state + if ($button.prop('disabled')) { + return; + } + $button.prop('disabled', true).text(quiztechThemeData.l10n.loading || 'Loading...'); + statusDiv.empty().removeClass('error success'); // Clear form status + + $.ajax({ + url: quiztechThemeData.ajax_url, + type: 'POST', + data: { + action: 'quiztech_get_job', + nonce: quiztechThemeData.get_job_nonce, // Use new nonce + job_id: jobId + }, + dataType: 'json', + success: function(response) { + if (response.success) { + const data = response.data; + resetAndPrepareForm('edit'); // Prepare form visuals for editing + + // Populate form fields + jobIdField.val(data.id); + jobTitleField.val(data.title); + jobDescriptionField.val(data.description); + + formWrapper.slideDown(); // Show the form + // Scroll to form for better UX + $('html, body').animate({ scrollTop: formWrapper.offset().top - 50 }, 300); + } else { + // Display error near the button or globally? For now, alert. + alert(response.data.message || quiztechThemeData.error_generic); + } + }, + error: function(jqXHR, textStatus, errorThrown) { + console.error("AJAX Error fetching job:", textStatus, errorThrown, jqXHR.responseText); + alert(quiztechThemeData.error_generic); + }, + complete: function() { + // Restore button state + $button.prop('disabled', false).text(originalButtonText); + } + }); + }); + + + // --- Add/Edit Job Form Submission AJAX --- + formElement.on('submit', function(e) { + e.preventDefault(); + + statusDiv.empty().removeClass('error success'); + spinner.css('visibility', 'visible'); + submitButton.prop('disabled', true); + + var formData = { + action: 'quiztech_save_job', // Use new action + nonce: quiztechThemeData.save_job_nonce, // Use new nonce + job_id: jobIdField.val(), // Include job_id (will be empty for new jobs) + job_title: jobTitleField.val(), + job_description: jobDescriptionField.val() + }; + + $.post(quiztechThemeData.ajax_url, formData) + .done(function(response) { + if (response.success) { + statusDiv.text(response.data.message || 'Job saved successfully.').addClass('success'); + // Reload page to see changes (simplest approach) + // TODO: Implement dynamic row update/add for better UX later + setTimeout(function() { location.reload(); }, 1500); + } else { + statusDiv.text(response.data.message || quiztechThemeData.error_generic).addClass('error'); + submitButton.prop('disabled', false); // Re-enable button on failure + } + }) + .fail(function(jqXHR, textStatus, errorThrown) { + console.error("AJAX Error saving job:", textStatus, errorThrown, jqXHR.responseText); + statusDiv.text(quiztechThemeData.error_generic).addClass('error'); + submitButton.prop('disabled', false); // Re-enable button on failure + }) + .always(function() { + spinner.css('visibility', 'hidden'); + // Keep button disabled on success until reload + }); + }); + + // --- Send Invite Form (Existing Logic - Unchanged) --- // Show the specific "Send Invite" form row when the link is clicked $('.send-job-invite').on('click', function(e) { @@ -41,50 +161,6 @@ jQuery(document).ready(function($) { $(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(); @@ -92,33 +168,31 @@ jQuery(document).ready(function($) { 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 $inviteSpinner = $wrapper.find('.spinner'); // Use different spinner variable + var $inviteStatusDiv = $wrapper.find('.invite-status'); // Use different status div variable 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'); + $inviteStatusDiv.empty().removeClass('error success'); + $inviteSpinner.css('visibility', 'visible'); $button.prop('disabled', true).siblings('.cancel-invite').prop('disabled', true); - var formData = { + var inviteFormData = { // Use different formData variable action: 'send_job_invite', nonce: quiztechThemeData.send_invite_nonce, job_id: jobId, applicant_email: applicantEmail }; - $.post(quiztechThemeData.ajax_url, formData) + $.post(quiztechThemeData.ajax_url, inviteFormData) .done(function(response) { if (response.success) { - $statusDiv.text(quiztechThemeData.invite_sent_success).addClass('success'); + $inviteStatusDiv.text(quiztechThemeData.invite_sent_success || 'Invite sent successfully!').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); @@ -126,18 +200,16 @@ jQuery(document).ready(function($) { 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'); + $inviteStatusDiv.text(errorMessage).addClass('error'); } }) - .fail(function() { - $statusDiv.text(quiztechThemeData.error_generic).addClass('error'); + .fail(function(jqXHR, textStatus, errorThrown) { + console.error("AJAX Error sending invite:", textStatus, errorThrown, jqXHR.responseText); + $inviteStatusDiv.text(quiztechThemeData.error_generic).addClass('error'); }) .always(function() { - $spinner.css('visibility', 'hidden'); + $inviteSpinner.css('visibility', 'hidden'); $button.prop('disabled', false).siblings('.cancel-invite').prop('disabled', false); }); }); diff --git a/template-manage-jobs.php b/template-manage-jobs.php index 1ee92f5..3fc27b2 100644 --- a/template-manage-jobs.php +++ b/template-manage-jobs.php @@ -63,12 +63,8 @@ get_header(); ?> - - | + + | | @@ -107,6 +103,7 @@ get_header(); ?>


+