Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5d05fd44f | ||
|
|
fa86852137 | ||
|
|
19ef21748b |
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);
|
||||||
|
?>
|
||||||
423
assets/css/custom.css
Normal file
423
assets/css/custom.css
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background-color: #121212;
|
||||||
|
--surface-color: #1E1E1E;
|
||||||
|
--primary-gradient: linear-gradient(90deg, #6A11CB 0%, #2575FC 100%);
|
||||||
|
--text-primary: #FFFFFF;
|
||||||
|
--text-secondary: #B0B0B0;
|
||||||
|
--border-color: #333333;
|
||||||
|
--border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
min-height: 700px;
|
||||||
|
background-color: var(--surface-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-visual {
|
||||||
|
flex: 1;
|
||||||
|
background: url('https://picsum.photos/seed/resume-art/800/1200') no-repeat center center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 4rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-header h1 {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-header p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs {
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-right: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link.active {
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-bottom: 2px solid #6A11CB;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link:hover {
|
||||||
|
border-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-link.active: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 {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-color: #6A11CB;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-google {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #000;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-google:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider::before,
|
||||||
|
.divider::after {
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider:not(:empty)::before {
|
||||||
|
margin-right: .25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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); }
|
||||||
|
}
|
||||||
105
assets/js/auth.js
Normal file
105
assets/js/auth.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-app.js";
|
||||||
|
import {
|
||||||
|
getAuth,
|
||||||
|
createUserWithEmailAndPassword,
|
||||||
|
signInWithEmailAndPassword,
|
||||||
|
GoogleAuthProvider,
|
||||||
|
signInWithPopup,
|
||||||
|
onAuthStateChanged
|
||||||
|
} from "https://www.gstatic.com/firebasejs/9.6.1/firebase-auth.js";
|
||||||
|
|
||||||
|
// Your web app's Firebase configuration
|
||||||
|
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"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize Firebase
|
||||||
|
const app = initializeApp(firebaseConfig);
|
||||||
|
const auth = getAuth(app);
|
||||||
|
const provider = new GoogleAuthProvider();
|
||||||
|
|
||||||
|
// DOM Elements
|
||||||
|
const loginForm = document.getElementById('loginForm');
|
||||||
|
const signupForm = document.getElementById('signupForm');
|
||||||
|
const googleLoginBtn = document.getElementById('googleLoginBtn');
|
||||||
|
const googleSignupBtn = document.getElementById('googleSignupBtn');
|
||||||
|
const errorContainer = document.getElementById('errorContainer');
|
||||||
|
|
||||||
|
// Function to display errors
|
||||||
|
const showError = (message) => {
|
||||||
|
errorContainer.textContent = message;
|
||||||
|
errorContainer.style.display = 'block';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Redirect if user is already logged in
|
||||||
|
onAuthStateChanged(auth, (user) => {
|
||||||
|
if (user) {
|
||||||
|
window.location.href = 'home.php';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Email/Password Sign Up
|
||||||
|
if (signupForm) {
|
||||||
|
signupForm.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const email = signupForm.querySelector('#signupEmail').value;
|
||||||
|
const password = signupForm.querySelector('#signupPassword').value;
|
||||||
|
|
||||||
|
createUserWithEmailAndPassword(auth, email, password)
|
||||||
|
.then((userCredential) => {
|
||||||
|
// Signed in
|
||||||
|
window.location.href = 'home.php';
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showError(error.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email/Password Login
|
||||||
|
if (loginForm) {
|
||||||
|
loginForm.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const email = loginForm.querySelector('#loginEmail').value;
|
||||||
|
const password = loginForm.querySelector('#loginPassword').value;
|
||||||
|
|
||||||
|
signInWithEmailAndPassword(auth, email, password)
|
||||||
|
.then((userCredential) => {
|
||||||
|
// Signed in
|
||||||
|
window.location.href = 'home.php';
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showError(error.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Google Sign-In
|
||||||
|
const handleGoogleSignIn = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
signInWithPopup(auth, provider)
|
||||||
|
.then((result) => {
|
||||||
|
// This gives you a Google Access Token. You can use it to access the Google API.
|
||||||
|
const credential = GoogleAuthProvider.credentialFromResult(result);
|
||||||
|
const token = credential.accessToken;
|
||||||
|
// The signed-in user info.
|
||||||
|
const user = result.user;
|
||||||
|
window.location.href = 'home.php';
|
||||||
|
}).catch((error) => {
|
||||||
|
// Handle Errors here.
|
||||||
|
showError(error.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (googleLoginBtn) {
|
||||||
|
googleLoginBtn.addEventListener('click', handleGoogleSignIn);
|
||||||
|
}
|
||||||
|
if (googleSignupBtn) {
|
||||||
|
googleSignupBtn.addEventListener('click', handleGoogleSignIn);
|
||||||
|
}
|
||||||
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>
|
||||||
85
home.php
Normal file
85
home.php
Normal file
@ -0,0 +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 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" 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 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');
|
||||||
|
|
||||||
|
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>
|
||||||
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;
|
||||||
|
}
|
||||||
|
?>
|
||||||
220
index.php
220
index.php
@ -1,131 +1,101 @@
|
|||||||
<?php
|
<!DOCTYPE html>
|
||||||
declare(strict_types=1);
|
|
||||||
@ini_set('display_errors', '1');
|
|
||||||
@error_reporting(E_ALL);
|
|
||||||
@date_default_timezone_set('UTC');
|
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
|
||||||
$now = date('Y-m-d H:i:s');
|
|
||||||
?>
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>New Style</title>
|
<title>AI Resume Builder - Login</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<meta name="description" content="Log in or sign up to create your professional resume with the help of AI.">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
<!-- Open Graph -->
|
||||||
<style>
|
<meta property="og:title" content="AI Resume Builder">
|
||||||
:root {
|
<meta property="og:description" content="Build your perfect resume with AI-powered assistance.">
|
||||||
--bg-color-start: #6a11cb;
|
<meta property="og:type" content="website">
|
||||||
--bg-color-end: #2575fc;
|
<meta property="og:url" content="http://<?php echo $_SERVER['HTTP_HOST']; ?>">
|
||||||
--text-color: #ffffff;
|
<meta property="og:image" content="https://picsum.photos/seed/resume-og/1200/630">
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
}
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% { background-position: 0% 0%; }
|
|
||||||
100% { background-position: 100% 100%; }
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.loader {
|
|
||||||
margin: 1.25rem auto 1.25rem;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px; height: 1px;
|
|
||||||
padding: 0; margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap; border: 0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
background: rgba(0,0,0,0.2);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
|
||||||
<div class="card">
|
<div class="auth-container">
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
<div class="auth-visual">
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
<!-- Alt text is handled by CSS background, but if it were an <img>:
|
||||||
<span class="sr-only">Loading…</span>
|
alt="Abstract visual representing creativity and document creation." -->
|
||||||
</div>
|
</div>
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWiZZy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
<div class="auth-content">
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
<div class="auth-header">
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
<h1>AI Resume Builder</h1>
|
||||||
|
<p>Welcome back! Please login to your account.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<ul class="nav nav-tabs" id="authTabs" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="login-tab" data-bs-toggle="tab" data-bs-target="#login" type="button" role="tab" aria-controls="login" aria-selected="true">Login</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="signup-tab" data-bs-toggle="tab" data-bs-target="#signup" type="button" role="tab" aria-controls="signup" aria-selected="false">Sign Up</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<div id="errorContainer" class="alert alert-danger mt-3" style="display: none;"></div>
|
||||||
|
|
||||||
|
<!-- Tab Content -->
|
||||||
|
<div class="tab-content pt-4" id="authTabsContent">
|
||||||
|
<!-- Login Pane -->
|
||||||
|
<div class="tab-pane fade show active" id="login" role="tabpanel" aria-labelledby="login-tab">
|
||||||
|
<form id="loginForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="loginEmail" class="form-label visually-hidden">Email address</label>
|
||||||
|
<input type="email" class="form-control" id="loginEmail" placeholder="Email address" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="loginPassword" class="form-label visually-hidden">Password</label>
|
||||||
|
<input type="password" class="form-control" id="loginPassword" placeholder="Password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Login</button>
|
||||||
|
</form>
|
||||||
|
<div class="divider">or</div>
|
||||||
|
<button class="btn btn-google" id="googleLoginBtn">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-google" viewBox="0 0 16 16">
|
||||||
|
<path d="M15.545 6.558a9.42 9.42 0 0 1 .139 1.626c0 2.434-.87 4.492-2.384 5.885h.002C11.978 15.292 10.158 16 8 16A8 8 0 1 1 8 0a7.689 7.689 0 0 1 5.352 2.082l-2.284 2.284A4.347 4.347 0 0 0 8 3.166c-2.087 0-3.86 1.408-4.492 3.304a4.792 4.792 0 0 0 0 3.063h.003c.635 1.893 2.405 3.301 4.492 3.301 1.078 0 2.004-.276 2.722-.764h-.003a3.702 3.702 0 0 0 1.599-2.431H8v-3.08h7.545z"/>
|
||||||
|
</svg>
|
||||||
|
Sign in with Google
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sign Up Pane -->
|
||||||
|
<div class="tab-pane fade" id="signup" role="tabpanel" aria-labelledby="signup-tab">
|
||||||
|
<form id="signupForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="signupName" class="form-label visually-hidden">Full Name</label>
|
||||||
|
<input type="text" class="form-control" id="signupName" placeholder="Full Name" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="signupEmail" class="form-label visually-hidden">Email address</label>
|
||||||
|
<input type="email" class="form-control" id="signupEmail" placeholder="Email address" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="signupPassword" class="form-label visually-hidden">Password</label>
|
||||||
|
<input type="password" class="form-control" id="signupPassword" placeholder="Password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Create Account</button>
|
||||||
|
</form>
|
||||||
|
<div class="divider">or</div>
|
||||||
|
<button class="btn btn-google" id="googleSignupBtn">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-google" viewBox="0 0 16 16">
|
||||||
|
<path d="M15.545 6.558a9.42 9.42 0 0 1 .139 1.626c0 2.434-.87 4.492-2.384 5.885h.002C11.978 15.292 10.158 16 8 16A8 8 0 1 1 8 0a7.689 7.689 0 0 1 5.352 2.082l-2.284 2.284A4.347 4.347 0 0 0 8 3.166c-2.087 0-3.86 1.408-4.492 3.304a4.792 4.792 0 0 0 0 3.063h.003c.635 1.893 2.405 3.301 4.492 3.301 1.078 0 2.004-.276 2.722-.764h-.003a3.702 3.702 0 0 0 1.599-2.431H8v-3.08h7.545z"/>
|
||||||
|
</svg>
|
||||||
|
Sign up with Google
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
|
||||||
<footer>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<script type="module" src="assets/js/auth.js?v=<?php echo time(); ?>"></script>
|
||||||
</footer>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
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