Compare commits

..

7 Commits

Author SHA1 Message Date
Flatlogic Bot
1ac3977059 ONLY_TRUE_FEEDBACK_OCT_2025 2025-10-08 16:26:26 +00:00
Flatlogic Bot
adc3e210b7 autofill survey form 2025-10-07 23:06:02 +00:00
Flatlogic Bot
6255e8b900 charts 2025-10-07 22:38:32 +00:00
Flatlogic Bot
cab661dbb4 v4 2025-10-07 21:54:29 +00:00
Flatlogic Bot
d0ad1dc6cd © 2025 Your Real Feedback 2025-10-07 17:31:34 +00:00
Flatlogic Bot
a29976994c v2 2025-10-07 16:34:00 +00:00
Flatlogic Bot
d47e350516 v1 2025-10-07 16:17:37 +00:00
38 changed files with 1964 additions and 153 deletions

129
admin.php Normal file
View File

@ -0,0 +1,129 @@
<?php
session_start();
// Check if user is logged in and is an Admin
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
header('Location: login.php');
exit;
}
require_once 'db/config.php';
// Handle deletion of a submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submission_id_to_delete'])) {
$submission_id = $_POST['submission_id_to_delete'];
// First, delete the associated answers
$delete_answers_stmt = db()->prepare("DELETE FROM survey_answers WHERE submission_id = ?");
$delete_answers_stmt->execute([$submission_id]);
// Then, delete the submission itself
$delete_submission_stmt = db()->prepare("DELETE FROM feedback_submissions WHERE id = ?");
$delete_submission_stmt->execute([$submission_id]);
// Redirect to the same page to see the changes
header('Location: admin.php?page=' . (isset($_GET['page']) ? $_GET['page'] : 1));
exit;
}
// Pagination
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$records_per_page = 10;
$offset = ($page - 1) * $records_per_page;
// Get total number of submissions
$total_stmt = db()->query("SELECT COUNT(*) FROM feedback_submissions");
$total_records = $total_stmt->fetchColumn();
$total_pages = ceil($total_records / $records_per_page);
// Fetch submissions for the current page
$submissions_stmt = db()->prepare("SELECT s.id, s.name, s.email, s.created_at, sv.title as survey_title, sv.id as survey_id FROM feedback_submissions s JOIN surveys sv ON s.survey_id = sv.id ORDER BY s.created_at DESC LIMIT :limit OFFSET :offset");
$submissions_stmt->bindValue(':limit', $records_per_page, PDO::PARAM_INT);
$submissions_stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$submissions_stmt->execute();
$submissions = $submissions_stmt->fetchAll(PDO::FETCH_ASSOC);
$pageTitle = "Admin - Feedback Submissions";
require_once 'templates/header.php';
?>
<main>
<section class="survey-section">
<div class="container">
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h1 class="h3">Feedback Submissions</h1>
<div>
<a href="dashboard.php" class="btn btn-info">Dashboard</a>
<a href="surveys.php" class="btn btn-success">Manage Surveys</a>
<a href="export.php" class="btn btn-primary">Export to CSV</a>
<a href="logout.php" class="btn btn-secondary">Logout</a>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead class="thead-dark">
<tr>
<th>Survey</th>
<th>Submitter</th>
<th>Email</th>
<th>Submitted At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($submissions)):
?>
<tr>
<td colspan="5">No feedback submissions yet.</td>
</tr>
<?php else:
?>
<?php foreach ($submissions as $submission):
?>
<tr>
<td><?= htmlspecialchars($submission['survey_title']) ?></td>
<td><?= htmlspecialchars($submission['name']) ?></td>
<td><?= htmlspecialchars($submission['email']) ?></td>
<td><?= $submission['created_at'] ?></td>
<td>
<a href="view_submission.php?id=<?= $submission['id'] ?>" class="btn btn-sm btn-info">View</a>
<form method="POST" action="admin.php" style="display: inline-block;" onsubmit="return confirm('Are you sure you want to delete this submission?');">
<input type="hidden" name="submission_id_to_delete" value="<?= $submission['id'] ?>">
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
</form>
</td>
</tr>
<?php endforeach;
?>
<?php endif;
?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Pagination Links -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<?php if ($page > 1): ?>
<li class="page-item"><a class="page-link" href="?page=<?= $page - 1 ?>">Previous</a></li>
<?php endif; ?>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?= ($i == $page) ? 'active' : '' ?>"><a class="page-link" href="?page=<?= $i ?>"><?= $i ?></a></li>
<?php endfor; ?>
<?php if ($page < $total_pages): ?>
<li class="page-item"><a class="page-link" href="?page=<?= $page + 1 ?>">Next</a></li>
<?php endif; ?>
</ul>
</nav>
</div>
</section>
</main>
<?php
require_once 'templates/footer.php';
?>

88
api.php Normal file
View File

@ -0,0 +1,88 @@
<?php
header('Content-Type: application/json');
session_start();
require_once 'db/config.php';
// Ensure admin is logged in
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
echo json_encode(['error' => 'Unauthorized']);
exit;
}
$action = $_GET['action'] ?? '';
switch ($action) {
case 'submissions_per_survey':
$stmt = db()->query("SELECT s.title, COUNT(fs.id) as submission_count FROM surveys s LEFT JOIN feedback_submissions fs ON s.id = fs.survey_id GROUP BY s.id");
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
$labels = array_column($results, 'title');
$values = array_column($results, 'submission_count');
echo json_encode(['labels' => $labels, 'values' => $values]);
break;
case 'survey_question_analytics':
$surveys_stmt = db()->query("SELECT id, title FROM surveys");
$surveys = $surveys_stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$questions_stmt = db()->query("SELECT survey_id, id, question_text, question_type FROM survey_questions WHERE question_type = 'rating' OR question_type = 'multiple-choice'");
$questions = $questions_stmt->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_ASSOC);
$answers_stmt = db()->query("SELECT q.id as question_id, a.answer_text FROM survey_answers a JOIN survey_questions q ON a.question_id = q.id WHERE q.question_type = 'rating' OR q.question_type = 'multiple-choice'");
$answers_by_question = $answers_stmt->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_COLUMN);
$response = [];
foreach ($surveys as $survey_id => $survey_title) {
$survey_data = [
'id' => $survey_id,
'title' => $survey_title,
'questions' => []
];
if (isset($questions[$survey_id])) {
foreach ($questions[$survey_id] as $question) {
$question_id = $question['id'];
$question_data = [
'id' => $question_id,
'question_text' => $question['question_text'],
'type' => $question['question_type'],
];
$answers = $answers_by_question[$question_id] ?? [];
if ($question['question_type'] == 'multiple-choice') {
$answer_counts = [];
foreach ($answers as $answer_row) {
$options = preg_split('/,\s*/', $answer_row);
foreach ($options as $option) {
$trimmed_option = trim($option);
if (!empty($trimmed_option)) {
if (!isset($answer_counts[$trimmed_option])) {
$answer_counts[$trimmed_option] = 0;
}
$answer_counts[$trimmed_option]++;
}
}
}
$question_data['answers'] = [
'labels' => array_keys($answer_counts),
'values' => array_values($answer_counts)
];
} else { // rating
$answer_counts = array_count_values($answers);
$question_data['answers'] = [
'labels' => array_keys($answer_counts),
'values' => array_values($answer_counts)
];
}
$survey_data['questions'][] = $question_data;
}
}
$response[] = $survey_data;
}
echo json_encode($response);
break;
default:
echo json_encode(['error' => 'Invalid action']);
break;
}

78
assets/css/custom.css Normal file
View File

@ -0,0 +1,78 @@
:root {
--primary-color: #556EE6;
--secondary-color: #F8B425;
--background-color: #F5F7FA;
--text-color: #333;
--light-gray: #E9ECEF;
--dark-gray: #7A7A7A;
}
body {
background-color: var(--background-color);
color: var(--text-color);
font-family: 'Inter', sans-serif;
}
.navbar {
background-color: #fff;
border-bottom: 1px solid var(--light-gray);
}
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.btn-primary:hover {
background-color: #4055B2;
border-color: #4055B2;
}
.btn-secondary {
background-color: var(--secondary-color);
border-color: var(--secondary-color);
}
.btn-secondary:hover {
background-color: #E0A01E;
border-color: #E0A01E;
}
a {
color: var(--primary-color);
}
a:hover {
color: #4055B2;
}
.card {
border: 1px solid var(--light-gray);
border-radius: 0.5rem;
}
.table {
border: 1px solid var(--light-gray);
}
.hidden {
display: none;
}
.navbar-text {
margin-left: 10px;
font-size: 1.25rem;
font-weight: 600;
}
.step {
display: none;
}
.step.active {
display: block;
}
.progress {
margin-bottom: 20px;
}

92
assets/js/dashboard.js Normal file
View File

@ -0,0 +1,92 @@
document.addEventListener('DOMContentLoaded', function () {
// Chart for submissions per survey
fetch('api.php?action=submissions_per_survey')
.then(response => response.json())
.then(data => {
const ctx = document.getElementById('submissions-chart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: data.labels,
datasets: [{
label: '# of Submissions',
data: data.values,
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
});
// Charts for each survey's questions
fetch('api.php?action=survey_question_analytics')
.then(response => response.json())
.then(surveys => {
const container = document.getElementById('survey-charts-container');
surveys.forEach(survey => {
const surveyRow = document.createElement('div');
surveyRow.className = 'row';
const surveyTitle = document.createElement('h2');
surveyTitle.innerText = `Survey: ${survey.title}`;
container.appendChild(surveyTitle);
container.appendChild(surveyRow);
survey.questions.forEach(question => {
if (question.type === 'rating' || question.type === 'multiple-choice') {
const col = document.createElement('div');
col.className = 'col-md-6';
const card = document.createElement('div');
card.className = 'card mb-4';
const cardHeader = document.createElement('div');
cardHeader.className = 'card-header';
cardHeader.innerHTML = `<h3>${question.question_text}</h3>`;
const cardBody = document.createElement('div');
cardBody.className = 'card-body';
const canvas = document.createElement('canvas');
cardBody.appendChild(canvas);
card.appendChild(cardHeader);
card.appendChild(cardBody);
col.appendChild(card);
surveyRow.appendChild(col);
new Chart(canvas.getContext('2d'), {
type: 'bar',
data: {
labels: question.answers.labels,
datasets: [{
label: 'Count',
data: question.answers.values,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
}
}
}
}
});
}
});
});
});
});

115
assets/js/main.js Normal file
View File

@ -0,0 +1,115 @@
document.addEventListener('DOMContentLoaded', function () {
const surveyForm = document.getElementById('survey-form');
if (!surveyForm) return;
const steps = Array.from(surveyForm.querySelectorAll('.step'));
const nextBtn = document.getElementById('nextBtn');
const prevBtn = document.getElementById('prevBtn');
const submitBtn = document.getElementById('submitBtn');
const progressBar = surveyForm.querySelector('.progress-bar');
let currentStep = 0;
function showStep(stepIndex) {
steps.forEach((step, index) => {
step.classList.toggle('active', index === stepIndex);
});
updateProgressBar();
updateButtons();
}
function updateProgressBar() {
const progress = ((currentStep + 1) / steps.length) * 100;
progressBar.style.width = progress + '%';
progressBar.setAttribute('aria-valuenow', progress);
}
function updateButtons() {
prevBtn.style.display = currentStep === 0 ? 'none' : 'inline-block';
nextBtn.style.display = currentStep === steps.length - 1 ? 'none' : 'inline-block';
submitBtn.style.display = currentStep === steps.length - 1 ? 'inline-block' : 'none';
}
function validateStep(stepIndex) {
const currentStepElement = steps[stepIndex];
const inputs = Array.from(currentStepElement.querySelectorAll('input, textarea'));
let isValid = true;
inputs.forEach(input => {
if (input.hasAttribute('required')) {
if (input.type === 'radio' || input.type === 'checkbox') {
const name = input.name;
if (!surveyForm.querySelector(`input[name="${name}"]:checked`)) {
isValid = false;
}
} else if (!input.value.trim()) {
isValid = false;
}
}
});
return isValid;
}
nextBtn.addEventListener('click', () => {
if (!validateStep(currentStep)) {
alert('Please answer the question before proceeding.');
return;
}
if (currentStep < steps.length - 1) {
currentStep++;
showStep(currentStep);
}
});
prevBtn.addEventListener('click', () => {
if (currentStep > 0) {
currentStep--;
showStep(currentStep);
}
});
showStep(currentStep);
const successMessage = document.getElementById('success-message');
const formContainer = document.querySelector('.form-container');
surveyForm.addEventListener('submit', function (e) {
e.preventDefault();
const formData = new FormData(this);
const emailInput = document.getElementById('email');
if (emailInput) {
formData.append('email', emailInput.value);
}
fetch('submit_feedback.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (formContainer) {
formContainer.classList.add('hidden');
}
if (successMessage) {
successMessage.classList.remove('hidden');
}
} else {
alert('An error occurred: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error:', error);
alert('A server error occurred. Please try again later.');
});
});
function validateEmail(email) {
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

55
dashboard.php Normal file
View File

@ -0,0 +1,55 @@
<?php
session_start();
require_once 'db/config.php';
// Ensure admin is logged in
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
header('Location: login.php');
exit;
}
$pageTitle = "Analytics Dashboard";
require_once 'templates/header.php';
?>
<main>
<section class="survey-section">
<div class="container">
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h1 class="h3">Analytics Dashboard</h1>
<a href="admin.php" class="btn btn-secondary">Back to Submissions</a>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-12">
<div class="card mb-4">
<div class="card-header">
<h3>Submissions per Survey</h3>
</div>
<div class="card-body">
<canvas id="submissions-chart"></canvas>
</div>
</div>
</div>
</div>
<hr>
<h2 class="h4 mb-4">Survey Question Analytics</h2>
<div id="survey-charts-container"></div>
</div>
</div>
</div>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="assets/js/dashboard.js?v=<?php echo time(); ?>"></script>
<?php
require_once 'templates/footer.php';
?>

View File

@ -8,10 +8,50 @@ define('DB_PASS', 'e45f2778-db1f-450c-99c6-29efb4601472');
function db() { function db() {
static $pdo; static $pdo;
if (!$pdo) { if (!$pdo) {
try {
// Connect to MySQL server without specifying a database
$pdo = new PDO('mysql:host='.DB_HOST.';charset=utf8mb4', DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
// Create the database if it doesn't exist
$pdo->exec('CREATE DATABASE IF NOT EXISTS `'.DB_NAME.'`');
// Re-connect to the specific database
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [ $pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]); ]);
} catch (PDOException $e) {
// If database creation fails, it's a critical error.
error_log('Database connection or creation failed: ' . $e->getMessage());
// Exit or handle the error gracefully. For a web request, you might show a generic error page.
http_response_code(500);
echo "Database connection failed. Please check the server logs.";
exit;
}
} }
return $pdo; return $pdo;
} }
function run_migrations() {
$pdo = db();
$migrations_dir = __DIR__ . '/migrations';
if (!is_dir($migrations_dir)) {
return;
}
$files = glob($migrations_dir . '/*.sql');
foreach ($files as $file) {
$sql = file_get_contents($file);
try {
$pdo->exec($sql);
} catch (PDOException $e) {
// Log error or handle it as needed
error_log("Migration failed for file: " . basename($file) . " with error: " . $e->getMessage());
}
}
}
run_migrations();

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS feedback_submissions (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS surveys (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS survey_questions (
id INT AUTO_INCREMENT PRIMARY KEY,
survey_id INT NOT NULL,
question_text TEXT NOT NULL,
question_type VARCHAR(50) NOT NULL, -- e.g., 'text', 'multiple-choice', 'rating'
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (survey_id) REFERENCES surveys(id) ON DELETE CASCADE
);

View File

@ -0,0 +1 @@
ALTER TABLE `feedback_submissions` ADD COLUMN `survey_id` INT NULL AFTER `id`;

View File

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS survey_answers (
id INT AUTO_INCREMENT PRIMARY KEY,
submission_id INT NOT NULL,
question_id INT NOT NULL,
answer_text TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (submission_id) REFERENCES feedback_submissions(id) ON DELETE CASCADE,
FOREIGN KEY (question_id) REFERENCES survey_questions(id) ON DELETE CASCADE
);

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,4 @@
CREATE TABLE IF NOT EXISTS roles (
id INT AUTO_INCREMENT PRIMARY KEY,
role_name VARCHAR(50) NOT NULL UNIQUE
);

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS user_roles (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
role_id INT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
);

View File

@ -0,0 +1 @@
INSERT IGNORE INTO `roles` (`role_name`) VALUES ('Admin'), ('Respondent');

View File

@ -0,0 +1 @@
ALTER TABLE feedback_submissions MODIFY COLUMN message TEXT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE survey_questions ADD COLUMN options TEXT NULL AFTER question_type;

220
debug_log.txt Executable file
View File

@ -0,0 +1,220 @@
--- NEW REQUEST ---
Surveys:
Array
(
[1] => Your True Flatlogic Feedback
)
Questions:
Array
(
[1] => Array
(
[0] => Array
(
[survey_id] => 1
[question_text] => What stopped you from upgrading after creating your app?
[question_type] => multiple-choice
)
)
[2] => Array
(
[0] => Array
(
[survey_id] => 1
[question_text] => What would help you continue?
[question_type] => multiple-choice
)
)
)
Answers by Question:
Array
(
[1] => Array
(
[0] => The generated app didnt look good enough, Bugs or errors on the platform, It used too many credits / hosting felt expensive
)
[2] => Array
(
[0] => A short call with an engineer, Clearer tutorials/documentation, Access to a specific template/feature
)
)
Processing Survey ID: 1
Processing Question ID:
Answers for Question :
Array
(
)
Processed Question Data:
Array
(
[id] =>
[question_text] => What stopped you from upgrading after creating your app?
[type] => multiple-choice
[answers] => Array
(
[labels] => Array
(
)
[values] => Array
(
)
)
)
Final Response:
Array
(
[0] => Array
(
[id] => 1
[title] => Your True Flatlogic Feedback
[questions] => Array
(
[0] => Array
(
[id] =>
[question_text] => What stopped you from upgrading after creating your app?
[type] => multiple-choice
[answers] => Array
(
[labels] => Array
(
)
[values] => Array
(
)
)
)
)
)
)
--- NEW REQUEST ---
Surveys:
Array
(
[1] => Your True Flatlogic Feedback
)
Questions:
Array
(
[1] => Array
(
[0] => Array
(
[survey_id] => 1
[question_text] => What stopped you from upgrading after creating your app?
[question_type] => multiple-choice
)
)
[2] => Array
(
[0] => Array
(
[survey_id] => 1
[question_text] => What would help you continue?
[question_type] => multiple-choice
)
)
)
Answers by Question:
Array
(
[1] => Array
(
[0] => The generated app didnt look good enough, Bugs or errors on the platform, It used too many credits / hosting felt expensive
)
[2] => Array
(
[0] => A short call with an engineer, Clearer tutorials/documentation, Access to a specific template/feature
)
)
Processing Survey ID: 1
Processing Question ID:
Answers for Question :
Array
(
)
Processed Question Data:
Array
(
[id] =>
[question_text] => What stopped you from upgrading after creating your app?
[type] => multiple-choice
[answers] => Array
(
[labels] => Array
(
)
[values] => Array
(
)
)
)
Final Response:
Array
(
[0] => Array
(
[id] => 1
[title] => Your True Flatlogic Feedback
[questions] => Array
(
[0] => Array
(
[id] =>
[question_text] => What stopped you from upgrading after creating your app?
[type] => multiple-choice
[answers] => Array
(
[labels] => Array
(
)
[values] => Array
(
)
)
)
)
)
)

75
edit_survey.php Normal file
View File

@ -0,0 +1,75 @@
<?php
session_start();
require_once 'db/config.php';
// Ensure admin is logged in
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
header('Location: login.php');
exit;
}
// Check for Survey ID
if (!isset($_GET['id'])) {
header('Location: surveys.php');
exit;
}
$survey_id = $_GET['id'];
// Fetch survey details
$survey_stmt = db()->prepare("SELECT * FROM surveys WHERE id = ?");
$survey_stmt->execute([$survey_id]);
$survey = $survey_stmt->fetch();
if (!$survey) {
header('Location: surveys.php');
exit;
}
// Handle form submission for updating survey
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['title'])) {
$title = trim($_POST['title']);
$description = trim($_POST['description']);
if (!empty($title)) {
$stmt = db()->prepare("UPDATE surveys SET title = ?, description = ? WHERE id = ?");
$stmt->execute([$title, $description, $survey_id]);
header("Location: surveys.php"); // Redirect to the survey list
exit;
}
}
$pageTitle = "Edit Survey";
require_once 'templates/header.php';
?>
<main>
<section class="survey-section">
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Edit Survey</h1>
<a href="surveys.php" class="btn btn-secondary">Back to Surveys</a>
</div>
<!-- Edit Survey Form -->
<div class="card">
<div class="card-body">
<h2 class="card-title">Edit Survey</h2>
<form method="POST">
<div class="form-group">
<label for="title" class="form-label">Survey Title</label>
<input type="text" id="title" name="title" class="form-control" value="<?= htmlspecialchars($survey['title']) ?>" required>
</div>
<div class="form-group">
<label for="description" class="form-label">Description (Optional)</label>
<textarea id="description" name="description" rows="3" class="form-control"><?= htmlspecialchars($survey['description']) ?></textarea>
</div>
<button type="submit" class="btn btn-primary">Save Changes</button>
</form>
</div>
</div>
</div>
</section>
</main>
<?php
require_once 'templates/footer.php';
?>

66
export.php Normal file
View File

@ -0,0 +1,66 @@
<?php
session_start();
require_once 'db/config.php';
// Ensure admin is logged in
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
header('Location: login.php');
exit;
}
// Fetch all submissions with their answers
$stmt = db()->query("SELECT
s.id as submission_id,
s.name as submitter_name,
s.email as submitter_email,
s.created_at as submission_date,
sv.title as survey_title,
q.question_text,
a.answer_text
FROM feedback_submissions s
JOIN surveys sv ON s.survey_id = sv.id
LEFT JOIN survey_answers a ON s.id = a.submission_id
LEFT JOIN survey_questions q ON a.question_id = q.id
ORDER BY s.created_at DESC, q.created_at ASC");
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Set headers for CSV download
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=feedback_submissions_' . date('Y-m-d') . '.csv');
$output = fopen('php://output', 'w');
// Get all unique questions in order
$questions_stmt = db()->query("SELECT DISTINCT question_text FROM survey_questions ORDER BY created_at ASC");
$question_headers = $questions_stmt->fetchAll(PDO::FETCH_COLUMN);
// Output the column headings
$headers = array_merge(['Submission ID', 'Submitter', 'Email', 'Date', 'Survey'], $question_headers);
fputcsv($output, $headers);
// Process the results
$submissions = [];
foreach ($results as $row) {
$submissions[$row['submission_id']]['details'] = [
'submission_id' => $row['submission_id'],
'submitter_name' => $row['submitter_name'],
'submitter_email' => $row['submitter_email'],
'submission_date' => $row['submission_date'],
'survey_title' => $row['survey_title']
];
$submissions[$row['submission_id']]['answers'][$row['question_text']] = $row['answer_text'];
}
// Write rows to CSV
foreach ($submissions as $submission_id => $data) {
$row = $data['details'];
foreach ($question_headers as $question) {
$row[] = $data['answers'][$question] ?? '';
}
fputcsv($output, $row);
}
fclose($output);
exit;
?>

226
index.php
View File

@ -1,150 +1,86 @@
<?php <?php
declare(strict_types=1); require_once __DIR__ . '/db/config.php';
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION; // Pagination
$now = date('Y-m-d H:i:s'); $page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$records_per_page = 10;
$offset = ($page - 1) * $records_per_page;
// Get total number of surveys
$total_stmt = db()->query("SELECT COUNT(*) FROM surveys");
$total_records = $total_stmt->fetchColumn();
$total_pages = ceil($total_records / $records_per_page);
// Fetch surveys for the current page
$surveys_stmt = db()->prepare("SELECT * FROM surveys ORDER BY created_at DESC LIMIT :limit OFFSET :offset");
$surveys_stmt->bindValue(':limit', $records_per_page, PDO::PARAM_INT);
$surveys_stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$surveys_stmt->execute();
$surveys = $surveys_stmt->fetchAll();
$pageTitle = "Available Surveys";
$description = "Choose a survey to provide your feedback.";
require_once 'templates/header.php';
?> ?>
<!doctype html>
<html lang="en"> <main>
<head> <section class="hero">
<meta charset="utf-8" /> <div class="container">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <h1>Available Surveys</h1>
<title>New Style</title> <p>Please choose one of the surveys below to provide your feedback.</p>
</div>
</section>
<section class="survey-section">
<div class="container">
<div class="row">
<?php if (empty($surveys)):
?>
<div class="col">
<p>No surveys available at the moment.</p>
</div>
<?php else:
?>
<?php foreach ($surveys as $survey):
?>
<div class="col-md-4 mb-4">
<div class="card h-100">
<div class="card-body d-flex flex-column">
<h5 class="card-title"><?= htmlspecialchars($survey['title']) ?></h5>
<p class="card-text flex-grow-1"><?= htmlspecialchars($survey['description']) ?></p>
<a href="survey.php?id=<?= $survey['id'] ?>" class="btn btn-primary mt-auto">Take Survey</a>
</div>
<div class="card-footer">
<small class="text-muted">Created: <?= date('M j, Y', strtotime($survey['created_at'])) ?></small>
</div>
</div>
</div>
<?php endforeach;
?>
<?php endif;
?>
</div>
<!-- Pagination Links -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center mt-4">
<?php if ($page > 1): ?>
<li class="page-item"><a class="page-link" href="?page=<?= $page - 1 ?>">Previous</a></li>
<?php endif; ?>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?= ($i == $page) ? 'active' : '' ?>"><a class="page-link" href="?page=<?= $i ?>"><?= $i ?></a></li>
<?php endfor; ?>
<?php if ($page < $total_pages): ?>
<li class="page-item"><a class="page-link" href="?page=<?= $page + 1 ?>">Next</a></li>
<?php endif; ?>
</ul>
</nav>
</div>
</section>
</main>
<?php <?php
// Read project preview data from environment require_once 'templates/footer.php';
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?> ?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<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;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
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>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
</body>
</html>

93
login.php Normal file
View File

@ -0,0 +1,93 @@
<?php
session_start();
require_once 'db/config.php';
// If already logged in, redirect to the appropriate page
if (isset($_SESSION['user_id'])) {
if (in_array('Admin', $_SESSION['user_roles'])) {
header('Location: admin.php');
} else {
header('Location: index.php');
}
exit;
}
$pageTitle = "Login";
require_once 'templates/header.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username']);
$password = $_POST['password'];
if (empty($username) || empty($password)) {
$error = 'Please fill out all fields.';
} else {
$pdo = db();
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
// Get user roles
$roles_stmt = $pdo->prepare("SELECT r.role_name FROM user_roles ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = ?");
$roles_stmt->execute([$user['id']]);
$roles = $roles_stmt->fetchAll(PDO::FETCH_COLUMN);
// Store user info in session
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['email'] = $user['email'];
$_SESSION['user_roles'] = $roles;
// Redirect based on role
if (in_array('Admin', $roles)) {
header('Location: admin.php');
} else {
header('Location: index.php');
}
exit;
} else {
$error = 'Invalid username or password.';
}
}
}
?>
<main>
<section class="survey-section">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h1 class="card-title text-center">Login</h1>
<?php if ($error):
?>
<div class="alert alert-danger"><?= $error ?></div>
<?php endif;
?>
<form method="POST">
<div class="form-group">
<label for="username" class="form-label">Username</label>
<input type="text" id="username" name="username" class="form-control" required>
</div>
<div class="form-group">
<label for="password" class="form-label">Password</label>
<input type="password" id="password" name="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Login</button>
</form>
<p class="mt-3 text-center">Don't have an account? <a href="register.php">Register here</a>.</p>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<?php
require_once 'templates/footer.php';
?>

7
logout.php Normal file
View File

@ -0,0 +1,7 @@
<?php
session_start();
session_unset();
session_destroy();
header('Location: login.php');
exit;
?>

144
manage_questions.php Normal file
View File

@ -0,0 +1,144 @@
<?php
session_start();
require_once 'db/config.php';
// Ensure admin is logged in
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
header('Location: login.php');
exit;
}
// Check for Survey ID
if (!isset($_GET['survey_id'])) {
header('Location: surveys.php');
exit;
}
$survey_id = $_GET['survey_id'];
// Fetch survey details
$survey_stmt = db()->prepare("SELECT * FROM surveys WHERE id = ?");
$survey_stmt->execute([$survey_id]);
$survey = $survey_stmt->fetch();
if (!$survey) {
header('Location: surveys.php');
exit;
}
// Handle form submission for new question
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['question_text'])) {
$question_text = trim($_POST['question_text']);
$question_type = trim($_POST['question_type']);
$options = ($question_type === 'multiple-choice') ? trim($_POST['options']) : null;
if (!empty($question_text) && !empty($question_type)) {
$stmt = db()->prepare("INSERT INTO survey_questions (survey_id, question_text, question_type, options) VALUES (?, ?, ?, ?)");
$stmt->execute([$survey_id, $question_text, $question_type, $options]);
header("Location: manage_questions.php?survey_id=" . $survey_id);
exit;
}
}
// Handle question deletion
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_question'])) {
$question_id = $_POST['delete_question'];
$stmt = db()->prepare("DELETE FROM survey_questions WHERE id = ? AND survey_id = ?");
$stmt->execute([$question_id, $survey_id]);
header("Location: manage_questions.php?survey_id=" . $survey_id);
exit;
}
// Fetch all questions for the survey
$questions_stmt = db()->prepare("SELECT * FROM survey_questions WHERE survey_id = ? ORDER BY created_at ASC");
$questions_stmt->execute([$survey_id]);
$questions = $questions_stmt->fetchAll();
$pageTitle = "Manage Questions for " . htmlspecialchars($survey['title']);
require_once 'templates/header.php';
?>
<main>
<section class="survey-section">
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Manage Questions for "<?= htmlspecialchars($survey['title']) ?>"</h1>
<a href="surveys.php" class="btn btn-secondary">Back to Surveys</a>
</div>
<!-- Add New Question Form -->
<div class="card mb-5">
<div class="card-body">
<h2 class="card-title">Add New Question</h2>
<form method="POST">
<div class="form-group">
<label for="question_text" class="form-label">Question Text</label>
<input type="text" id="question_text" name="question_text" class="form-control" required>
</div>
<div class="form-group">
<label for="question_type" class="form-label">Question Type</label>
<select id="question_type" name="question_type" class="form-control" required onchange="toggleOptions(this.value)">
<option value="text">Text (Single Line)</option>
<option value="textarea">Textarea (Multi-line)</option>
<option value="rating">Rating (1-5)</option>
<option value="multiple-choice">Multiple Choice</option>
</select>
</div>
<div class="form-group" id="options-container" style="display: none;">
<label for="options" class="form-label">Options (comma-separated)</label>
<input type="text" id="options" name="options" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Add Question</button>
</form>
</div>
</div>
<!-- List of Existing Questions -->
<h2>Existing Questions</h2>
<div class="row">
<?php if (empty($questions)):
?>
<div class="col">
<p>No questions added to this survey yet.</p>
</div>
<?php else:
?>
<?php foreach ($questions as $question):
?>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title"><?= htmlspecialchars($question['question_text']) ?></h5>
<p class="card-text">
<strong>Type:</strong> <?= htmlspecialchars($question['question_type']) ?><br>
<?php if (!empty($question['options'])):
?>
<strong>Options:</strong> <?= htmlspecialchars($question['options']) ?>
<?php endif;
?>
</p>
</div>
<div class="card-footer">
<form method="POST" action="?survey_id=<?= $survey_id ?>" style="display: inline-block;">
<input type="hidden" name="delete_question" value="<?= $question['id'] ?>">
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this question?')">Delete</button>
</form>
</div>
</div>
</div>
<?php endforeach;
?>
<?php endif;
?>
</div>
</div>
</section>
</main>
<script>
function toggleOptions(type) {
document.getElementById('options-container').style.display = (type === 'multiple-choice') ? 'block' : 'none';
}
</script>
<?php
require_once 'templates/footer.php';
?>

37
privacy.php Normal file
View File

@ -0,0 +1,37 @@
<?php
$pageTitle = "Privacy Policy";
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($pageTitle); ?> - Your Real Feedback</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<header class="header">
<div class="container">
<a href="/" class="logo">Your Real Feedback</a>
</div>
</header>
<main class="container py-5">
<h1>Privacy Policy</h1>
<p>This is a placeholder for your privacy policy. You should replace this with your own terms.</p>
<p>Information we collect and why we collect it...</p>
<p>How we use that information...</p>
<p>etc...</p>
</main>
<footer class="footer">
<div class="container">
<p>&copy; <?php echo date('Y'); ?> Your Real Feedback. All Rights Reserved.</p>
<p><a href="/privacy.php">Privacy Policy</a></p>
</div>
</footer>
</body>
</html>

105
register.php Normal file
View File

@ -0,0 +1,105 @@
<?php
require_once 'db/config.php';
$pageTitle = "Register";
require_once 'templates/header.php';
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username']);
$email = trim($_POST['email']);
$password = $_POST['password'];
$password_confirm = $_POST['password_confirm'];
if (empty($username) || empty($email) || empty($password)) {
$error = 'Please fill out all fields.';
} elseif ($password !== $password_confirm) {
$error = 'Passwords do not match.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error = 'Invalid email format.';
} else {
$pdo = db();
// Check if username or email already exists
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? OR email = ?");
$stmt->execute([$username, $email]);
if ($stmt->fetch()) {
$error = 'Username or email already exists.';
} else {
// Insert new user
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$user_stmt = $pdo->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
$user_stmt->execute([$username, $email, $hashed_password]);
$user_id = $pdo->lastInsertId();
// If this is the first user (id = 1), make them an Admin. Otherwise, assign Respondent.
$user_count_stmt = $pdo->query("SELECT COUNT(*) FROM users");
$is_first_user = ($user_count_stmt->fetchColumn() == 1);
$default_role = $is_first_user ? 'Admin' : 'Respondent';
$role_stmt = $pdo->prepare("SELECT id FROM roles WHERE role_name = ?");
$role_stmt->execute([$default_role]);
$role_id = $role_stmt->fetchColumn();
if ($role_id) {
$user_role_stmt = $pdo->prepare("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)");
$user_role_stmt->execute([$user_id, $role_id]);
}
$success = 'Registration successful! You can now <a href="login.php">login</a>.';
}
}
}
?>
<main>
<section class="survey-section">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h1 class="card-title text-center">Register</h1>
<?php if ($error):
?>
<div class="alert alert-danger"><?= $error ?></div>
<?php endif;
?>
<?php if ($success):
?>
<div class="alert alert-success"><?= $success ?></div>
<?php else:
?>
<form method="POST">
<div class="form-group">
<label for="username" class="form-label">Username</label>
<input type="text" id="username" name="username" class="form-control" required>
</div>
<div class="form-group">
<label for="email" class="form-label">Email Address</label>
<input type="email" id="email" name="email" class="form-control" required>
</div>
<div class="form-group">
<label for="password" class="form-label">Password</label>
<input type="password" id="password" name="password" class="form-control" required>
</div>
<div class="form-group">
<label for="password_confirm" class="form-label">Confirm Password</label>
<input type="password" id="password_confirm" name="password_confirm" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Register</button>
</form>
<?php endif;
?>
<p class="mt-3 text-center">Already have an account? <a href="login.php">Login here</a>.</p>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<?php
require_once 'templates/footer.php';
?>

64
submit_feedback.php Normal file
View File

@ -0,0 +1,64 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/db/config.php';
$response = ['success' => false, 'error' => 'Invalid request'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$survey_id = trim($_POST['survey_id'] ?? '');
$answers = $_POST['answers'] ?? [];
if (empty($name) || empty($survey_id) || empty($answers)) {
$response['error'] = 'Please fill out all fields.';
} else {
$pdo = db();
try {
$pdo->beginTransaction();
// Insert into feedback_submissions
$stmt = $pdo->prepare("INSERT INTO feedback_submissions (name, email, survey_id) VALUES (?, ?, ?)");
$stmt->execute([$name, $email, $survey_id]);
$submission_id = $pdo->lastInsertId();
// Insert into survey_answers
$answer_stmt = $pdo->prepare("INSERT INTO survey_answers (submission_id, question_id, answer_text) VALUES (?, ?, ?)");
foreach ($answers as $question_id => $answer_text) {
if (is_array($answer_text)) {
$answer_text = implode(', ', $answer_text);
}
if (!empty($answer_text)) {
$answer_stmt->execute([$submission_id, $question_id, $answer_text]);
}
}
$pdo->commit();
$response['success'] = true;
unset($response['error']);
// Send email notification
require_once __DIR__ . '/mail/MailService.php';
$survey_stmt = $pdo->prepare("SELECT title FROM surveys WHERE id = ?");
$survey_stmt->execute([$survey_id]);
$survey_title = $survey_stmt->fetchColumn();
$subject = "New Submission for Survey: " . $survey_title;
$submission_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . '/view_submission.php?id=' . $submission_id;
$safe_name = htmlspecialchars($name);
$safe_email = htmlspecialchars($email);
$htmlBody = "<p>A new submission has been received for the survey: <strong>{$survey_title}</strong></p>\n <p><strong>Submitter:</strong> {$safe_name} ({$safe_email})</p>\n <p><a href=\"{$submission_url}\">Click here to view the full submission.</a></p>";
$textBody = "A new submission has been received for the survey: {$survey_title}. Submitter: {$name} ({$email}). View the submission here: {$submission_url}";
MailService::sendMail(null, $subject, $htmlBody, $textBody);
} catch (PDOException $e) {
$pdo->rollBack();
error_log($e->getMessage());
$response['error'] = 'Database error. Could not submit feedback.';
}
}
}
echo json_encode($response);

136
survey.php Normal file
View File

@ -0,0 +1,136 @@
<?php
session_start();
require_once __DIR__ . '/db/config.php';
// Get survey ID from the URL
if (!isset($_GET['id'])) {
header("Location: index.php");
exit;
}
$survey_id = $_GET['id'];
// --- Email Logic ---
$email = '';
$isEmailDisabled = false;
// 1. Check for email in URL parameter
if (isset($_GET['email']) && !empty($_GET['email'])) {
$email = $_GET['email'];
// Store it in a cookie for 1 year
setcookie('user_email', $email, time() + (86400 * 365), "/"); // 86400 = 1 day
$isEmailDisabled = true;
}
// 2. If not in URL, check for email in cookie
else if (isset($_COOKIE['user_email']) && !empty($_COOKIE['user_email'])) {
$email = $_COOKIE['user_email'];
$isEmailDisabled = true;
}
// 3. Otherwise, the field will be empty and enabled.
// Fetch survey details
$survey_stmt = db()->prepare("SELECT * FROM surveys WHERE id = ?");
$survey_stmt->execute([$survey_id]);
$survey = $survey_stmt->fetch();
// If survey doesn't exist, show an error or redirect
if (!$survey) {
$pageTitle = "Survey Not Found";
require_once 'templates/header.php';
echo "<main><section class='survey-section'><div class='container'><h2>Survey not found.</h2></div></section></main>";
require_once 'templates/footer.php';
exit;
}
// Fetch questions for the survey
$questions_stmt = db()->prepare("SELECT * FROM survey_questions WHERE survey_id = ? ORDER BY created_at ASC");
$questions_stmt->execute([$survey_id]);
$questions = $questions_stmt->fetchAll();
$pageTitle = htmlspecialchars($survey['title']);
$description = htmlspecialchars($survey['description']);
require_once 'templates/header.php';
?>
<main>
<section class="hero">
<div class="container">
<h1><?= htmlspecialchars($survey['title']) ?></h1>
<p><?= htmlspecialchars($survey['description']) ?></p>
</div>
</section>
<section class="survey-section">
<div class="container">
<div id="success-message" class="hidden" style="text-align: center;">
<h2>Thank you!</h2>
<p>Your feedback has been submitted successfully. We appreciate you taking the time to help us improve.</p>
<p>We'll try our best to make Flatlogic better, and as a courtesy, we give you this promocode: <strong>ONLY_TRUE_FEEDBACK_OCT_2025</strong></p>
<p>It gives you <strong>free $10 credits</strong>. To use it, go to Billing -> Add Credits -> Select the $10 product and apply the promocode as shown below:</p>
<img src="assets/pasted-20251008-162409-6b449f77.png" alt="How to apply promocode" style="max-width: 100%; height: auto; border: 1px solid #ddd; margin-top: 15px;">
</div>
<div class="form-container">
<form id="survey-form">
<input type="hidden" name="survey_id" value="<?= $survey_id ?>">
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="step">
<div class="form-group">
<label for="name" class="form-label">Name</label>
<input type="text" id="name" name="name" class="form-control" value="<?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>" required>
</div>
<div class="form-group">
<label for="email" class="form-label">Email Address</label>
<input type="email" id="email" name="email" class="form-control" value="<?= htmlspecialchars($email) ?>" <?= $isEmailDisabled ? 'disabled' : '' ?> required>
</div>
</div>
<?php foreach ($questions as $question): ?>
<div class="step">
<div class="form-group">
<label class="form-label"><?= htmlspecialchars($question['question_text']) ?></label>
<?php if ($question['question_type'] === 'text'): ?>
<input type="text" name="answers[<?= $question['id'] ?>]" class="form-control" required>
<?php elseif ($question['question_type'] === 'textarea'): ?>
<textarea name="answers[<?= $question['id'] ?>]" rows="5" class="form-control" required></textarea>
<?php elseif ($question['question_type'] === 'rating'): ?>
<div class="rating">
<?php for ($i = 1; $i <= 5; $i++): ?>
<input type="radio" id="rating-<?= $question['id'] ?>-<?= $i ?>" name="answers[<?= $question['id'] ?>]" value="<?= $i ?>" required>
<label for="rating-<?= $question['id'] ?>-<?= $i ?>"><?= $i ?></label>
<?php endfor; ?>
</div>
<?php elseif ($question['question_type'] === 'multiple-choice'): ?>
<?php $options = explode(',', $question['options']); ?>
<div class="checkbox-group">
<?php foreach ($options as $option): ?>
<?php $option = trim($option); ?>
<div class="checkbox-item">
<input type="checkbox" id="option-<?= htmlspecialchars($option) ?>-<?= $question['id'] ?>" name="answers[<?= $question['id'] ?>][]" value="<?= htmlspecialchars($option) ?>">
<label for="option-<?= htmlspecialchars($option) ?>-<?= $question['id'] ?>"><?= htmlspecialchars($option) ?></label>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<div class="d-flex justify-content-between">
<button type="button" id="prevBtn" class="btn btn-secondary">Previous</button>
<button type="button" id="nextBtn" class="btn btn-primary">Next</button>
<button type="submit" id="submitBtn" class="btn btn-primary">Submit Feedback</button>
</div>
</form>
</div>
</div>
</section>
</main>
<?php
require_once 'templates/footer.php';
?>

123
surveys.php Normal file
View File

@ -0,0 +1,123 @@
<?php
session_start();
require_once 'db/config.php';
// Ensure admin is logged in
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
header('Location: login.php');
exit;
}
// Handle form submission for new survey
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['title'])) {
$title = trim($_POST['title']);
$description = trim($_POST['description']);
if (!empty($title)) {
$stmt = db()->prepare("INSERT INTO surveys (title, description) VALUES (?, ?)");
$stmt->execute([$title, $description]);
header("Location: surveys.php"); // Redirect to avoid form resubmission
exit;
}
}
// Pagination
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$records_per_page = 10;
$offset = ($page - 1) * $records_per_page;
// Get total number of surveys
$total_stmt = db()->query("SELECT COUNT(*) FROM surveys");
$total_records = $total_stmt->fetchColumn();
$total_pages = ceil($total_records / $records_per_page);
// Fetch surveys for the current page
$surveys_stmt = db()->prepare("SELECT * FROM surveys ORDER BY created_at DESC LIMIT :limit OFFSET :offset");
$surveys_stmt->bindValue(':limit', $records_per_page, PDO::PARAM_INT);
$surveys_stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$surveys_stmt->execute();
$surveys = $surveys_stmt->fetchAll();
$pageTitle = "Manage Surveys";
require_once 'templates/header.php';
?>
<main>
<section class="survey-section">
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Manage Surveys</h1>
<a href="admin.php" class="btn btn-secondary">Back to Submissions</a>
</div>
<!-- Create New Survey Form -->
<div class="card mb-5">
<div class="card-body">
<h2 class="card-title">Create New Survey</h2>
<form method="POST">
<div class="form-group">
<label for="title" class="form-label">Survey Title</label>
<input type="text" id="title" name="title" class="form-control" required>
</div>
<div class="form-group">
<label for="description" class="form-label">Description (Optional)</label>
<textarea id="description" name="description" rows="3" class="form-control"></textarea>
</div>
<button type="submit" class="btn btn-primary">Create Survey</button>
</form>
</div>
</div>
<!-- List of Existing Surveys -->
<h2>Existing Surveys</h2>
<div class="row">
<?php if (empty($surveys)):
?>
<div class="col">
<p>No surveys created yet.</p>
</div>
<?php else:
?>
<?php foreach ($surveys as $survey):
?>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title"><?= htmlspecialchars($survey['title']) ?></h5>
<p class="card-text"><?= htmlspecialchars($survey['description']) ?></p>
</div>
<div class="card-footer">
<a href="manage_questions.php?survey_id=<?= $survey['id'] ?>" class="btn btn-primary">Manage Questions</a>
<a href="edit_survey.php?id=<?= $survey['id'] ?>" class="btn btn-secondary">Edit</a>
<small class="text-muted float-right">Created: <?= date('M j, Y', strtotime($survey['created_at'])) ?></small>
</div>
</div>
</div>
<?php endforeach;
?>
<?php endif;
?>
</div>
<!-- Pagination Links -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<?php if ($page > 1): ?>
<li class="page-item"><a class="page-link" href="?page=<?= $page - 1 ?>">Previous</a></li>
<?php endif; ?>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?= ($i == $page) ? 'active' : '' ?>"><a class="page-link" href="?page=<?= $i ?>"><?= $i ?></a></li>
<?php endfor; ?>
<?php if ($page < $total_pages): ?>
<li class="page-item"><a class="page-link" href="?page=<?= $page + 1 ?>">Next</a></li>
<?php endif; ?>
</ul>
</nav>
</div>
</section>
</main>
<?php
require_once 'templates/footer.php';
?>

11
templates/footer.php Normal file
View File

@ -0,0 +1,11 @@
<footer class="footer mt-auto py-3 bg-light">
<div class="container text-center">
<span class="text-muted">&copy; <?= date('Y') ?> Your Real Feedback by <a href="https://flatlogic.com/">Flatlogic</a> | <a href="https://flatlogic.com/privacy">Privacy Policy</a> | <a href="https://flatlogic.com/terms">Terms</a></span>
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

59
templates/header.php Normal file
View File

@ -0,0 +1,59 @@
<?php
$pageTitle = isset($pageTitle) ? $pageTitle : 'SurveySphere';
$description = isset($description) ? $description : 'Create and manage surveys with ease.';
$canonical_url = "https://" . $_SERVER['HTTP_HOST'] . strtok($_SERVER["REQUEST_URI"], '?');
// Determine if the current page is the survey page
$isSurveyPage = basename($_SERVER['PHP_SELF']) == 'survey.php';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $pageTitle ?></title>
<meta property="og:image" content="https://project-screens.s3.amazonaws.com/screenshots/34760/app-hero-20251007-160150.png" />
<meta property="twitter:image" content="https://project-screens.s3.amazonaws.com/screenshots/34760/app-hero-20251007-160150.png" />
<meta name="description" content="<?= $description ?>">
<link rel="canonical" href="<?= $canonical_url ?>" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<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;600;700&display=swap" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<div class="d-flex align-items-center">
<a class="navbar-brand" href="https://flatlogic.com/">
<img src="https://flatlogic.com/assets/icons/footer_logo-102b5debccc6a5a944601a0bc451c7b8da6e282147b7906a42818dda605383ff.svg" alt="Flatlogic" width="120">
</a>
<span class="navbar-text ml-2">Surveys</span>
</div>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto">
<?php if (!$isSurveyPage): ?>
<li class="nav-item">
<a class="nav-link" href="index.php">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="surveys.php">Surveys</a>
</li>
<?php endif; ?>
<?php if (isset($_SESSION['user_id'])): ?>
<li class="nav-item">
<a class="nav-link" href="dashboard.php">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">Logout</a>
</li>
<?php endif; ?>
</ul>
</div>
</div>
</nav>

86
view_submission.php Normal file
View File

@ -0,0 +1,86 @@
<?php
session_start();
require_once 'db/config.php';
// Ensure admin is logged in
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
header('Location: login.php');
exit;
}
// Check for Submission ID
if (!isset($_GET['id'])) {
header('Location: admin.php');
exit;
}
$submission_id = $_GET['id'];
// Fetch submission details
$submission_stmt = db()->prepare("SELECT s.name, s.email, s.created_at, sv.title as survey_title FROM feedback_submissions s JOIN surveys sv ON s.survey_id = sv.id WHERE s.id = ?");
$submission_stmt->execute([$submission_id]);
$submission = $submission_stmt->fetch();
if (!$submission) {
header('Location: admin.php');
exit;
}
// Fetch answers for the submission
$answers_stmt = db()->prepare("SELECT q.question_text, a.answer_text FROM survey_answers a JOIN survey_questions q ON a.question_id = q.id WHERE a.submission_id = ? ORDER BY q.created_at ASC");
$answers_stmt->execute([$submission_id]);
$answers = $answers_stmt->fetchAll();
$pageTitle = "View Submission";
require_once 'templates/header.php';
?>
<main>
<section class="survey-section">
<div class="container">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Submission Details</h1>
<a href="admin.php" class="btn btn-secondary">Back to All Submissions</a>
</div>
<div class="card">
<div class="card-header">
<h3 class="mb-0">Submission for "<?= htmlspecialchars($submission['survey_title']) ?>"</h3>
</div>
<div class="card-body">
<div class="mb-4">
<h5>Submission Details</h5>
<p class="mb-1"><strong>Submitter:</strong> <?= htmlspecialchars($submission['name']) ?></p>
<p class="mb-1"><strong>Email:</strong> <?= htmlspecialchars($submission['email']) ?></p>
<p class="mb-0"><strong>Submitted At:</strong> <?= $submission['created_at'] ?></p>
</div>
<hr>
<div>
<h5>Answers</h5>
<table class="table table-striped table-bordered">
<thead class="thead-light">
<tr>
<th style="width: 30%;">Question</th>
<th>Answer</th>
</tr>
</thead>
<tbody>
<?php foreach ($answers as $answer):
?>
<tr>
<td><?= htmlspecialchars($answer['question_text']) ?></td>
<td><?= nl2br(htmlspecialchars($answer['answer_text'])) ?></td>
</tr>
<?php endforeach;
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
</main>
<?php
require_once 'templates/footer.php';
?>