Initial Commit with ENVs, directory structures, cursor rules, etc
This commit is contained in:
commit
1a65e69d25
22
.cursor/rules/naming-conventions.mdc
Normal file
22
.cursor/rules/naming-conventions.mdc
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
description:
|
||||
globs: *.php,*.js,*.css
|
||||
alwaysApply: false
|
||||
---
|
||||
.php files should use the following naming conventions:
|
||||
"classes": "PascalCase",
|
||||
"functions": "camelCase",
|
||||
"variables": "camelCase",
|
||||
"constants": "UPPER_SNAKE_CASE"
|
||||
|
||||
.js files should use the following naming conventions:
|
||||
"functions": "camelCase",
|
||||
"variables": "camelCase",
|
||||
"constants": "UPPER_SNAKE_CASE"
|
||||
|
||||
|
||||
.css files should us the following naming conventions:
|
||||
|
||||
"ids": "camelCase",
|
||||
|
||||
"classes": "kebab-case"
|
||||
8
.cursor/rules/project-guidelines.mdc
Normal file
8
.cursor/rules/project-guidelines.mdc
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
This is a zero build project. We are not allowing packages to be installed or suggested for this project. No dependendcies on the backend/server side. On the front-end we will only allow CDN js and css files or libraries.
|
||||
|
||||
The following are allowed on the front-end: bootstrap|fontawesome|jquery
|
||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# Data directory
|
||||
/data/
|
||||
/data/*.json
|
||||
|
||||
# Exports directory
|
||||
/exports/
|
||||
/exports/*.json
|
||||
/exports/export_log.txt
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# IDE files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.sublime-*
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
15
.htaccess
Normal file
15
.htaccess
Normal file
@ -0,0 +1,15 @@
|
||||
# Protect data directory
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteRule ^data/.* - [F,L]
|
||||
RewriteRule ^exports/.* - [F,L]
|
||||
</IfModule>
|
||||
|
||||
# Disable directory browsing
|
||||
Options -Indexes
|
||||
|
||||
# Deny access to hidden files and directories
|
||||
<FilesMatch "^\.">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
59
assets/css/style.css
Normal file
59
assets/css/style.css
Normal file
@ -0,0 +1,59 @@
|
||||
/* Main Styles */
|
||||
:root {
|
||||
--primary-color: #4a90e2;
|
||||
--secondary-color: #f5f5f5;
|
||||
--text-color: #333;
|
||||
--border-color: #ddd;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Tab Styles */
|
||||
.tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: none;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: white;
|
||||
border-color: var(--border-color);
|
||||
border-bottom: 1px solid white;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tab {
|
||||
margin-right: 0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
25
assets/js/main.js
Normal file
25
assets/js/main.js
Normal file
@ -0,0 +1,25 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Tab functionality
|
||||
const tabs = document.querySelectorAll('.tab');
|
||||
const tabContents = document.querySelectorAll('.tab-content');
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
// Remove active class from all tabs
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
tabContents.forEach(content => content.style.display = 'none');
|
||||
|
||||
// Add active class to clicked tab
|
||||
tab.classList.add('active');
|
||||
|
||||
// Show corresponding content
|
||||
const targetId = tab.getAttribute('data-target');
|
||||
document.getElementById(targetId).style.display = 'block';
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize first tab as active
|
||||
if (tabs.length > 0) {
|
||||
tabs[0].click();
|
||||
}
|
||||
});
|
||||
41
config/config.php
Normal file
41
config/config.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/env.php';
|
||||
|
||||
// Load environment variables
|
||||
Env::load();
|
||||
|
||||
// Paths
|
||||
define('ROOT_PATH', dirname(__DIR__));
|
||||
define('DATA_PATH', ROOT_PATH . '/data');
|
||||
define('EXPORT_PATH', ROOT_PATH . '/exports');
|
||||
|
||||
// Google API Configuration
|
||||
define('GOOGLE_CLIENT_ID', Env::get('GOOGLE_CLIENT_ID'));
|
||||
define('GOOGLE_CLIENT_SECRET', Env::get('GOOGLE_CLIENT_SECRET'));
|
||||
define('GOOGLE_REDIRECT_URI', Env::get('GOOGLE_REDIRECT_URI'));
|
||||
define('GOOGLE_CALENDAR_ID', Env::get('GOOGLE_CALENDAR_ID'));
|
||||
define('GOOGLE_CALENDAR_EMBED_CODE', Env::get('GOOGLE_CALENDAR_EMBED_CODE'));
|
||||
define('GOOGLE_DRIVE_FOLDER_ID', Env::get('GOOGLE_DRIVE_FOLDER_ID'));
|
||||
|
||||
// Application Settings
|
||||
define('APP_ENV', Env::get('APP_ENV', 'production'));
|
||||
define('APP_DEBUG', Env::get('APP_DEBUG', 'false') === 'true');
|
||||
define('APP_URL', Env::get('APP_URL', 'http://localhost/family-hub'));
|
||||
|
||||
// Export settings
|
||||
define('EXPORT_DESTINATION', EXPORT_PATH);
|
||||
define('EXPORT_FREQUENCY', Env::get('EXPORT_FREQUENCY', 'daily'));
|
||||
define('EXPORT_RETENTION_DAYS', (int)Env::get('EXPORT_RETENTION_DAYS', 30));
|
||||
|
||||
// Tab configuration
|
||||
$TABS = [
|
||||
'chores' => ['title' => 'Chores', 'icon' => 'tasks'],
|
||||
'groceries' => ['title' => 'Grocery List', 'icon' => 'shopping-cart'],
|
||||
'meals' => ['title' => 'Meal Plan', 'icon' => 'utensils']
|
||||
];
|
||||
|
||||
// Load local configuration if exists
|
||||
if (file_exists(__DIR__ . '/local.php')) {
|
||||
include __DIR__ . '/local.php';
|
||||
}
|
||||
?>
|
||||
41
config/env.php
Normal file
41
config/env.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
class Env {
|
||||
private static $variables = [];
|
||||
|
||||
public static function load() {
|
||||
$envFile = dirname(__DIR__) . '/.env';
|
||||
|
||||
if (!file_exists($envFile)) {
|
||||
throw new Exception('.env file not found. Please create one based on .env.example');
|
||||
}
|
||||
|
||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
// Skip comments
|
||||
if (strpos(trim($line), '#') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list($name, $value) = explode('=', $line, 2);
|
||||
$name = trim($name);
|
||||
$value = trim($value);
|
||||
|
||||
// Remove quotes if present
|
||||
if (strpos($value, '"') === 0 || strpos($value, "'") === 0) {
|
||||
$value = substr($value, 1, -1);
|
||||
}
|
||||
|
||||
self::$variables[$name] = $value;
|
||||
putenv("$name=$value");
|
||||
}
|
||||
}
|
||||
|
||||
public static function get($key, $default = null) {
|
||||
return self::$variables[$key] ?? $default;
|
||||
}
|
||||
|
||||
public static function all() {
|
||||
return self::$variables;
|
||||
}
|
||||
}
|
||||
20
env.example
Normal file
20
env.example
Normal file
@ -0,0 +1,20 @@
|
||||
# Google API Credentials
|
||||
GOOGLE_CLIENT_ID=your_client_id_here
|
||||
GOOGLE_CLIENT_SECRET=your_client_secret_here
|
||||
GOOGLE_REDIRECT_URI=http://localhost/family-hub/auth/google/callback
|
||||
|
||||
# Google Calendar
|
||||
GOOGLE_CALENDAR_ID=your_calendar_id_here
|
||||
GOOGLE_CALENDAR_EMBED_CODE=your_embed_code_here
|
||||
|
||||
# Google Drive
|
||||
GOOGLE_DRIVE_FOLDER_ID=your_folder_id_here
|
||||
|
||||
# Application Settings
|
||||
APP_ENV=development
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost/family-hub
|
||||
|
||||
# Export Settings
|
||||
EXPORT_FREQUENCY=daily
|
||||
EXPORT_RETENTION_DAYS=30
|
||||
29
export.php
Normal file
29
export.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/db.php';
|
||||
require_once __DIR__ . '/includes/utils.php';
|
||||
require_once __DIR__ . '/config/config.php';
|
||||
|
||||
function exportData($type) {
|
||||
ensureExportDirectory();
|
||||
$data = readJsonFile($type . '.json');
|
||||
$filename = generateExportFilename($type);
|
||||
$exportPath = EXPORT_DESTINATION . '/' . $filename;
|
||||
|
||||
return file_put_contents($exportPath, json_encode($data, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
// Handle export request
|
||||
if (isset($_GET['type'])) {
|
||||
$type = sanitizeInput($_GET['type']);
|
||||
if (in_array($type, ['chores', 'groceries', 'meals'])) {
|
||||
if (exportData($type)) {
|
||||
echo json_encode(['success' => true, 'message' => 'Export successful']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Export failed']);
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid export type']);
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'No export type specified']);
|
||||
}
|
||||
29
gitignore
Normal file
29
gitignore
Normal file
@ -0,0 +1,29 @@
|
||||
# Data files (contain user data)
|
||||
/data/*.json
|
||||
|
||||
# Export directory
|
||||
/exports/
|
||||
|
||||
# Environment-specific configurations
|
||||
/config/local.php
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Editor files
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.tmp.*
|
||||
*.tmp/*
|
||||
*.tmp/*.*
|
||||
|
||||
# Cache files
|
||||
*.cache
|
||||
*.cache.*
|
||||
*.cache/*
|
||||
*.cache/*.*
|
||||
23
includes/db.php
Normal file
23
includes/db.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
function readJsonFile($filename) {
|
||||
$filepath = __DIR__ . '/../data/' . $filename;
|
||||
if (!file_exists($filepath)) {
|
||||
return [];
|
||||
}
|
||||
$content = file_get_contents($filepath);
|
||||
return json_decode($content, true) ?? [];
|
||||
}
|
||||
|
||||
function writeJsonFile($filename, $data) {
|
||||
$filepath = __DIR__ . '/../data/' . $filename;
|
||||
$json = json_encode($data, JSON_PRETTY_PRINT);
|
||||
return file_put_contents($filepath, $json);
|
||||
}
|
||||
|
||||
function ensureDataDirectory() {
|
||||
$dataDir = __DIR__ . '/../data';
|
||||
if (!file_exists($dataDir)) {
|
||||
mkdir($dataDir, 0755, true);
|
||||
}
|
||||
}
|
||||
17
includes/footer.php
Normal file
17
includes/footer.php
Normal file
@ -0,0 +1,17 @@
|
||||
</main>
|
||||
<footer class="bg-light p-3 mt-4">
|
||||
<div class="container text-center">
|
||||
<p>Family Hub © <?= date('Y') ?></p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap JS from CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- jQuery from CDN (if needed) -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
|
||||
<!-- Custom JavaScript -->
|
||||
<script src="assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
23
includes/header.php
Normal file
23
includes/header.php
Normal file
@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Family Hub</title>
|
||||
|
||||
<!-- Bootstrap CSS from CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Font Awesome icons from CDN -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- Custom styles -->
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="bg-primary text-white p-3">
|
||||
<div class="container">
|
||||
<h1>Family Hub</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main class="container py-4">
|
||||
23
includes/utils.php
Normal file
23
includes/utils.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
|
||||
function sanitizeInput($input) {
|
||||
if (is_array($input)) {
|
||||
return array_map('sanitizeInput', $input);
|
||||
}
|
||||
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function formatDate($date) {
|
||||
return date('Y-m-d', strtotime($date));
|
||||
}
|
||||
|
||||
function generateExportFilename($type) {
|
||||
return $type . '_' . date('Y-m-d') . '.json';
|
||||
}
|
||||
|
||||
function ensureExportDirectory() {
|
||||
if (!file_exists(EXPORT_DESTINATION)) {
|
||||
mkdir(EXPORT_DESTINATION, 0755, true);
|
||||
}
|
||||
}
|
||||
27
index.php
Normal file
27
index.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
require_once 'config/config.php';
|
||||
require_once 'includes/db.php';
|
||||
require_once 'includes/utils.php';
|
||||
|
||||
// Determine which tab is active
|
||||
$activeTab = isset($_GET['tab']) ? $_GET['tab'] : 'chores';
|
||||
|
||||
// Include header
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container">
|
||||
<div class="tabs">
|
||||
<?php foreach ($TABS as $tabId => $tab): ?>
|
||||
<a href="?tab=<?= $tabId ?>" class="tab <?= $activeTab === $tabId ? 'active' : '' ?>">
|
||||
<i class="fa fa-<?= $tab['icon'] ?>"></i> <?= $tab['title'] ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
<?php include "tabs/$activeTab.php"; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
47
readme.md
Normal file
47
readme.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Family Hub
|
||||
|
||||
A centralized family organization system with tabs for chores, grocery lists, and meal planning.
|
||||
|
||||
## Features
|
||||
- Tabbed interface for different family needs
|
||||
- JSON-based data storage
|
||||
- Daily automated exports
|
||||
- Mobile-friendly interface
|
||||
|
||||
## Setup
|
||||
1. Clone this repository to your web server
|
||||
2. Ensure proper permissions on data directory
|
||||
3. Set up the daily cron job for exports
|
||||
4. Access the hub at http://your-local-ip/familyHub/
|
||||
|
||||
## Directory Structure
|
||||
familyHub/
|
||||
├── assets/ # Static assets
|
||||
│ ├── css/ # CSS files
|
||||
│ │ └── style.css
|
||||
│ ├── js/ # JavaScript files
|
||||
│ │ └── main.js
|
||||
│ └── img/ # Images
|
||||
├── config/ # Configuration files
|
||||
│ └── config.php
|
||||
├── data/ # JSON data storage (not tracked in git)
|
||||
│ ├── chores.json
|
||||
│ ├── groceries.json
|
||||
│ └── meals.json
|
||||
├── includes/ # PHP includes/components
|
||||
│ ├── header.php
|
||||
│ ├── footer.php
|
||||
│ ├── db.php # JSON file handling functions
|
||||
│ └── utils.php # Utility functions
|
||||
├── exports/ # Temporary location for exports (not tracked in git)
|
||||
├── scripts/ # Scripts for cron jobs
|
||||
│ └── daily_export.php
|
||||
├── tabs/ # Tab-specific functionality
|
||||
│ ├── chores.php
|
||||
│ ├── groceries.php
|
||||
│ └── meals.php
|
||||
├── .gitignore
|
||||
├── .cursor.json # Cursor editor configuration
|
||||
├── README.md
|
||||
├── index.php # Main entry point
|
||||
└── export.php # Export functionality
|
||||
26
scripts/daily_export.php
Normal file
26
scripts/daily_export.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/db.php';
|
||||
require_once __DIR__ . '/../includes/utils.php';
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
|
||||
// Export all data types
|
||||
$types = ['chores', 'groceries', 'meals'];
|
||||
$results = [];
|
||||
|
||||
foreach ($types as $type) {
|
||||
$data = readJsonFile($type . '.json');
|
||||
$filename = generateExportFilename($type);
|
||||
$exportPath = EXPORT_DESTINATION . '/' . $filename;
|
||||
|
||||
if (file_put_contents($exportPath, json_encode($data, JSON_PRETTY_PRINT))) {
|
||||
$results[$type] = 'success';
|
||||
} else {
|
||||
$results[$type] = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
// Log results
|
||||
$logFile = EXPORT_DESTINATION . '/export_log.txt';
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$logMessage = "[$timestamp] Export results: " . json_encode($results) . "\n";
|
||||
file_put_contents($logFile, $logMessage, FILE_APPEND);
|
||||
25
tabs/chores.php
Normal file
25
tabs/chores.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/db.php';
|
||||
require_once __DIR__ . '/../includes/utils.php';
|
||||
|
||||
$chores = readJsonFile('chores.json');
|
||||
?>
|
||||
|
||||
<div id="chores" class="tab-content">
|
||||
<h2>Chores</h2>
|
||||
<div class="chores-list">
|
||||
<?php if (empty($chores)): ?>
|
||||
<p>No chores added yet.</p>
|
||||
<?php else: ?>
|
||||
<ul>
|
||||
<?php foreach ($chores as $chore): ?>
|
||||
<li>
|
||||
<span class="chore-name"><?php echo sanitizeInput($chore['name']); ?></span>
|
||||
<span class="chore-assignee"><?php echo sanitizeInput($chore['assignee']); ?></span>
|
||||
<span class="chore-due-date"><?php echo formatDate($chore['due_date']); ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
25
tabs/groceries.php
Normal file
25
tabs/groceries.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/db.php';
|
||||
require_once __DIR__ . '/../includes/utils.php';
|
||||
|
||||
$groceries = readJsonFile('groceries.json');
|
||||
?>
|
||||
|
||||
<div id="groceries" class="tab-content">
|
||||
<h2>Grocery List</h2>
|
||||
<div class="groceries-list">
|
||||
<?php if (empty($groceries)): ?>
|
||||
<p>No items in the grocery list yet.</p>
|
||||
<?php else: ?>
|
||||
<ul>
|
||||
<?php foreach ($groceries as $item): ?>
|
||||
<li>
|
||||
<span class="item-name"><?php echo sanitizeInput($item['name']); ?></span>
|
||||
<span class="item-quantity"><?php echo sanitizeInput($item['quantity']); ?></span>
|
||||
<span class="item-category"><?php echo sanitizeInput($item['category']); ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
25
tabs/meals.php
Normal file
25
tabs/meals.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/db.php';
|
||||
require_once __DIR__ . '/../includes/utils.php';
|
||||
|
||||
$meals = readJsonFile('meals.json');
|
||||
?>
|
||||
|
||||
<div id="meals" class="tab-content">
|
||||
<h2>Meal Planning</h2>
|
||||
<div class="meals-list">
|
||||
<?php if (empty($meals)): ?>
|
||||
<p>No meals planned yet.</p>
|
||||
<?php else: ?>
|
||||
<ul>
|
||||
<?php foreach ($meals as $meal): ?>
|
||||
<li>
|
||||
<span class="meal-name"><?php echo sanitizeInput($meal['name']); ?></span>
|
||||
<span class="meal-date"><?php echo formatDate($meal['date']); ?></span>
|
||||
<span class="meal-type"><?php echo sanitizeInput($meal['type']); ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
Loading…
x
Reference in New Issue
Block a user