feat: Complete Woles Framework v1.0 with enterprise-grade UI
- Add comprehensive error handling system with custom error pages - Implement professional enterprise-style design with Tailwind CSS - Create modular HMVC architecture with clean separation of concerns - Add security features: CSRF protection, XSS filtering, Argon2ID hashing - Include CLI tools for development workflow - Add error reporting dashboard with system monitoring - Implement responsive design with consistent slate color scheme - Replace all emoji icons with professional SVG icons - Add comprehensive test suite with PHPUnit - Include database migrations and seeders - Add proper exception handling with fallback pages - Implement template engine with custom syntax support - Add helper functions and facades for clean code - Include proper logging and debugging capabilities
This commit is contained in:
219
app/Modules/User/Controller.php
Normal file
219
app/Modules/User/Controller.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\User;
|
||||
|
||||
use App\Core\Controller as BaseController;
|
||||
|
||||
/**
|
||||
* User Controller
|
||||
* Handles user management
|
||||
*/
|
||||
class Controller extends BaseController
|
||||
{
|
||||
private Model $model;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->model = new Model();
|
||||
}
|
||||
|
||||
/**
|
||||
* List all users
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$users = $this->model->all();
|
||||
|
||||
if ($this->request()->expectsJson()) {
|
||||
return $this->json($users);
|
||||
}
|
||||
|
||||
return $this->view('User.view.index', [
|
||||
'title' => 'Users - NovaCore Framework',
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show user details
|
||||
*/
|
||||
public function show(int $id)
|
||||
{
|
||||
$user = $this->model->findById($id);
|
||||
|
||||
if (!$user) {
|
||||
if ($this->request()->expectsJson()) {
|
||||
return $this->error('User not found', 404);
|
||||
}
|
||||
|
||||
http_response_code(404);
|
||||
echo "<h1>404 - User Not Found</h1>";
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->request()->expectsJson()) {
|
||||
return $this->json($user);
|
||||
}
|
||||
|
||||
return $this->view('User.view.show', [
|
||||
'title' => 'User Details - NovaCore Framework',
|
||||
'user' => $user
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show create user form
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return $this->view('User.view.create', [
|
||||
'title' => 'Create User - NovaCore Framework'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store new user
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
$data = $this->request()->all();
|
||||
|
||||
// Validation
|
||||
$errors = $this->validate($data, [
|
||||
'name' => 'required|min:2',
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|min:6'
|
||||
]);
|
||||
|
||||
// Check if email exists
|
||||
if (empty($errors) && $this->model->emailExists($data['email'])) {
|
||||
$errors['email'] = 'Email already exists.';
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
if ($this->request()->expectsJson()) {
|
||||
return $this->error('Validation failed', 422);
|
||||
}
|
||||
|
||||
return $this->view('User.view.create', [
|
||||
'title' => 'Create User - NovaCore Framework',
|
||||
'errors' => $errors,
|
||||
'old' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
// Create user
|
||||
$userId = $this->model->create($data);
|
||||
|
||||
if ($this->request()->expectsJson()) {
|
||||
return $this->success(['id' => $userId], 'User created successfully');
|
||||
}
|
||||
|
||||
return $this->redirect('/users');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show edit user form
|
||||
*/
|
||||
public function edit(int $id)
|
||||
{
|
||||
$user = $this->model->findById($id);
|
||||
|
||||
if (!$user) {
|
||||
http_response_code(404);
|
||||
echo "<h1>404 - User Not Found</h1>";
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->view('User.view.edit', [
|
||||
'title' => 'Edit User - NovaCore Framework',
|
||||
'user' => $user
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user
|
||||
*/
|
||||
public function update(int $id)
|
||||
{
|
||||
$user = $this->model->findById($id);
|
||||
|
||||
if (!$user) {
|
||||
if ($this->request()->expectsJson()) {
|
||||
return $this->error('User not found', 404);
|
||||
}
|
||||
|
||||
http_response_code(404);
|
||||
echo "<h1>404 - User Not Found</h1>";
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->request()->all();
|
||||
|
||||
// Validation
|
||||
$errors = $this->validate($data, [
|
||||
'name' => 'required|min:2',
|
||||
'email' => 'required|email'
|
||||
]);
|
||||
|
||||
// Check if email exists (excluding current user)
|
||||
if (empty($errors) && $this->model->emailExists($data['email'], $id)) {
|
||||
$errors['email'] = 'Email already exists.';
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
if ($this->request()->expectsJson()) {
|
||||
return $this->error('Validation failed', 422);
|
||||
}
|
||||
|
||||
return $this->view('User.view.edit', [
|
||||
'title' => 'Edit User - NovaCore Framework',
|
||||
'user' => array_merge($user, $data),
|
||||
'errors' => $errors
|
||||
]);
|
||||
}
|
||||
|
||||
// Remove password if empty
|
||||
if (empty($data['password'])) {
|
||||
unset($data['password']);
|
||||
} else {
|
||||
$data['password'] = password_hash($data['password'], PASSWORD_ARGON2ID);
|
||||
}
|
||||
|
||||
// Update user
|
||||
$this->model->update($id, $data);
|
||||
|
||||
if ($this->request()->expectsJson()) {
|
||||
return $this->success([], 'User updated successfully');
|
||||
}
|
||||
|
||||
return $this->redirect('/users');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user
|
||||
*/
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$user = $this->model->findById($id);
|
||||
|
||||
if (!$user) {
|
||||
if ($this->request()->expectsJson()) {
|
||||
return $this->error('User not found', 404);
|
||||
}
|
||||
|
||||
http_response_code(404);
|
||||
echo "<h1>404 - User Not Found</h1>";
|
||||
return;
|
||||
}
|
||||
|
||||
$this->model->delete($id);
|
||||
|
||||
if ($this->request()->expectsJson()) {
|
||||
return $this->success([], 'User deleted successfully');
|
||||
}
|
||||
|
||||
return $this->redirect('/users');
|
||||
}
|
||||
}
|
||||
184
app/Modules/User/Model.php
Normal file
184
app/Modules/User/Model.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\User;
|
||||
|
||||
/**
|
||||
* User Model
|
||||
* User management model
|
||||
*/
|
||||
class Model
|
||||
{
|
||||
private \PDO $pdo;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pdo = $this->getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database connection
|
||||
*/
|
||||
private function getConnection(): \PDO
|
||||
{
|
||||
$config = include __DIR__ . '/../../Config/database.php';
|
||||
$connection = $config['connections'][$config['default']];
|
||||
|
||||
$dsn = "mysql:host={$connection['host']};port={$connection['port']};dbname={$connection['database']};charset={$connection['charset']}";
|
||||
|
||||
return new \PDO($dsn, $connection['username'], $connection['password'], $connection['options']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by ID
|
||||
*/
|
||||
public function findById(int $id): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
$user = $stmt->fetch();
|
||||
return $user ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by email
|
||||
*/
|
||||
public function findByEmail(string $email): ?array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
|
||||
$user = $stmt->fetch();
|
||||
return $user ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all users
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
$stmt = $this->pdo->query("SELECT id, name, email, created_at, updated_at FROM users ORDER BY created_at DESC");
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new user
|
||||
*/
|
||||
public function create(array $data): int
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO users (name, email, password, created_at, updated_at)
|
||||
VALUES (?, ?, ?, NOW(), NOW())
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$data['name'],
|
||||
$data['email'],
|
||||
password_hash($data['password'], PASSWORD_ARGON2ID)
|
||||
]);
|
||||
|
||||
return $this->pdo->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user
|
||||
*/
|
||||
public function update(int $id, array $data): bool
|
||||
{
|
||||
$fields = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if ($key !== 'id') {
|
||||
$fields[] = "{$key} = ?";
|
||||
$values[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$values[] = $id;
|
||||
$sql = "UPDATE users SET " . implode(', ', $fields) . ", updated_at = NOW() WHERE id = ?";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
return $stmt->execute($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user
|
||||
*/
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$stmt = $this->pdo->prepare("DELETE FROM users WHERE id = ?");
|
||||
return $stmt->execute([$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if email exists
|
||||
*/
|
||||
public function emailExists(string $email, ?int $excludeId = null): bool
|
||||
{
|
||||
$sql = "SELECT COUNT(*) FROM users WHERE email = ?";
|
||||
$params = [$email];
|
||||
|
||||
if ($excludeId) {
|
||||
$sql .= " AND id != ?";
|
||||
$params[] = $excludeId;
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
return $stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get users with pagination
|
||||
*/
|
||||
public function paginate(int $page = 1, int $perPage = 10): array
|
||||
{
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT id, name, email, created_at, updated_at
|
||||
FROM users
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
");
|
||||
|
||||
$stmt->execute([$perPage, $offset]);
|
||||
$users = $stmt->fetchAll();
|
||||
|
||||
// Get total count
|
||||
$countStmt = $this->pdo->query("SELECT COUNT(*) FROM users");
|
||||
$total = $countStmt->fetchColumn();
|
||||
|
||||
return [
|
||||
'data' => $users,
|
||||
'total' => $total,
|
||||
'per_page' => $perPage,
|
||||
'current_page' => $page,
|
||||
'last_page' => ceil($total / $perPage)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Search users
|
||||
*/
|
||||
public function search(string $query): array
|
||||
{
|
||||
$stmt = $this->pdo->prepare("
|
||||
SELECT id, name, email, created_at, updated_at
|
||||
FROM users
|
||||
WHERE name LIKE ? OR email LIKE ?
|
||||
ORDER BY created_at DESC
|
||||
");
|
||||
|
||||
$searchTerm = "%{$query}%";
|
||||
$stmt->execute([$searchTerm, $searchTerm]);
|
||||
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
}
|
||||
13
app/Modules/User/routes.php
Normal file
13
app/Modules/User/routes.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* User Module Routes
|
||||
*/
|
||||
|
||||
$router->get('/users', 'User\Controller@index');
|
||||
$router->get('/users/{id}', 'User\Controller@show');
|
||||
$router->get('/users/create', 'User\Controller@create');
|
||||
$router->post('/users', 'User\Controller@store');
|
||||
$router->get('/users/{id}/edit', 'User\Controller@edit');
|
||||
$router->put('/users/{id}', 'User\Controller@update');
|
||||
$router->delete('/users/{id}', 'User\Controller@destroy');
|
||||
119
app/Modules/User/view/create.php
Normal file
119
app/Modules/User/view/create.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ $title }}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
'sans': ['Inter', 'system-ui', 'sans-serif'],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="font-sans bg-gray-50 min-h-screen">
|
||||
<!-- Header -->
|
||||
<header class="bg-white shadow-sm border-b border-gray-200">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<div class="flex items-center">
|
||||
<div class="text-2xl mr-3">⚡</div>
|
||||
<h1 class="text-xl font-bold text-gray-900">Woles Framework</h1>
|
||||
</div>
|
||||
<nav class="flex space-x-4">
|
||||
<a href="/dashboard" class="text-gray-600 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium transition-colors">Dashboard</a>
|
||||
<a href="/users" class="text-blue-600 hover:text-blue-700 px-3 py-2 rounded-md text-sm font-medium transition-colors">Users</a>
|
||||
<a href="/logout" class="text-red-600 hover:text-red-700 px-3 py-2 rounded-md text-sm font-medium transition-colors">Logout</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- Page Header -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-3xl font-bold text-gray-900 mb-2">Create New User</h2>
|
||||
<p class="text-gray-600">Add a new user to the system</p>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="bg-white shadow-sm rounded-xl border border-gray-200 p-8">
|
||||
<form method="POST" action="/users" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Full Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
value="{{ old('name', $old['name'] ?? '') }}"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
|
||||
placeholder="Enter full name"
|
||||
required>
|
||||
@if (isset($errors['name']))
|
||||
<p class="mt-2 text-sm text-red-600">{{ $errors['name'] }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
value="{{ old('email', $old['email'] ?? '') }}"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
|
||||
placeholder="Enter email address"
|
||||
required>
|
||||
@if (isset($errors['email']))
|
||||
<p class="mt-2 text-sm text-red-600">{{ $errors['email'] }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
|
||||
placeholder="Enter password"
|
||||
required>
|
||||
@if (isset($errors['password']))
|
||||
<p class="mt-2 text-sm text-red-600">{{ $errors['password'] }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-4 pt-4">
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
||||
Create User
|
||||
</button>
|
||||
<a href="/users" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-3 rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
203
app/Modules/User/view/edit.php
Normal file
203
app/Modules/User/view/edit.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ $title }}</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: white;
|
||||
padding: 1rem 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #667eea;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
color: #333;
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 2px solid #e1e5e9;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.field-error {
|
||||
color: #dc3545;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.3s ease;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>NovaCore Framework</h1>
|
||||
<div class="nav-links">
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
<a href="/users">Users</a>
|
||||
<a href="/logout">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h2>Edit User</h2>
|
||||
<p>Update user information</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<form method="POST" action="/users/{{ $user['id'] }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Full Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
value="{{ $user['name'] }}"
|
||||
required>
|
||||
@if (isset($errors['name']))
|
||||
<div class="field-error">{{ $errors['name'] }}</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
value="{{ $user['email'] }}"
|
||||
required>
|
||||
@if (isset($errors['email']))
|
||||
<div class="field-error">{{ $errors['email'] }}</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">New Password (leave blank to keep current)</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password">
|
||||
@if (isset($errors['password']))
|
||||
<div class="field-error">{{ $errors['password'] }}</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn">Update User</button>
|
||||
<a href="/users/{{ $user['id'] }}" class="btn btn-secondary">Cancel</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
110
app/Modules/User/view/index.php
Normal file
110
app/Modules/User/view/index.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ $title }}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
'sans': ['Inter', 'system-ui', 'sans-serif'],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="font-sans bg-gray-50 min-h-screen">
|
||||
<!-- Header -->
|
||||
<header class="bg-white shadow-sm border-b border-gray-200">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center h-16">
|
||||
<div class="flex items-center">
|
||||
<div class="text-2xl mr-3">⚡</div>
|
||||
<h1 class="text-xl font-bold text-gray-900">Woles Framework</h1>
|
||||
</div>
|
||||
<nav class="flex space-x-4">
|
||||
<a href="/dashboard" class="text-gray-600 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium transition-colors">Dashboard</a>
|
||||
<a href="/users" class="text-blue-600 hover:text-blue-700 px-3 py-2 rounded-md text-sm font-medium transition-colors">Users</a>
|
||||
<a href="/logout" class="text-red-600 hover:text-red-700 px-3 py-2 rounded-md text-sm font-medium transition-colors">Logout</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- Page Header -->
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<h2 class="text-3xl font-bold text-gray-900">Users Management</h2>
|
||||
<a href="/users/create" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-medium transition-colors">
|
||||
Add New User
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Users Table -->
|
||||
<div class="bg-white shadow-sm rounded-xl border border-gray-200 overflow-hidden">
|
||||
@if (empty($users))
|
||||
<div class="text-center py-12">
|
||||
<div class="text-6xl mb-4">👥</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">No users found</h3>
|
||||
<p class="text-gray-600 mb-6">Get started by creating your first user.</p>
|
||||
<a href="/users/create" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg font-medium transition-colors">
|
||||
Create User
|
||||
</a>
|
||||
</div>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Created At</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@foreach ($users as $user)
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $user['id'] }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $user['name'] }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ $user['email'] }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ date('M j, Y', strtotime($user['created_at'])) }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<div class="flex space-x-2">
|
||||
<a href="/users/{{ $user['id'] }}" class="text-blue-600 hover:text-blue-700 bg-blue-50 hover:bg-blue-100 px-3 py-1 rounded-md text-xs font-medium transition-colors">
|
||||
View
|
||||
</a>
|
||||
<a href="/users/{{ $user['id'] }}/edit" class="text-green-600 hover:text-green-700 bg-green-50 hover:bg-green-100 px-3 py-1 rounded-md text-xs font-medium transition-colors">
|
||||
Edit
|
||||
</a>
|
||||
<form method="POST" action="/users/{{ $user['id'] }}" class="inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit"
|
||||
class="text-red-600 hover:text-red-700 bg-red-50 hover:bg-red-100 px-3 py-1 rounded-md text-xs font-medium transition-colors"
|
||||
onclick="return confirm('Are you sure you want to delete this user?')">
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
202
app/Modules/User/view/show.php
Normal file
202
app/Modules/User/view/show.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ $title }}</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: white;
|
||||
padding: 1rem 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #667eea;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
color: #333;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.3s ease;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #333;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 2rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>NovaCore Framework</h1>
|
||||
<div class="nav-links">
|
||||
<a href="/dashboard">Dashboard</a>
|
||||
<a href="/users">Users</a>
|
||||
<a href="/logout">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h2>User Details</h2>
|
||||
<div>
|
||||
<a href="/users/{{ $user['id'] }}/edit" class="btn btn-secondary">Edit User</a>
|
||||
<a href="/users" class="btn">Back to Users</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="user-info">
|
||||
<div class="info-item">
|
||||
<div class="info-label">ID</div>
|
||||
<div class="info-value">{{ $user['id'] }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<div class="info-label">Name</div>
|
||||
<div class="info-value">{{ $user['name'] }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<div class="info-label">Email</div>
|
||||
<div class="info-value">{{ $user['email'] }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<div class="info-label">Created At</div>
|
||||
<div class="info-value">{{ date('M j, Y g:i A', strtotime($user['created_at'])) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<div class="info-label">Updated At</div>
|
||||
<div class="info-value">{{ date('M j, Y g:i A', strtotime($user['updated_at'])) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<form method="POST" action="/users/{{ $user['id'] }}" style="display: inline;">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-danger"
|
||||
onclick="return confirm('Are you sure you want to delete this user?')">
|
||||
Delete User
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user