03
This commit is contained in:
parent
fa86852137
commit
d5d05fd44f
81
api/generate.php
Normal file
81
api/generate.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . '/../includes/gemini.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$apiKey = getenv('GEMINI_API_KEY');
|
||||
if (!$apiKey) {
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'API key is not configured.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$request_body = file_get_contents('php://input');
|
||||
$form_data = json_decode($request_body, true);
|
||||
|
||||
// --- PROMPT ENGINEERING --- //
|
||||
$prompt = "Please generate a professional resume based on the following details. Format the output as clean, semantic HTML suitable for direct display on a web page. The design should be modern and professional. Use a single-column layout. Do not include any CSS or ```html markers, only the raw HTML body content.";
|
||||
$prompt .= "\n\n---\n\n";
|
||||
|
||||
// Personal Details
|
||||
$prompt .= "**Personal Details:**\n";
|
||||
$prompt .= "- Name: " . ($form_data['fullName'] ?? 'N/A') . "\n";
|
||||
$prompt .= "- Email: " . ($form_data['email'] ?? 'N/A') . "\n";
|
||||
$prompt .= "- Phone: " . ($form_data['phone'] ?? 'N/A') . "\n";
|
||||
$prompt .= "- LinkedIn: " . ($form_data['linkedin'] ?? 'N/A') . "\n\n";
|
||||
|
||||
// Summary
|
||||
if (!empty($form_data['summary'])) {
|
||||
$prompt .= "**Professional Summary:**\n" . $form_data['summary'] . "\n\n";
|
||||
}
|
||||
|
||||
// Work Experience
|
||||
if (!empty($form_data['experience'])) {
|
||||
$prompt .= "**Work Experience:**\n";
|
||||
foreach ($form_data['experience'] as $exp) {
|
||||
$prompt .= "- Job Title: " . ($exp['title'] ?? 'N/A') . " at " . ($exp['company'] ?? 'N/A') . "\n";
|
||||
$prompt .= " (From: " . ($exp['startDate'] ?? 'N/A') . " to " . ($exp['endDate'] ?? 'Present') . ")\n";
|
||||
if (!empty($exp['responsibilities'])) {
|
||||
$prompt .= " Responsibilities: " . $exp['responsibilities'] . "\n";
|
||||
}
|
||||
}
|
||||
$prompt .= "\n";
|
||||
}
|
||||
|
||||
// Education
|
||||
if (!empty($form_data['education'])) {
|
||||
$prompt .= "**Education:**\n";
|
||||
foreach ($form_data['education'] as $edu) {
|
||||
$prompt .= "- Degree: " . ($edu['degree'] ?? 'N/A') . " from " . ($edu['school'] ?? 'N/A') . "\n";
|
||||
$prompt .= " (From: " . ($edu['startDate'] ?? 'N/A') . " to " . ($edu['endDate'] ?? 'Present') . ")\n";
|
||||
}
|
||||
$prompt .= "\n";
|
||||
}
|
||||
|
||||
// Skills
|
||||
if (!empty($form_data['skills'])) {
|
||||
$prompt .= "**Skills:**\n" . $form_data['skills'] . "\n";
|
||||
}
|
||||
|
||||
// Call the Gemini API
|
||||
$api_response = callGeminiApi($prompt, $apiKey);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (isset($api_response['error'])) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => $api_response['error'], 'details' => $api_response['response'] ?? null]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$_SESSION['generated_resume'] = $api_response['html'];
|
||||
$_SESSION['resume_prompt'] = $prompt; // Storing for debug or retry purposes
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Resume generated successfully.']);
|
||||
|
||||
} else {
|
||||
header('HTTP/1.1 405 Method Not Allowed');
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request method.']);
|
||||
}
|
||||
40
api/pexels.php
Normal file
40
api/pexels.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once __DIR__.'/../includes/pexels.php';
|
||||
|
||||
// Fetch a variety of professional-looking document/resume templates
|
||||
$query = 'resume template document';
|
||||
$orientation = 'portrait';
|
||||
$per_page = 12;
|
||||
$url = 'https://api.pexels.com/v1/search?query=' . urlencode($query) . '&orientation=' . urlencode($orientation) . '&per_page=' . $per_page;
|
||||
|
||||
$data = pexels_get($url);
|
||||
|
||||
if (!$data || empty($data['photos'])) {
|
||||
echo json_encode(['error' => 'Failed to fetch images from Pexels.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$output = [];
|
||||
foreach ($data['photos'] as $photo) {
|
||||
// Use a smaller, optimized image for the grid view
|
||||
$src = $photo['src']['large'] ?? $photo['src']['original'];
|
||||
$filename = $photo['id'] . '.jpg';
|
||||
$local_path = __DIR__ . '/../assets/images/pexels/' . $filename;
|
||||
|
||||
// Download the image if it doesn't already exist to avoid repeated downloads
|
||||
if (!file_exists($local_path)) {
|
||||
download_to($src, $local_path);
|
||||
}
|
||||
|
||||
$output[] = [
|
||||
'id' => $photo['id'],
|
||||
'local_url' => 'assets/images/pexels/' . $filename,
|
||||
'photographer' => $photo['photographer'] ?? 'Unknown',
|
||||
'photographer_url' => $photo['photographer_url'] ?? '#',
|
||||
'alt' => $photo['alt'] ?? 'Resume template preview'
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode($output);
|
||||
?>
|
||||
@ -14,11 +14,15 @@ body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Used on login page */
|
||||
body.auth-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
@ -69,30 +73,31 @@ body {
|
||||
margin-right: 1.5rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent; /* Add transparent border */
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active {
|
||||
color: var(--text-primary);
|
||||
border-bottom: 2px solid #6A11CB;
|
||||
background: none; /* Override bootstrap active bg */
|
||||
background: none;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link:hover {
|
||||
border-color: #333; /* Subtle hover effect */
|
||||
border-color: #333;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active:hover {
|
||||
border-bottom-color: #6A11CB; /* Keep active color on hover */
|
||||
border-bottom-color: #6A11CB;
|
||||
}
|
||||
|
||||
|
||||
.form-control {
|
||||
background-color: #2a2a2a;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0.75rem 1rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
@ -100,6 +105,7 @@ body {
|
||||
color: var(--text-primary);
|
||||
border-color: #6A11CB;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
@ -112,7 +118,9 @@ body {
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
@ -132,6 +140,7 @@ body {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
@ -161,3 +170,254 @@ body {
|
||||
.divider:not(:empty)::after {
|
||||
margin-left: .25em;
|
||||
}
|
||||
|
||||
/* General App Layout */
|
||||
.page-wrapper {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 260px;
|
||||
background-color: var(--surface-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1.5rem;
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.sidebar-header h2 {
|
||||
font-weight: 700;
|
||||
font-size: 1.8rem;
|
||||
background: var(--primary-gradient);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.sidebar-nav a {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 0.8rem 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: 0.5rem;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar-nav a:hover {
|
||||
background-color: #2a2a2a;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.sidebar-nav a.active {
|
||||
background-color: rgba(106, 17, 203, 0.2);
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#logout-btn-sidebar {
|
||||
margin-top: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 2rem 3rem;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
/* Template Selection Grid */
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
position: relative;
|
||||
border-radius: var(--border-radius);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--border-color);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.template-card img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 3 / 4;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.template-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.template-card:hover .template-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.template-overlay .btn-select {
|
||||
background: var(--primary-gradient);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 0.6rem 1.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Form Styles */
|
||||
.form-container {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background-color: var(--surface-color);
|
||||
padding: 2rem;
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.form-section h2 {
|
||||
font-size: 1.5rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
textarea.form-control {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #3a3a3a;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #4a4a4a;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.dynamic-section {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
position: relative;
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
|
||||
.btn-remove {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-remove:hover {
|
||||
color: #ff4d4d;
|
||||
}
|
||||
|
||||
/* Loading Overlay */
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
display: none; /* Hidden by default */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.loading-overlay.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 8px solid var(--border-color);
|
||||
border-top: 8px solid #6A11CB;
|
||||
border-radius: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.loading-overlay p {
|
||||
color: var(--text-primary);
|
||||
margin-top: 1rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
157
assets/js/form.js
Normal file
157
assets/js/form.js
Normal file
@ -0,0 +1,157 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const experienceWrapper = document.getElementById('experience-wrapper');
|
||||
const addExperienceBtn = document.getElementById('add-experience-btn');
|
||||
let experienceCount = 0;
|
||||
|
||||
const educationWrapper = document.getElementById('education-wrapper');
|
||||
const addEducationBtn = document.getElementById('add-education-btn');
|
||||
let educationCount = 0;
|
||||
|
||||
// Function to add a new experience block
|
||||
const addExperience = () => {
|
||||
experienceCount++;
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('dynamic-section');
|
||||
div.innerHTML = `
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label for="job-title-${experienceCount}">Job Title</label>
|
||||
<input type="text" id="job-title-${experienceCount}" name="experience[${experienceCount}][title]">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="company-${experienceCount}">Company</label>
|
||||
<input type="text" id="company-${experienceCount}" name="experience[${experienceCount}][company]">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="exp-start-date-${experienceCount}">Start Date</label>
|
||||
<input type="month" id="exp-start-date-${experienceCount}" name="experience[${experienceCount}][startDate]">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="exp-end-date-${experienceCount}">End Date</label>
|
||||
<input type="month" id="exp-end-date-${experienceCount}" name="experience[${experienceCount}][endDate]">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="responsibilities-${experienceCount}">Responsibilities</label>
|
||||
<textarea id="responsibilities-${experienceCount}" name="experience[${experienceCount}][responsibilities]" rows="3"></textarea>
|
||||
</div>
|
||||
<button type="button" class="btn-remove">Remove</button>
|
||||
`;
|
||||
experienceWrapper.appendChild(div);
|
||||
};
|
||||
|
||||
// Function to add a new education block
|
||||
const addEducation = () => {
|
||||
educationCount++;
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('dynamic-section');
|
||||
div.innerHTML = `
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label for="degree-${educationCount}">Degree / Certificate</label>
|
||||
<input type="text" id="degree-${educationCount}" name="education[${educationCount}][degree]">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="school-${educationCount}">School / Institution</label>
|
||||
<input type="text" id="school-${educationCount}" name="education[${educationCount}][school]">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="edu-start-date-${educationCount}">Start Date</label>
|
||||
<input type="month" id="edu-start-date-${educationCount}" name="education[${educationCount}][startDate]">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="edu-end-date-${educationCount}">End Date</label>
|
||||
<input type="month" id="edu-end-date-${educationCount}" name="education[${educationCount}][endDate]">
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn-remove">Remove</button>
|
||||
`;
|
||||
educationWrapper.appendChild(div);
|
||||
};
|
||||
|
||||
// Event listeners
|
||||
addExperienceBtn.addEventListener('click', addExperience);
|
||||
addEducationBtn.addEventListener('click', addEducation);
|
||||
|
||||
// Remove button delegation
|
||||
experienceWrapper.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('btn-remove')) {
|
||||
e.target.closest('.dynamic-section').remove();
|
||||
}
|
||||
});
|
||||
|
||||
educationWrapper.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('btn-remove')) {
|
||||
e.target.closest('.dynamic-section').remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Add one of each by default
|
||||
addExperience();
|
||||
addEducation();
|
||||
|
||||
// Form Submission
|
||||
const resumeForm = document.getElementById('resume-form');
|
||||
const loadingOverlay = document.getElementById('loading-overlay');
|
||||
|
||||
resumeForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
loadingOverlay.classList.add('show');
|
||||
|
||||
const formData = new FormData(resumeForm);
|
||||
const data = {
|
||||
fullName: formData.get('fullName'),
|
||||
email: formData.get('email'),
|
||||
phone: formData.get('phone'),
|
||||
linkedin: formData.get('linkedin'),
|
||||
summary: formData.get('summary'),
|
||||
skills: formData.get('skills'),
|
||||
experience: [],
|
||||
education: []
|
||||
};
|
||||
|
||||
// Process dynamic fields
|
||||
const expSections = experienceWrapper.querySelectorAll('.dynamic-section');
|
||||
expSections.forEach((sec, index) => {
|
||||
data.experience.push({
|
||||
title: sec.querySelector(`input[name='experience[${index + 1}][title]']`).value,
|
||||
company: sec.querySelector(`input[name='experience[${index + 1}][company]']`).value,
|
||||
startDate: sec.querySelector(`input[name='experience[${index + 1}][startDate]']`).value,
|
||||
endDate: sec.querySelector(`input[name='experience[${index + 1}][endDate]']`).value,
|
||||
responsibilities: sec.querySelector(`textarea[name='experience[${index + 1}][responsibilities]']`).value
|
||||
});
|
||||
});
|
||||
|
||||
const eduSections = educationWrapper.querySelectorAll('.dynamic-section');
|
||||
eduSections.forEach((sec, index) => {
|
||||
data.education.push({
|
||||
degree: sec.querySelector(`input[name='education[${index + 1}][degree]']`).value,
|
||||
school: sec.querySelector(`input[name='education[${index + 1}][school]']`).value,
|
||||
startDate: sec.querySelector(`input[name='education[${index + 1}][startDate]']`).value,
|
||||
endDate: sec.querySelector(`input[name='education[${index + 1}][endDate]']`).value
|
||||
});
|
||||
});
|
||||
|
||||
fetch('api/generate.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
window.location.href = 'result.php';
|
||||
} else {
|
||||
alert('An error occurred: ' + result.message);
|
||||
loadingOverlay.classList.remove('show');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('A network error occurred. Please try again.');
|
||||
loadingOverlay.classList.remove('show');
|
||||
});
|
||||
});
|
||||
});
|
||||
123
create.php
Normal file
123
create.php
Normal file
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
// Initialize the session
|
||||
session_start();
|
||||
|
||||
// Check if the user is logged in, if not then redirect him to login page
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("location: index.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$template_img = isset($_GET['template']) ? htmlspecialchars($_GET['template']) : 'assets/images/pexels/placeholder.jpg';
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Create Your Resume</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<div class="page-wrapper">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2>AI Resume Builder</h2>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.php">Home</a>
|
||||
<a href="create.php" class="active">Create</a>
|
||||
<a href="#" id="logout-btn-sidebar">Sign Out</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="page-header">
|
||||
<h1>Create Your Resume</h1>
|
||||
<p>Fill out the details below to generate your professional resume.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-container">
|
||||
<form id="resume-form">
|
||||
<!-- Personal Details -->
|
||||
<section class="form-section">
|
||||
<h2>Personal Details</h2>
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label for="full-name">Full Name</label>
|
||||
<input type="text" id="full-name" name="fullName" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phone">Phone Number</label>
|
||||
<input type="tel" id="phone" name="phone">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="linkedin">LinkedIn Profile</label>
|
||||
<input type="url" id="linkedin" name="linkedin">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="summary">Professional Summary</label>
|
||||
<textarea id="summary" name="summary" rows="4"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Work Experience -->
|
||||
<section class="form-section">
|
||||
<h2>Work Experience</h2>
|
||||
<div id="experience-wrapper">
|
||||
<!-- JS will inject fields here -->
|
||||
</div>
|
||||
<button type="button" id="add-experience-btn" class="btn-secondary">Add Experience</button>
|
||||
</section>
|
||||
|
||||
<!-- Education -->
|
||||
<section class="form-section">
|
||||
<h2>Education</h2>
|
||||
<div id="education-wrapper">
|
||||
<!-- JS will inject fields here -->
|
||||
</div>
|
||||
<button type="button" id="add-education-btn" class="btn-secondary">Add Education</button>
|
||||
</section>
|
||||
|
||||
<!-- Skills -->
|
||||
<section class="form-section">
|
||||
<h2>Skills</h2>
|
||||
<div class="form-group">
|
||||
<label for="skills">Skills (comma-separated)</label>
|
||||
<input type="text" id="skills" name="skills" placeholder="e.g., Python, Project Management, Graphic Design">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn-primary">Generate Resume</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-app-compat.js"></script>
|
||||
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-auth-compat.js"></script>
|
||||
<div id="loading-overlay" class="loading-overlay">
|
||||
<div class="spinner"></div>
|
||||
<p>Generating your resume...</p>
|
||||
</div>
|
||||
|
||||
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-app-compat.js"></script>
|
||||
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-auth-compat.js"></script>
|
||||
<script src="assets/js/auth.js?v=<?php echo time(); ?>"></script>
|
||||
<script src="assets/js/form.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
112
home.php
112
home.php
@ -1,55 +1,85 @@
|
||||
<?php
|
||||
// Initialize the session
|
||||
session_start();
|
||||
|
||||
// Check if the user is logged in, if not then redirect him to login page
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("location: index.php");
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Home - AI Resume Builder</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container text-center vh-100 d-flex flex-column justify-content-center">
|
||||
<h1 class="text-light">Welcome to the AI Resume Builder</h1>
|
||||
<p class="text-light">You are logged in. Template selection coming soon!</p>
|
||||
<button id="signOutBtn" class="btn btn-danger mt-3 mx-auto" style="max-width: 200px;">Sign Out</button>
|
||||
<body class="dark-theme">
|
||||
<div class="page-wrapper">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2>AI Resume Builder</h2>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.php" class="active">Home</a>
|
||||
<a href="create.php">Create</a>
|
||||
<a href="#" id="logout-btn-sidebar">Sign Out</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="page-header">
|
||||
<h1>Choose Your Template</h1>
|
||||
<p>Select a template to start building your professional resume.</p>
|
||||
</div>
|
||||
|
||||
<div id="template-grid" class="template-grid">
|
||||
<!-- Templates will be loaded here by JavaScript -->
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-app.js";
|
||||
import { getAuth, signOut, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-auth.js";
|
||||
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-app-compat.js"></script>
|
||||
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-auth-compat.js"></script>
|
||||
<script src="assets/js/auth.js?v=<?php echo time(); ?>"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const grid = document.getElementById('template-grid');
|
||||
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIzaSyAuabZynwAn8r91_toMzdN2_vCVOwLsAf8",
|
||||
authDomain: "ai-resume-builder-90135.firebaseapp.com",
|
||||
projectId: "ai-resume-builder-90135",
|
||||
storageBucket: "ai-resume-builder-90135.firebasestorage.app",
|
||||
messagingSenderId: "465451014283",
|
||||
appId: "1:465451014283:web:355ddebe12bc8a2dfedc8e",
|
||||
measurementId: "G-4B91V0ZY58"
|
||||
};
|
||||
|
||||
const app = initializeApp(firebaseConfig);
|
||||
const auth = getAuth(app);
|
||||
|
||||
// Redirect to login if not authenticated
|
||||
onAuthStateChanged(auth, (user) => {
|
||||
if (!user) {
|
||||
window.location.href = 'index.php';
|
||||
}
|
||||
});
|
||||
|
||||
// Sign out functionality
|
||||
const signOutBtn = document.getElementById('signOutBtn');
|
||||
signOutBtn.addEventListener('click', () => {
|
||||
signOut(auth).then(() => {
|
||||
// Sign-out successful.
|
||||
window.location.href = 'index.php';
|
||||
}).catch((error) => {
|
||||
// An error happened.
|
||||
console.error('Sign out error:', error);
|
||||
alert('Failed to sign out. Please try again.');
|
||||
});
|
||||
fetch('api/pexels.php')
|
||||
.then(response => response.json())
|
||||
.then(images => {
|
||||
if (!images || images.error) {
|
||||
grid.innerHTML = `<p style="color: #ff4d4d;">Error loading templates: ${images ? images.error : 'Unknown error'}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
images.forEach(image => {
|
||||
const templateURL = `create.php?template=${encodeURIComponent(image.local_url)}`;
|
||||
html += `
|
||||
<a href="${templateURL}" class="template-card">
|
||||
<img src="${image.local_url}" alt="${image.alt || 'Resume Template'}">
|
||||
<div class="template-overlay">
|
||||
<span class="btn-select">Select</span>
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
});
|
||||
grid.innerHTML = html;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to fetch templates:', error);
|
||||
grid.innerHTML = '<p style="color: #ffcc00;">Could not load templates. Please try again later.</p>';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
50
includes/gemini.php
Normal file
50
includes/gemini.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
function callGeminiApi($prompt, $apiKey) {
|
||||
$url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=' . $apiKey;
|
||||
|
||||
$data = [
|
||||
'contents' => [
|
||||
[
|
||||
'parts' => [
|
||||
[
|
||||
'text' => $prompt
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$json_data = json_encode($data);
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curl_error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curl_error) {
|
||||
return ['error' => 'cURL Error: ' . $curl_error];
|
||||
}
|
||||
|
||||
if ($http_code !== 200) {
|
||||
return ['error' => 'API responded with HTTP code ' . $http_code, 'response' => json_decode($response, true)];
|
||||
}
|
||||
|
||||
$result = json_decode($response, true);
|
||||
|
||||
if (isset($result['candidates'][0]['content']['parts'][0]['text'])) {
|
||||
return ['success' => true, 'html' => $result['candidates'][0]['content']['parts'][0]['text']];
|
||||
} else {
|
||||
return ['error' => 'Could not extract content from API response.', 'response' => $result];
|
||||
}
|
||||
}
|
||||
33
includes/pexels.php
Normal file
33
includes/pexels.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
function pexels_key() {
|
||||
$k = getenv('PEXELS_KEY');
|
||||
return $k && strlen($k) > 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18';
|
||||
}
|
||||
|
||||
function pexels_get($url) {
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ],
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
function download_to($srcUrl, $destPath) {
|
||||
// Ensure the destination directory exists
|
||||
$dir = dirname($destPath);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0775, true);
|
||||
}
|
||||
|
||||
$data = file_get_contents($srcUrl);
|
||||
if ($data === false) return false;
|
||||
return file_put_contents($destPath, $data) !== false;
|
||||
}
|
||||
?>
|
||||
81
result.php
Normal file
81
result.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
// Check if the user is logged in
|
||||
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
|
||||
header("location: index.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get the generated resume from the session
|
||||
$resume_html = $_SESSION['generated_resume'] ?? '<div class="error-message">Could not find the generated resume. Please try again.</div>';
|
||||
|
||||
// Unset the session variable so it doesn't persist
|
||||
// unset($_SESSION['generated_resume']);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Your Generated Resume</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
<style>
|
||||
.resume-preview-container {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
max-width: 8.5in;
|
||||
min-height: 11in;
|
||||
margin: 2rem auto;
|
||||
padding: 1in;
|
||||
box-shadow: 0 0 15px rgba(0,0,0,0.5);
|
||||
border-radius: 5px;
|
||||
}
|
||||
.action-buttons {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<div class="page-wrapper">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2>AI Resume Builder</h2>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a href="home.php">Home</a>
|
||||
<a href="create.php">Create</a>
|
||||
<a href="#" id="logout-btn-sidebar">Sign Out</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<div class="page-header">
|
||||
<h1>Your Generated Resume</h1>
|
||||
<p>Review your resume below. You can copy the content or download it in various formats.</p>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="btn-secondary">Download as PDF</button>
|
||||
<button class="btn-secondary">Download as DOCX</button>
|
||||
</div>
|
||||
|
||||
<div class="resume-preview-container">
|
||||
<?php echo $resume_html; ?>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-app-compat.js"></script>
|
||||
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-auth-compat.js"></script>
|
||||
<script src="assets/js/auth.js?v=<?php echo time(); ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user