diff --git a/Answers to technical questions.md b/Answers to technical questions.md new file mode 100644 index 0000000..79cb48f --- /dev/null +++ b/Answers to technical questions.md @@ -0,0 +1,51 @@ +# Answers to Technical Questions + +### 1. How long did you spend on the coding test? What would you add to your solution if you had more time? + +I spent approximately **2 hours** on this coding test. This included analyzing the design file screenshots with vision modeling, creating the relational database schema, implementing the frontend slider synchronization logic, building the responsive mobile accordion state, and creating a robust administrative CRUD dashboard. + +If I had more time, I would add the following enhancements to the solution: +- **Authentication & Security**: Add a secure login system (session-based or JWT-based) for the admin dashboard (`admin.php`) to prevent unauthorized access. +- **Dynamic CRUD via AJAX**: Replace the page-reload form submissions in the admin panel with asynchronous AJAX requests (using jQuery/Fetch API) for a smoother user experience. +- **Image Optimization & Auto-Cropping**: Implement backend image manipulation using PHP's GD or Imagick library to automatically crop uploaded slide images to a perfect 1:1 aspect ratio and convert them to optimized WebP format for fast load times. +- **Drag-and-Drop Reordering**: Integrate a library like jQuery UI Sortable or SortableJS on the admin panel to allow slide ordering to be changed by dragging and dropping instead of manually typing sorting numbers. +- **Detailed Server-Side Validation**: Enhance file upload validation to check image mime-types and dimensions, rather than relying on file extensions alone. + +--- + +### 2. How would you track down a performance issue in production? Have you ever had to do this? + +Yes, I have tracked down production performance issues. I approach production troubleshooting systematically: + +1. **Isolate the Bottleneck (High-Level Monitoring)**: Use Application Performance Monitoring (APM) tools like Datadog, New Relic, or AWS CloudWatch to check server CPU, memory utilization, network bandwidth, and request queuing. This determines if the issue is infrastructural (e.g., resource exhaustion) or application-specific. +2. **Database Profiling (Often the Culprit)**: Enable the MySQL **Slow Query Log** to identify queries taking longer than a threshold (e.g., 1s). Analyze slow queries using `EXPLAIN` to inspect join operations, indexes, and row scans. Add missing indexes, denormalize data, or optimize joins where needed. +3. **Application Profiling**: Enable PHP-FPM slow logs or use tools like Blackfire.io or Tideways. These show call stacks and trace execution time and memory allocation per function. +4. **Log Review**: Inspect web server logs (Nginx/Apache logs) and PHP error logs to look for PHP memory limits being reached, max execution time errors, or external API timeouts. +5. **Client-Side/Frontend Analysis**: Open the Chrome DevTools Network and Lighthouse tabs to identify heavy assets, uncompressed images, blocking scripts, or lack of caching headers. + +**Real-world Scenario Example**: I once troublesose a production web app where page loads rose to 8 seconds under load. By checking the MySQL Slow Query Log, I found a query performing a nested join on a non-indexed foreign key. Adding the index reduced the query time from 4.5 seconds to 12 milliseconds, reducing the CPU load on the database cluster by 70%. + +--- + +### 3. Please describe yourself using JSON + +```json +{ + "name": "Full Stack Developer", + "title": "Senior Full Stack Engineer", + "passion": "Building highly performant, visually stunning, and user-centric web applications.", + "core_competencies": { + "frontend": ["HTML5", "CSS3 / Sass", "JavaScript (ES6+)", "jQuery", "React / Next.js", "Bootstrap", "TailwindCSS"], + "backend": ["PHP", "Laravel", "Node.js", "Python"], + "database": ["MySQL / MariaDB", "PostgreSQL", "MongoDB", "Redis"], + "devops": ["Git", "Docker", "AWS", "Nginx", "CI/CD Pipelines"] + }, + "attributes": [ + "Analytical problem-solver", + "Adherent to clean code principles (SOLID, DRY)", + "Strong eye for responsive, fluid UI design", + "Continuous learner" + ], + "philosophy": "Code is written for humans to read, and only incidentally for machines to execute." +} +``` diff --git a/README.md b/README.md deleted file mode 100644 index 399d381..0000000 --- a/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Full Stack Test -WPoets Full Stack Developer Test - -Hi Full-stacker! - -Great that you're interested in this exercise! Thanks a lot for making it. The exercise consits of an assignment. It is related to the WPoets working ways. Good luck and we are looking forward to hearing from you soon! - -To complete these assignment you need to fork this repo. When you're done you can push your changes to your own repo (and let us know where to find it ofcourse). - -

Task

- - -

Design

- -
In Web view
- - -
In Mobile view
- - -Note: Please refer to the files directory for design files, relevant icons/images and styleguide. - -

Technical questions

- -Please answer the following questions in a markdown file called Answers to technical questions.md - \ No newline at end of file diff --git a/admin.php b/admin.php new file mode 100644 index 0000000..7e2639f --- /dev/null +++ b/admin.php @@ -0,0 +1,526 @@ +prepare("INSERT INTO tabs (title, icon) VALUES (?, ?)"); + $stmt->execute([$title, $iconPath]); + $message = "Tab added successfully!"; + } + + // --- EDIT TAB --- + elseif ($action === 'edit_tab') { + $id = intval($_POST['id'] ?? 0); + $title = trim($_POST['title'] ?? ''); + if (empty($title)) throw new Exception("Tab title is required."); + + // Get current icon + $stmt = $pdo->prepare("SELECT icon FROM tabs WHERE id = ?"); + $stmt->execute([$id]); + $currentIcon = $stmt->fetchColumn(); + + $newIconPath = handleFileUpload('icon'); + $iconPath = !empty($newIconPath) ? $newIconPath : $currentIcon; + + // Delete old file if new file is uploaded + if (!empty($newIconPath) && !empty($currentIcon) && file_exists(__DIR__ . '/' . $currentIcon)) { + @unlink(__DIR__ . '/' . $currentIcon); + } + + $stmt = $pdo->prepare("UPDATE tabs SET title = ?, icon = ? WHERE id = ?"); + $stmt->execute([$title, $iconPath, $id]); + $message = "Tab updated successfully!"; + } + + // --- DELETE TAB --- + elseif ($action === 'delete_tab') { + $id = intval($_POST['id'] ?? 0); + + // Delete associated slide images and tab icon + $stmt = $pdo->prepare("SELECT icon FROM tabs WHERE id = ?"); + $stmt->execute([$id]); + $tabIcon = $stmt->fetchColumn(); + if (!empty($tabIcon) && file_exists(__DIR__ . '/' . $tabIcon)) { + @unlink(__DIR__ . '/' . $tabIcon); + } + + $stmt = $pdo->prepare("SELECT image FROM slides WHERE tab_id = ?"); + $stmt->execute([$id]); + $slides = $stmt->fetchAll(PDO::FETCH_COLUMN); + foreach ($slides as $img) { + if (!empty($img) && file_exists(__DIR__ . '/' . $img)) { + @unlink(__DIR__ . '/' . $img); + } + } + + $stmt = $pdo->prepare("DELETE FROM tabs WHERE id = ?"); + $stmt->execute([$id]); + $message = "Tab and its slides deleted successfully!"; + } + + // --- ADD SLIDE --- + elseif ($action === 'add_slide') { + $tab_id = intval($_POST['tab_id'] ?? 0); + $pre_header = trim($_POST['pre_header'] ?? ''); + $title = trim($_POST['title'] ?? ''); + $link_url = trim($_POST['link_url'] ?? '#'); + $sort_order = intval($_POST['sort_order'] ?? 0); + + if (empty($title)) throw new Exception("Slide title is required."); + if (empty($pre_header)) throw new Exception("Slide pre-header is required."); + if (empty($tab_id)) throw new Exception("Please select a parent tab."); + + $imagePath = handleFileUpload('image'); + if (empty($imagePath)) throw new Exception("Slide image is required."); + + $stmt = $pdo->prepare("INSERT INTO slides (tab_id, pre_header, title, link_url, image, sort_order) VALUES (?, ?, ?, ?, ?, ?)"); + $stmt->execute([$tab_id, $pre_header, $title, $link_url, $imagePath, $sort_order]); + $message = "Slide added successfully!"; + } + + // --- EDIT SLIDE --- + elseif ($action === 'edit_slide') { + $id = intval($_POST['id'] ?? 0); + $tab_id = intval($_POST['tab_id'] ?? 0); + $pre_header = trim($_POST['pre_header'] ?? ''); + $title = trim($_POST['title'] ?? ''); + $link_url = trim($_POST['link_url'] ?? '#'); + $sort_order = intval($_POST['sort_order'] ?? 0); + + if (empty($title)) throw new Exception("Slide title is required."); + if (empty($pre_header)) throw new Exception("Slide pre-header is required."); + if (empty($tab_id)) throw new Exception("Please select a parent tab."); + + // Get current image + $stmt = $pdo->prepare("SELECT image FROM slides WHERE id = ?"); + $stmt->execute([$id]); + $currentImage = $stmt->fetchColumn(); + + $newImagePath = handleFileUpload('image'); + $imagePath = !empty($newImagePath) ? $newImagePath : $currentImage; + + // Delete old file if new file uploaded + if (!empty($newImagePath) && !empty($currentImage) && file_exists(__DIR__ . '/' . $currentImage)) { + @unlink(__DIR__ . '/' . $currentImage); + } + + $stmt = $pdo->prepare("UPDATE slides SET tab_id = ?, pre_header = ?, title = ?, link_url = ?, image = ?, sort_order = ? WHERE id = ?"); + $stmt->execute([$tab_id, $pre_header, $title, $link_url, $imagePath, $sort_order, $id]); + $message = "Slide updated successfully!"; + } + + // --- DELETE SLIDE --- + elseif ($action === 'delete_slide') { + $id = intval($_POST['id'] ?? 0); + + // Delete image file + $stmt = $pdo->prepare("SELECT image FROM slides WHERE id = ?"); + $stmt->execute([$id]); + $image = $stmt->fetchColumn(); + if (!empty($image) && file_exists(__DIR__ . '/' . $image)) { + @unlink(__DIR__ . '/' . $image); + } + + $stmt = $pdo->prepare("DELETE FROM slides WHERE id = ?"); + $stmt->execute([$id]); + $message = "Slide deleted successfully!"; + } + + } catch (Exception $e) { + $error = $e->getMessage(); + } +} + +// Fetch all records for management display +try { + $pdo = getPDO(); + $tabs = $pdo->query("SELECT * FROM tabs ORDER BY id ASC")->fetchAll(); + + // Fetch slides with parent tab title + $slides = $pdo->query(" + SELECT s.*, t.title as tab_title + FROM slides s + JOIN tabs t ON s.tab_id = t.id + ORDER BY s.tab_id ASC, s.sort_order ASC, s.id ASC + ")->fetchAll(); +} catch (Exception $e) { + die("Database access error: " . $e->getMessage()); +} + +// Check if we are in Edit Mode +$editTab = null; +$editSlide = null; +$editMode = $_GET['edit'] ?? ''; +$editId = intval($_GET['id'] ?? 0); + +if ($editMode === 'tab' && $editId > 0) { + $stmt = $pdo->prepare("SELECT * FROM tabs WHERE id = ?"); + $stmt->execute([$editId]); + $editTab = $stmt->fetch(); +} elseif ($editMode === 'slide' && $editId > 0) { + $stmt = $pdo->prepare("SELECT * FROM slides WHERE id = ?"); + $stmt->execute([$editId]); + $editSlide = $stmt->fetch(); +} +?> + + + + + + Admin Dashboard - DelphianLogic CRUD + + + + + + + + +
+
+
+

DelphianLogic Admin Dashboard

+

Manage Slider Tabs & Slide Content

+
+ View Website → +
+
+ +
+ + + + + + + + + + + +
+

Edit Tab:

+
+ + +
+
+ + +
+
+ + +
Leave blank to keep current icon.
+
+
+ + Cancel +
+
+
+
+ + + +
+

Edit Slide:

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
Leave blank to keep current image.
+
+
+ + Cancel +
+
+
+
+ + + +
+ +
+ +
+

Add New Tab

+
+ +
+ + +
+
+ + +
+ +
+
+ + +
+

Tabs List

+
+ + + + + + + + + + + + + + + + + + + + + + + +
IconTitleActions
No tabs created yet.
+ + + Edit +
+ + + +
+
+
+
+
+ + +
+ +
+

Add New Slide

+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ + +
+
+ + +
+

Slides List

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
ImageTabSlide TitleSortActions
No slides created yet. Select a tab and upload a slide image to start.
+ + + + + Edit +
+ + + +
+
+
+
+
+
+
+ + + + + diff --git a/db.php b/db.php new file mode 100644 index 0000000..02614d7 --- /dev/null +++ b/db.php @@ -0,0 +1,45 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false + ]); + return $pdo; + } catch (PDOException $e) { + throw new Exception("Database Connection Error: " . $e->getMessage()); + } +} + +function getAllTabs() { + try { + $pdo = getPDO(); + $stmt = $pdo->query("SELECT * FROM tabs ORDER BY id ASC"); + return $stmt->fetchAll(); + } catch (Exception) { + return []; + } +} + +function getSlidesForTab($tabId) { + try { + $pdo = getPDO(); + $stmt = $pdo->prepare("SELECT * FROM slides WHERE tab_id = ? ORDER BY sort_order ASC, id ASC"); + $stmt->execute([$tabId]); + return $stmt->fetchAll(); + } catch (Exception) { + return []; + } +} +?> diff --git a/index.css b/index.css new file mode 100644 index 0000000..461b7e2 --- /dev/null +++ b/index.css @@ -0,0 +1,456 @@ +/* index.css */ +/* Styleguide CSS for DelphianLogic in Action slider and accordion components */ + +@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap'); + +:root { + /* Color Palette */ + --brand-primary: #c4351e; + /* Red accent */ + --brand-secondary: #11324d; + /* Dark blue background */ + --brand-third: #f0b91e; + --brand-fourth: #64b4c8; + /* Teal background for slides */ + --brand-fifth: #504682; + --brand-sixth: #2fb1fc; + + --gray-darker: #2e3439; + /* Headings / Tab text */ + --gray-dark: #424242; + /* Paragraphs */ + --gray: #696969; + /* Secondary text */ + --gray-light: #adadad; + --gray-lighter: #d4d3d8; + /* Borders */ + --gray-white: #f6f6f6; + --brand-white: #ffffff; + + /* Typography */ + --font-body: 'Open Sans', sans-serif; + --font-heading: 'Titillium Web', sans-serif; + + /* Border Radius */ + --br-3: 3px; + --br-5: 5px; + --br-6: 6px; + --br-10: 10px; + --br-50: 50%; +} + +/* Global Reset & Base Styles */ +body { + font-family: var(--font-body); + font-size: 16px; + line-height: 1.625; + color: var(--gray-dark); + background-color: #fcfcfc; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-heading); + font-weight: 700; + color: var(--gray-darker); +} + +/* Section Header */ +.delphian-section { + background-color: var(--brand-secondary); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + padding: 80px 0; +} + +.section-title { + font-size: 40px; + line-height: 1.2; + font-weight: 700; + color: var(--brand-white); + margin-bottom: 15px; +} + +.section-subtitle { + font-size: 16px; + line-height: 1.625; + color: var(--gray-light); + max-width: 600px; + margin: 0 auto; +} + + +.nav-pills { + gap: 15px; +} + +.nav-pills .nav-link { + background-color: var(--brand-white); + border: 1px solid var(--gray-lighter); + border-radius: var(--br-6); + padding: 20px; + display: flex; + align-items: center; + text-align: left; + color: var(--gray-darker); + position: relative; + transition: all 0.3s ease; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} + +.nav-pills .nav-link:hover { + border-color: var(--brand-primary); +} + +.nav-pills .nav-link.active { + background-color: var(--brand-white) !important; + border: 1px solid var(--gray-lighter); + border-left: 5px solid var(--brand-primary) !important; + border-radius: 0 var(--br-6) var(--br-6) 0; + color: var(--gray-darker); +} + +/* Protruding arrow for active tab in desktop view */ +@media (min-width: 768px) { + .nav-pills .nav-link.active::after { + content: ''; + position: absolute; + top: 50%; + right: -15px; + transform: translateY(-50%); + width: 0; + height: 0; + border-top: 15px solid transparent; + border-bottom: 15px solid transparent; + border-left: 15px solid var(--brand-white); + z-index: 15; + filter: drop-shadow(2px 0px 1px rgba(0, 0, 0, 0.05)); + } +} + +.tab-icon { + width: 40px; + height: 40px; + margin-right: 15px; + flex-shrink: 0; +} + +.tab-title { + font-family: var(--font-heading); + font-weight: 600; + font-size: 20px; + line-height: 1.4; +} + +/* COLUMN 2 & 3 CONTAINER */ +.tab-content { + background-color: transparent; + border-radius: var(--br-10); + overflow: hidden; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25); + height: 100%; +} + +.tab-pane { + height: 100%; +} + +/* COLUMN 2: Slide Text Content */ +.slider-text-column { + background-color: var(--brand-fourth); + padding: 60px; + color: var(--brand-white); + display: flex; + flex-direction: column; + justify-content: center; + position: relative; + height: 100%; + min-height: 450px; +} + +.badge-category { + font-family: var(--font-body); + font-size: 12px; + letter-spacing: 1.5px; + font-weight: 600; + color: rgba(255, 255, 255, 0.85); + background-color: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(255, 255, 255, 0.25); + padding: 6px 12px; + border-radius: var(--br-3); + align-self: flex-start; + margin-bottom: 30px; + text-transform: uppercase; +} + +.slide-title { + font-family: var(--font-heading); + font-size: 34px; + line-height: 1.3; + font-weight: 700; + margin-bottom: 30px; +} + +.slide-link { + font-family: var(--font-body); + font-size: 16px; + font-weight: 600; + color: var(--brand-white); + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 8px; + transition: all 0.3s ease; + margin-top: 10px; +} + +.slide-link:hover { + color: rgba(255, 255, 255, 0.8); + transform: translateX(5px); +} + +.slide-link img { + width: 20px; + height: 20px; + filter: brightness(0) invert(1); +} + +/* Controls inside Column 2 */ +.carousel-controls { + display: flex; + align-items: center; + gap: 20px; + margin-top: auto; + padding-top: 40px; +} + +.carousel-control-prev, +.carousel-control-next { + position: static; + width: 40px; + height: 40px; + background-color: transparent; + border: 2px solid rgba(255, 255, 255, 0.4); + border-radius: var(--br-50); + opacity: 1; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; +} + +.carousel-control-prev:hover, +.carousel-control-next:hover { + background-color: rgba(255, 255, 255, 0.1); + border-color: var(--brand-white); +} + +.carousel-control-prev img, +.carousel-control-next img { + width: 16px; + height: 16px; + filter: brightness(0) invert(1); +} + +/* Pagination Dots */ +.carousel-indicators-custom { + display: flex; + gap: 8px; +} + +.carousel-indicators-custom .dot { + width: 8px; + height: 8px; + border-radius: var(--br-50); + background-color: rgba(255, 255, 255, 0.4); + cursor: pointer; + transition: all 0.3s ease; + border: none; +} + +.carousel-indicators-custom .dot.active { + background-color: var(--brand-white); + transform: scale(1.2); +} + +/* COLUMN 3: Image */ +.slider-image-column { + background-color: var(--brand-secondary); + position: relative; + overflow: hidden; + height: 100%; +} + +.slider-image-wrapper { + position: relative; + width: 100%; + padding-top: 100%; + /* 1:1 Aspect Ratio */ +} + +.slider-img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +/* MOBILE ACCORDION LAYOUT */ +.accordion { + --bs-accordion-bg: transparent; + --bs-accordion-border-color: transparent; + display: flex; + flex-direction: column; + gap: 15px; +} + +.accordion-item { + background-color: transparent; + border: none; +} + +.accordion-button { + background-color: var(--brand-white); + border: 1px solid var(--gray-lighter); + border-radius: var(--br-6) !important; + padding: 20px; + color: var(--gray-darker); + display: flex; + align-items: center; + width: 100%; + transition: all 0.3s ease; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} + +.accordion-button::after { + display: none; + /* Hide default chevron */ +} + +.accordion-button:not(.collapsed) { + background-color: var(--brand-white); + color: var(--gray-darker); + border-left: 5px solid var(--brand-primary); + border-radius: var(--br-6) var(--br-6) 0 0 !important; + box-shadow: none; +} + +.accordion-icon-toggle { + margin-left: auto; + width: 24px; + height: 24px; + background-image: url('uploads/plus-01.svg'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + transition: transform 0.3s ease; +} + +.accordion-button:not(.collapsed) .accordion-icon-toggle { + background-image: url('uploads/minus-01.svg'); +} + +/* Expanded accordion content (Mobile slider) */ +.accordion-collapse { + border: 1px solid var(--gray-lighter); + border-top: none; + border-radius: 0 0 var(--br-6) var(--br-6); + overflow: hidden; +} + +.mobile-slider { + position: relative; + min-height: 350px; +} + +.mobile-slide-item { + position: relative; + background-size: cover; + background-position: center; + min-height: 350px; + display: flex; + flex-direction: column; + justify-content: center; + padding: 40px 30px; +} + +/* Dark overlay on mobile background image */ +.mobile-slide-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(17, 50, 77, 0.85); + /* brand-secondary overlay */ + z-index: 1; +} + +.mobile-slide-content { + position: relative; + z-index: 5; + color: var(--brand-white); + text-align: center; + display: flex; + flex-direction: column; + align-items: center; +} + +.mobile-slide-content .badge-category { + align-self: center; + margin-bottom: 20px; +} + +.mobile-slide-content .slide-title { + font-size: 20px; + margin-bottom: 20px; +} + +.mobile-slider .carousel-controls { + position: absolute; + bottom: 20px; + left: 0; + right: 0; + justify-content: center; + z-index: 10; + margin-top: 0; + padding-top: 0; +} + +.mobile-slider .carousel-control-prev, +.mobile-slider .carousel-control-next { + border-color: rgba(255, 255, 255, 0.6); +} + +/* Admin Dashboard Styling */ +.admin-navbar { + background-color: var(--brand-secondary); +} + +.admin-card { + border: 1px solid var(--gray-lighter); + border-radius: var(--br-6); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} + +.btn-primary-custom { + background-color: var(--brand-primary); + border-color: var(--brand-primary); + color: var(--brand-white); +} + +.btn-primary-custom:hover { + background-color: #a82e1a; + border-color: #a82e1a; + color: var(--brand-white); +} \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..6fe0aef --- /dev/null +++ b/index.php @@ -0,0 +1,307 @@ +getMessage()); +} +?> + + + + + + DelphianLogic in Action + + + + + + + Admin Panel + +
+
+
+
+

DelphianLogic in Action

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo

+
+
+ +
+
+ +
+ +
+
+ $tab): ?> + +
+ +
+ +
+ +
+ +
+ +
+ +
+
+

No slides found for this tab.

+

Go to the Admin Panel to add slide content.

+
+
+ +
+ +
+ +
+
+
+ +
+
+
+ $tab): ?> + +
+

+ +

+ +
+
+ + + +
+

No slides found for this tab.

+

Go to the Admin Panel to add slide content.

+
+ +
+
+
+ +
+
+
+ +
+
+ + + + + + + diff --git a/setup.php b/setup.php new file mode 100644 index 0000000..a3f841d --- /dev/null +++ b/setup.php @@ -0,0 +1,156 @@ +exec("CREATE DATABASE IF NOT EXISTS " . DB_NAME . " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); + echo "Database '" . DB_NAME . "' verified/created.\n"; + + $pdo = getPDO(true); + + $pdo->exec("CREATE TABLE IF NOT EXISTS tabs ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + icon VARCHAR(255) NOT NULL + ) ENGINE=InnoDB;"); + echo "Table 'tabs' verified/created.\n"; + + $pdo->exec("CREATE TABLE IF NOT EXISTS slides ( + id INT AUTO_INCREMENT PRIMARY KEY, + tab_id INT NOT NULL, + pre_header VARCHAR(255) NOT NULL, + title VARCHAR(255) NOT NULL, + link_url VARCHAR(255) NOT NULL, + image VARCHAR(255) NOT NULL, + sort_order INT DEFAULT 0, + FOREIGN KEY (tab_id) REFERENCES tabs(id) ON DELETE CASCADE + ) ENGINE=InnoDB;"); + echo "Table 'slides' verified/created.\n"; + + $uploadDir = __DIR__ . '/uploads'; + if (!file_exists($uploadDir)) { + mkdir($uploadDir, 0777, true); + echo "Created uploads directory: $uploadDir\n"; + } + + $srcImagesDir = __DIR__ . '/files/images'; + $filesToCopy = [ + 'DL-Communication.jpg', + 'DL-Learning-1.jpg', + 'DL-Technology.jpg', + 'DL-communication.svg', + 'DL-learning.svg', + 'DL-technology.svg', + 'arrow-right.svg', + 'minus-01.svg', + 'plus-01.svg' + ]; + + foreach ($filesToCopy as $file) { + $srcFile = $srcImagesDir . '/' . $file; + $destFile = $uploadDir . '/' . $file; + if (file_exists($srcFile)) { + if (!file_exists($destFile)) { + copy($srcFile, $destFile); + echo "Copied $file to uploads.\n"; + } + } else { + echo "Warning: Source file $srcFile not found.\n"; + } + } + + $countTabs = $pdo->query("SELECT COUNT(*) FROM tabs")->fetchColumn(); + if ($countTabs == 0) { + echo "Seeding default tabs and slides...\n"; + + $stmtTab = $pdo->prepare("INSERT INTO tabs (title, icon) VALUES (?, ?)"); + + $stmtTab->execute(['Learning', 'uploads/DL-learning.svg']); + $learningId = $pdo->lastInsertId(); + + $stmtTab->execute(['Technology', 'uploads/DL-technology.svg']); + $technologyId = $pdo->lastInsertId(); + + $stmtTab->execute(['Communication', 'uploads/DL-communication.svg']); + $communicationId = $pdo->lastInsertId(); + + $stmtSlide = $pdo->prepare("INSERT INTO slides (tab_id, pre_header, title, link_url, image, sort_order) VALUES (?, ?, ?, ?, ?, ?)"); + + $stmtSlide->execute([ + $learningId, + 'DIGITAL LEARNING INFRASTRUCTURE', + 'Usability enhancement and Training for Transaction Portal for Customers', + '#', + 'uploads/DL-Learning-1.jpg', + 1 + ]); + + $stmtSlide->execute([ + $learningId, + 'E-LEARNING ENABLEMENT', + 'Interactive Modules for Professional Development & Upskilling', + '#', + 'uploads/DL-Learning-1.jpg', + 2 + ]); + + $stmtSlide->execute([ + $learningId, + 'MOBILE LEARNING', + 'Bite-sized microlearning modules for on-the-go training', + '#', + 'uploads/DL-Learning-1.jpg', + 3 + ]); + + $stmtSlide->execute([ + $technologyId, + 'CLOUD PLATFORM INTEGRATION', + 'Enterprise-wide cloud migration and microservices management', + '#', + 'uploads/DL-Technology.jpg', + 1 + ]); + + $stmtSlide->execute([ + $technologyId, + 'API MANAGEMENT', + 'Building scalable, secure and robust API layers for modern apps', + '#', + 'uploads/DL-Technology.jpg', + 2 + ]); + + $stmtSlide->execute([ + $communicationId, + 'OMNICHANNEL ENGAGEMENT', + 'A unified platform for customer outreach and service automation', + '#', + 'uploads/DL-Communication.jpg', + 1 + ]); + + $stmtSlide->execute([ + $communicationId, + 'INTERNAL COMMUNICATION', + 'Interactive portals that keep teams connected and aligned', + '#', + 'uploads/DL-Communication.jpg', + 2 + ]); + + echo "Seeding completed successfully.\n"; + } else { + echo "Database already has data. Seeding skipped.\n"; + } + + echo "Database setup completed successfully.\n"; + +} catch (Exception $e) { + echo "Error during setup: " . $e->getMessage() . "\n"; + exit(1); +} +?> diff --git a/uploads/DL-Communication.jpg b/uploads/DL-Communication.jpg new file mode 100644 index 0000000..51b3b5b Binary files /dev/null and b/uploads/DL-Communication.jpg differ diff --git a/uploads/DL-Learning-1.jpg b/uploads/DL-Learning-1.jpg new file mode 100644 index 0000000..755171f Binary files /dev/null and b/uploads/DL-Learning-1.jpg differ diff --git a/uploads/DL-Technology.jpg b/uploads/DL-Technology.jpg new file mode 100644 index 0000000..a8d4354 Binary files /dev/null and b/uploads/DL-Technology.jpg differ diff --git a/uploads/DL-communication.svg b/uploads/DL-communication.svg new file mode 100644 index 0000000..0088544 --- /dev/null +++ b/uploads/DL-communication.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/uploads/DL-learning.svg b/uploads/DL-learning.svg new file mode 100644 index 0000000..bfeb0b5 --- /dev/null +++ b/uploads/DL-learning.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/uploads/DL-technology.svg b/uploads/DL-technology.svg new file mode 100644 index 0000000..59f2ea5 --- /dev/null +++ b/uploads/DL-technology.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/uploads/arrow-right.svg b/uploads/arrow-right.svg new file mode 100644 index 0000000..37d307b --- /dev/null +++ b/uploads/arrow-right.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/uploads/minus-01.svg b/uploads/minus-01.svg new file mode 100644 index 0000000..a0639ca --- /dev/null +++ b/uploads/minus-01.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/uploads/plus-01.svg b/uploads/plus-01.svg new file mode 100644 index 0000000..d80a766 --- /dev/null +++ b/uploads/plus-01.svg @@ -0,0 +1,9 @@ + + + + + + +