diff --git a/.env.example b/.env.example new file mode 100755 index 0000000..fc444bc --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# Local environment variables for the WPoets full-stack test +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=wpoets_test +DB_USERNAME=root +DB_PASSWORD= diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..3323b34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +.DS_Store diff --git a/Answers to technical questions.md b/Answers to technical questions.md new file mode 100755 index 0000000..6339373 --- /dev/null +++ b/Answers to technical questions.md @@ -0,0 +1,64 @@ +# Answers to technical questions + +## How long did you spend on the coding test? +I spent about 5 hours building the app, including the PHP CRUD admin panel, frontend slider layout, and database schema. + +If I had more time, I would add: +- authentication and authorization for the admin panel +- better CRUD UX with a success message that disappears automatically +- edge case handling for many slides, especially to prevent the slider dots from distorting +- a cleaner admin UI +- real image upload support instead of fixed image paths +- improved keyboard navigation and screen reader support +- animation and swipe support for the slider on mobile devices +- I have used constants and constant arrays in a few places for simplicity. In a larger production application, this logic would likely be abstracted into more modular and configurable structures. However, given the scope of the assignment, I chose a simpler implementation approach to avoid adding unnecessary complexity and development overhead without much practical benefit. + +## How would you track down a performance issue in production? +I would first identify which page or user flow is slow. Then I would check logs, review slow database queries, and inspect the page for large files or extra requests. I would confirm that the SQL queries use the right indexes and look for frontend issues like large images or unused scripts. + +If needed, I would add logging or profiling around database calls to see whether the issue is backend, frontend, or network related. After that, I would use caching, pagination, or asset optimization depending on the root cause. + +## Please describe yourself using JSON. +{ + "name": "Shubham Mishra", + "designation": "Full Stack Software Engineer", + "experience": "3+ years", + "specialization": "Backend-heavy full stack development", + "skills": [ + "PHP", + "Laravel", + "MySQL", + "PostgreSQL", + "Redis", + "Vue.js", + "React.js", + "JavaScript", + "HTML5", + "CSS3", + "Bootstrap", + "jQuery", + "REST APIs", + "System Design" + ], + "strengths": [ + "scalable backend architecture", + "clean and maintainable code", + "performance optimization", + "database design", + "frontend integration", + "team collaboration" + ], + "experienceHighlights": { + "teamLeadership": "Mentored and guided a team of developers", + "productOwnership": "Worked extensively on long-term product development", + "fullStackDevelopment": true + }, + "approach": "Build practical, scalable, and maintainable applications with strong focus on backend architecture and user experience.", + "values": { + "clarity": true, + "simplicity": true, + "performance": true, + "ownership": true, + "teamwork": true + } +} \ No newline at end of file diff --git a/admin.php b/admin.php new file mode 100755 index 0000000..1bc6b6e --- /dev/null +++ b/admin.php @@ -0,0 +1,185 @@ +getMessage(); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && $repository !== null) { + $action = $_POST['action'] ?? ''; + $slideData = [ + 'id' => $_POST['id'] ?? null, + 'topic' => $_POST['topic'] ?? '', + 'title' => $_POST['title'] ?? '', + 'image_path' => $_POST['image_path'] ?? '', + 'sort_order' => $_POST['sort_order'] ?? 0, + ]; + + try { + if ($action === 'save') { + $repository->saveSlide($slideData); + header('Location: admin.php?message=' . urlencode('Slide saved successfully.')); + exit; + } + + if ($action === 'delete' && !empty($slideData['id'])) { + $repository->removeSlide((int) $slideData['id']); + header('Location: admin.php?message=' . urlencode('Slide deleted successfully.')); + exit; + } + } catch (Throwable $exception) { + $errorMessage = 'A database error occurred: ' . $exception->getMessage(); + } +} + +if ($repository !== null) { + $allSlides = $repository->fetchAllSlides(); + $editingSlide = null; + if (!empty($_GET['edit'])) { + $editingSlide = $repository->fetchSlideById((int) $_GET['edit']); + } +} else { + $allSlides = []; + $editingSlide = null; +} +?> + + + + + + + WPoets Admin CRUD + + + + +
+
+

Admin Panel

+ View public slider +
+
+
+ +
+ + +
A database error occurred:
+ + +
+
+
+
Create or update a slide
+
+
+ + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + Reset form +
+
+
+
+
+ +
+
+
Existing slides
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
TopicTitleOrderActions
+
+ Edit +
+ + + +
+
+
No slides created yet.
+
+
+
+
+
+
+ + diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100755 index 0000000..53d707c --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,512 @@ +/* ============================================================ + DelphianLogic Slider – Pixel-Perfect Rewrite + ============================================================ */ + +/* ---------------------------------------------------------- + Page background + ---------------------------------------------------------- */ +body { + background-color: #1a2a4a; +} + +/* ---------------------------------------------------------- + Outer slider container – no gaps, no radius, fixed height + ---------------------------------------------------------- */ +.slider-outer { + display: flex; + justify-content: center; + align-items: stretch; + width: 100%; + max-width: 1180px; + margin: 0 auto; + height: 440px; /* wide & short */ + overflow: hidden; + border-radius: 0; +} + +/* ---------------------------------------------------------- + COL 1 – Topics panel + ---------------------------------------------------------- */ + +.topics-panel { + width: 27%; + flex-shrink: 0; + background: #efefef; + display: flex; + align-items: center; + justify-content: center; + padding: 34px 18px; + position: relative; + z-index: 2; +} + +/* nav wrapper */ +.topics-panel .nav.flex-column { + width: 100%; + gap: 22px; + padding: 0; + margin: 0; +} + +/* ---------------------------------------------------------- + EACH CARD + ---------------------------------------------------------- */ + +.topic-tab { + position: relative; + + display: flex; + align-items: center; + gap: 22px; + width: 100%; + min-height: 102px; + padding: 0 28px; + background: #f8f8f8; + border: 1px solid #e7e7e7; + border-radius: 4px !important; + text-decoration: none; + color: #2d3748; + transition: all 0.18s ease; + margin: 0 !important; +} + +/* remove previous divider logic */ +.topic-tab:last-child { + border-bottom: 1px solid #e7e7e7; +} + +/* ---------------------------------------------------------- + HOVER + ---------------------------------------------------------- */ + +.topic-tab:hover:not(.active) { + background: #ffffff; + color: #111827; +} + +/* ---------------------------------------------------------- + ACTIVE CARD + ---------------------------------------------------------- */ + +.topic-tab.active { + background: #ffffff; + color: #111827; + box-shadow: + 0 6px 18px rgba(0,0,0,0.12), + 0 2px 6px rgba(0,0,0,0.08); + z-index: 5; +} + +/* ---------------------------------------------------------- + ARROW – desktop only (right-pointing into slider panel) + ---------------------------------------------------------- */ + +.topic-tab.active::after { + content: ''; + position: absolute; + right: -22px; + top: 50%; + transform: translateY(-50%); + width: 0; + height: 0; + border-top: 14px solid transparent; + border-bottom: 14px solid transparent; + + /* MIRRORED TRIANGLE */ + border-right: 14px solid #64b4c8; + z-index: 10; +} + +/* ---------------------------------------------------------- + ICON + ---------------------------------------------------------- */ + +.topic-icon { + width: 42px; + height: 42px; + flex-shrink: 0; + object-fit: contain; +} + +/* ---------------------------------------------------------- + LABEL + ---------------------------------------------------------- */ + +.topic-tab span { + font-size: 1rem; + font-weight: 600; + line-height: 1.2; + color: inherit; +} + +/* Hide toggle-btn on desktop */ +.toggle-btn { + display: none; +} + +/* ---------------------------------------------------------- + COL 2 – Slider panel (~37 %) + ---------------------------------------------------------- */ +.slider-panel { + width: 37%; + flex-shrink: 0; + background: #64b4c8; + display: flex; + flex-direction: column; + justify-content: center; + padding: 2.5rem 2.5rem 1.75rem 2.5rem; + position: relative; + z-index: 1; +} + +/* hide the original Bootstrap header row (prev/next buttons, title) */ +.slider-header { + display: none !important; +} + +.slider-items { + display: flex; + flex-direction: column; + flex: 1; + justify-content: center; +} + +/* individual slide cards */ +.slider-card { + display: none; + flex-direction: column; + gap: 0.85rem; + padding: 0; + border-radius: 0 !important; + background: transparent; + box-shadow: none; + min-height: unset; + color: #fff; + align-items: center; + justify-content: center; + text-align: center; +} + +.slider-card.active { + display: flex; +} + +/* Badge above the title – SQUARE corners */ +.slider-badge { + line-height: 16px; + display: inline-block; + width: fit-content; + background: #3686915e; + font-size: 0.7rem; + font-weight: 400; + letter-spacing: 0.03em; + text-transform: uppercase; + border-radius: 0; + padding: 0.2rem 0.5rem; + color: rgba(255, 255, 255, 0.92); +} + +.slider-card h3 { + font-size: 1.65rem; + font-weight: 500; + line-height: 1.2; + color: #fff; + margin: 0; + width: 345px; +} + +.slider-card p { + font-size: 0.92rem; + color: rgba(255, 255, 255, 0.9); + margin: 0; + line-height: 1.55; +} + +/* hide the "Slide X of Y" text */ +.slider-card .slide-counter { + display: none; +} + +/* Learn More link */ +.learn-more-link { + display: inline-flex; + align-items: center; + gap: 0.4rem; + font-size: 0.9rem; + font-weight: 700; + color: #fff; + text-decoration: none; + margin-top: 0.25rem; +} + +.learn-more-link:hover { opacity: 0.8; } + +/* Admin panel action buttons */ +.action-cell { + white-space: nowrap; +} + +.admin-action-buttons { + align-items: center; +} + +.admin-action-buttons .btn { + white-space: nowrap; +} + +/* Dot indicators at the bottom */ +.slider-dots { + display: flex; + justify-content: center; + gap: 0.5rem; + margin-top: 1.75rem; +} + +.slider-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.4); + border: none; + padding: 0; + cursor: pointer; + transition: background 0.2s; +} + +.slider-dot.active { + background: #fff; +} + +/* hide old prev/next */ +#previous-slide, +#next-slide { + display: none !important; +} + +/* ---------------------------------------------------------- + COL 3 – Image panel (~37 %) + ---------------------------------------------------------- */ +.image-panel { + width: 37%; + flex: 0 0 37%; + min-width: 0; + padding: 0; +} + +.image-frame { + width: 100%; + aspect-ratio: 1 / 1; + border-radius: 0 !important; + overflow: hidden; + background: #1a2744; + display: flex; + align-items: center; + justify-content: center; + min-height: unset; +} + +.image-frame img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.placeholder { + padding: 2rem; + text-align: center; + color: #9ca3af; +} + +/* ---------------------------------------------------------- + Page hero header + ---------------------------------------------------------- */ +.page-hero { + text-align: center; + padding: 2.5rem 0 1.75rem; + color: #fff; +} + +.page-hero h1 { + font-size: 2.1rem; + font-weight: 700; + color: #fff; + margin-bottom: 0.4rem; +} + +.page-hero p { + color: rgba(255, 255, 255, 0.7); + font-size: 1rem; + margin: 0; +} + +/* ---------------------------------------------------------- + Responsive – TABLET 768 px – 991 px + Simple stacked layout, no accordion + ---------------------------------------------------------- */ +@media (min-width: 768px) and (max-width: 991px) { + + .slider-outer { + flex-direction: column; + height: auto; + } + + .topics-panel, + .slider-panel, + .image-panel { + width: 100%; + } + + .topics-panel { + justify-content: flex-start; + } + + .topic-tab { + height: auto; + padding: 1rem 1.5rem; + } + + .topic-tab.active::after { + display: none; + } + + .slider-card h3 { + font-size: 1.3rem; + width: 100%; + } + + .image-frame { + min-height: 250px; + } +} + +/* ---------------------------------------------------------- + Responsive – MOBILE ≤ 767 px + Accordion layout: each topic tab expands inline + ---------------------------------------------------------- */ +@media (max-width: 767px) { + + /* Outer container: vertical, full-width, no fixed height */ + .slider-outer { + flex-direction: column; + height: auto; + max-width: 100%; + overflow: visible; + } + + /* Hide the desktop slider-panel and image-panel columns — + their content is cloned into accordion bodies by JS */ + .slider-panel, + .image-panel { + display: none !important; + } + + /* Topics panel: full-width accordion shell, transparent bg */ + .topics-panel { + width: 100%; + background: transparent; + display: flex; + flex-direction: column; + align-items: stretch; + padding: 10px; + justify-content: flex-start; + } + + .topics-panel .nav.flex-column { + gap: 10px; + width: 100%; + } + + /* Each topic tab card */ + .topic-tab { + width: 100%; + min-height: 80px; + height: auto; + padding: 0 20px; + background: #ffffff; + border-radius: 6px !important; + gap: 16px; + } + + /* Hide desktop right-pointing arrow on mobile */ + .topic-tab.active::after { + display: none !important; + } + + /* Show toggle-btn on mobile */ + .toggle-btn { + display: flex; + margin-left: auto; + width: 0px; + height: 0px; + border-radius: 50%; + background: #ffffff; + align-items: center; + justify-content: center; + flex-shrink: 0; + padding: 0; + } + + .toggle-btn .toggle-icon { + width: 20px; + height: 20px; + display: block; + } + + /* ── Accordion body ─────────────────────────────────────── + Injected by JS right after each .topic-tab. + Full bleed teal block with upward triangle at the top. + --------------------------------------------------------- */ + .accordion-slider-body { + position: relative; + width: 100%; + background: #64b4c8; + padding: 30px 20px 28px; + overflow: visible; + /* Margin so the triangle visually connects to the tab above */ + margin-top: 8px; + } + + /* Upward-pointing triangle at the top of the accordion body + Points up into the active topic-tab card above it */ + .accordion-slider-body::before { + content: ''; + position: absolute; + top: -12px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 16px solid transparent; + border-right: 16px solid transparent; + border-bottom: 16px solid #64b4c8; + } + + /* Re-show the cloned slider panel inside the accordion body */ + .accordion-slider-body .slider-panel { + display: flex !important; + background: transparent; + width: 100%; + padding: 0; + position: static; + flex-direction: column; + justify-content: center; + } + + /* Slide cards inside accordion */ + .accordion-slider-body .slider-card { + color: #fff; + text-align: center; + align-items: center; + } + + .accordion-slider-body .slider-card h3 { + font-size: 1.35rem; + width: 100%; + } + + /* Dots inside accordion */ + .accordion-slider-body .slider-dots { + margin-top: 1.5rem; + } +} \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100755 index 0000000..185b579 --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,218 @@ +$(document).ready(function () { + /* ───────────────────────────────────────── + Shared state + ───────────────────────────────────────── */ + const cards = $('.slider-card'); + const imageEl = $('#active-slide-image'); + const dotsContainer = $('#slider-dots'); + const sliderPanel = $('.slider-panel'); // original desktop column + + let currentIndex = parseInt(cards.filter('.active').data('index'), 10); + if (Number.isNaN(currentIndex)) currentIndex = 0; + + const totalSlides = cards.length; + + /* ───────────────────────────────────────── + Dot navigation (built once; shared) + ───────────────────────────────────────── */ + function buildDots() { + dotsContainer.empty(); + for (let i = 0; i < totalSlides; i++) { + $(' + + +
+ + $slide): ?> +
+ + + Digital Learning Infrastructure + +

+ + Learn More  → + + +

+ Slide of +

+
+ + +
+ + + + + + + +
+
+ + <?php echo htmlspecialchars($activeSlide['title']); ?> + +
No slides in this topic yet.
+ +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/setup.md b/setup.md new file mode 100644 index 0000000..5ac0f56 --- /dev/null +++ b/setup.md @@ -0,0 +1,117 @@ +# WPoets Full Stack Test + +## Project summary +This repository contains a small PHP frontend and admin application built to demonstrate: +- a PHP + MySQL connection using PDO +- CRUD operations for managing slide content +- a public UI with topic tabs, slide content, and a synced image panel +- a simple admin panel for creating, updating, and deleting slides +- `.env` configuration support for database credentials + +## What is included +- `index.php` — public slider page showing a topic tab list, slide cards, and a connected image +- `admin.php` — slide management page with create, edit, and delete operations +- `src/Database.php` — database connection class using PDO +- `src/SlideRepository.php` — repository layer for slide CRUD operations +- `config.php` — environment loader and database configuration +- `.env.example` — example database environment values +- `.env` — local environment values for this project +- `.gitignore` — excludes `.env` from version control +- `setup.sql` — MySQL schema and seeded slide data +- `assets/` — frontend CSS and JavaScript used by `index.php` and `admin.php` +- `Answers to technical questions.md` — technical answers for the coding test + +## Requirements +- PHP 8 or newer with PDO enabled +- MySQL server +- A local browser + +## Setup instructions +1. Copy `.env.example` to `.env` if you want custom local values. + - The repository already includes `.env` for convenience, but do not commit your own credentials. +2. Create the database and initial table data. + - If you have direct MySQL access, run: + ```bash + mysql -u root -p < setup.sql + ``` + - If you are using Apache with phpMyAdmin or another GUI, import `setup.sql` there instead. +3. Confirm `.env` matches your MySQL settings: + ```dotenv + DB_CONNECTION=mysql + DB_HOST=127.0.0.1 + DB_PORT=3306 + DB_DATABASE=wpoets_test + DB_USERNAME=root + DB_PASSWORD= + ``` + +## Running the project +### Option 1: Apache +1. Place the project inside your Apache web root, for example: + - macOS: `/Library/WebServer/Documents/full-stack-test` + - MAMP: `/Applications/MAMP/htdocs/full-stack-test` +2. Make sure Apache is running and PHP is enabled. +3. Open the project in your browser at the correct Apache URL, for example: + - `http://localhost/full-stack-test/index.php` + - `http://localhost/full-stack-test/admin.php` + +### Option 2: Built-in PHP server +From the repository root, run: +```bash +php -S localhost:8000 +``` +Then open: +- `http://localhost:8000/index.php` +- `http://localhost:8000/admin.php` + +### How `.env` is used +- `config.php` reads `.env` and loads the database credentials into the app +- `src/Database.php` uses those values to create the PDO connection +- If Apache is configured and PHP works there, this project uses the same MySQL server as long as `.env` is correct + +### Note on `php artisan serve` +- `php artisan serve` is a Laravel command +- It is not required for this project +- For this PHP app, use Apache or PHP's built-in server instead + +## Why the MySQL setup is included +The MySQL setup is included so the app has a real backend data source: +- it shows how PHP connects to MySQL using PDO +- it supports persistent slide content instead of hard-coded data +- it demonstrates a basic repository pattern for database access + +This is not just UI work: it shows the app can read and write slide records from a database. + +## How to verify MySQL connection +1. Start the PHP server. +2. Open `index.php` in the browser. +3. If the slider loads data and displays slides, the MySQL connection is working. + +Additional verification: +- Open `admin.php` and confirm existing slides appear. +- Create or edit a slide and save it. +- If the change persists and appears in the public view, the database connection is working. + +If you see an error like `Table 'wpoets_test.slides' doesn't exist`, run the database initialization script again: +```bash +mysql -u root -p < setup.sql +``` +If the database still does not exist, create it first or ensure `.env` contains the correct `DB_DATABASE` value. + +If the database cannot connect, the app will fail when creating the PDO connection inside `src/Database.php`. + +## Notes +- `.env` is excluded by `.gitignore` so local credentials are not pushed +- `config.php` reads `.env` and uses those values in `src/Database.php` +- `setup.sql` is included to make it easy to initialize the database for this test + +## Which files to read +- `README.md` contains setup, run instructions, and project details +- `Answers to technical questions.md` contains the coding test answers + +## Technical questions +Answer these in `Answers to technical questions.md`: +- How long did you spend on the coding test? What would you add if you had more time? +- How would you track down a performance issue in production? +- Please describe yourself using JSON. + diff --git a/setup.sql b/setup.sql new file mode 100755 index 0000000..6398475 --- /dev/null +++ b/setup.sql @@ -0,0 +1,21 @@ +CREATE DATABASE IF NOT EXISTS wpoets_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE wpoets_test; + +CREATE TABLE IF NOT EXISTS slides ( + id INT AUTO_INCREMENT PRIMARY KEY, + topic VARCHAR(100) NOT NULL, + title VARCHAR(255) NOT NULL, + image_path VARCHAR(255) NOT NULL, + sort_order INT NOT NULL DEFAULT 0 +); + +INSERT INTO slides (topic, title, image_path, sort_order) VALUES +('Learning', 'Usability enhancement and Training for Transaction Portal for Customers', 'files/images/DL-Learning-1.jpg', 1), +('Learning', 'Research with empathy', 'files/images/DL-learning.svg', 2), +('Learning', 'Share knowledge', 'files/images/DL-Technology.jpg', 3), +('Technology', 'Build resilient systems', 'files/images/DL-Technology.jpg', 1), +('Technology', 'Use modern tools', 'files/images/DL-learning.svg', 2), +('Technology', 'Automate releases', 'files/images/DL-communication.svg', 3), +('Communication', 'Speak clearly', 'files/images/DL-Communication.jpg', 1), +('Communication', 'Visual storytelling', 'files/images/DL-communication.svg', 2), +('Communication', 'Audience-first work', 'files/images/DL-Technology.jpg', 3); \ No newline at end of file diff --git a/src/Database.php b/src/Database.php new file mode 100755 index 0000000..278302d --- /dev/null +++ b/src/Database.php @@ -0,0 +1,36 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + ]); + } catch (PDOException $error) { + throw new RuntimeException('Could not connect to the database: ' . $error->getMessage()); + } + + return $pdo; + } +} diff --git a/src/SlideRepository.php b/src/SlideRepository.php new file mode 100755 index 0000000..3e41a8a --- /dev/null +++ b/src/SlideRepository.php @@ -0,0 +1,84 @@ +connection = Database::getConnection(); + } + + public function fetchTopics(): array + { + $query = 'SELECT DISTINCT topic FROM slides ORDER BY topic'; + return $this->connection->query($query)->fetchAll(); + } + + public function fetchSlidesByTopic(string $topic): array + { + $sql = 'SELECT * FROM slides WHERE topic = :topic ORDER BY sort_order, id'; + $statement = $this->connection->prepare($sql); + $statement->execute(['topic' => $topic]); + return $statement->fetchAll(); + } + + public function fetchSlideById(int $slideId): ?array + { + $sql = 'SELECT * FROM slides WHERE id = :id'; + $statement = $this->connection->prepare($sql); + $statement->execute(['id' => $slideId]); + + $slide = $statement->fetch(); + return $slide === false ? null : $slide; + } + + public function fetchAllSlides(): array + { + $sql = 'SELECT * FROM slides ORDER BY sort_order ASC, topic ASC, id ASC'; + return $this->connection->query($sql)->fetchAll(); + } + + public function saveSlide(array $slideData): bool + { + if (isset($slideData['id']) && $this->fetchSlideById((int) $slideData['id'])) { + return $this->updateSlide($slideData); + } + + return $this->createSlide($slideData); + } + + private function createSlide(array $slideData): bool + { + $sql = 'INSERT INTO slides (topic, title, image_path, sort_order) VALUES (:topic, :title, :image_path, :sort_order)'; + $statement = $this->connection->prepare($sql); + return $statement->execute([ + 'topic' => trim($slideData['topic']), + 'title' => trim($slideData['title']), + 'image_path' => trim($slideData['image_path']), + 'sort_order' => (int) $slideData['sort_order'], + ]); + } + + private function updateSlide(array $slideData): bool + { + $sql = 'UPDATE slides SET topic = :topic, title = :title, image_path = :image_path, sort_order = :sort_order WHERE id = :id'; + $statement = $this->connection->prepare($sql); + return $statement->execute([ + 'topic' => trim($slideData['topic']), + 'title' => trim($slideData['title']), + 'image_path' => trim($slideData['image_path']), + 'sort_order' => (int) $slideData['sort_order'], + 'id' => (int) $slideData['id'], + ]); + } + + public function removeSlide(int $slideId): bool + { + $sql = 'DELETE FROM slides WHERE id = :id'; + $statement = $this->connection->prepare($sql); + return $statement->execute(['id' => $slideId]); + } +}