inital commit

This commit is contained in:
Tim Lappe 2025-04-22 19:05:28 +02:00
parent 33eb86d6ed
commit b870378fae
15 changed files with 4656 additions and 19 deletions

9
.cursor/rules/react.mdc Normal file
View File

@ -0,0 +1,9 @@
---
description:
globs:
alwaysApply: true
---
# React components
1. You will always provide a maintainable folder structure when adding or updating react components.
2. You will reuse common components

View File

@ -1,4 +1,7 @@
You are an expert React and PHP Symfony developer.
You will build very clean and abstract code to provide a clean and extendable codebase.
You are using PHP 8.4 with the latest symfony in the backend directory.
You are using the latest React version in the frontend directory
You are using the latest React version in the frontend directory
You will never use react native components
You will rarely install new packages

3
.gitignore vendored
View File

@ -1 +1,2 @@
var
var
node_modules

View File

@ -5,7 +5,7 @@ set -e
service php8.4-fpm start
# Change to frontend directory and start npm in background
cd /var/www/html/frontend && npm start &
cd /var/www/html/frontend && npm run start &
# Start Nginx in foreground to keep container running
exec nginx -g 'daemon off;'

View File

@ -6,6 +6,11 @@
"packages": {
"": {
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
@ -2461,6 +2466,76 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz",
"integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz",
"integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz",
"integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==",
"license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz",
"integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==",
"license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz",
"integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==",
"license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.7.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/react-fontawesome": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz",
"integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
"react": ">=16.3"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",

View File

@ -1,5 +1,10 @@
{
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
@ -15,7 +20,7 @@
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"start": "export PORT=9010 && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"

View File

@ -1,24 +1,22 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { TabView } from './components/navigation/TabView';
import Home from './pages/home/Home';
import { faCalendar, faUser } from '@fortawesome/free-solid-svg-icons';
import { faHome } from '@fortawesome/free-solid-svg-icons';
const tabs = [
{ id: 'home', label: 'Home', component: Home, icon: faHome },
{ id: 'calendar', label: 'Calendar', component: Home, icon: faCalendar },
{ id: 'profile', label: 'Profile', component: Home, icon: faUser }
];
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<TabView
tabs={tabs}
/>
</div>
);
}

View File

@ -0,0 +1,72 @@
.tab-container {
display: flex;
flex-direction: column;
height: 100vh;
width: 100%;
position: fixed;
top: 0;
left: 0;
background-color: #f8fafc;
}
.tab-bar {
display: flex;
background-color: #ffffff;
border-top: 1px solid #e2e8f0;
margin-top: auto;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
border-radius: 16px 16px 0 0;
padding: 5px 10px;
}
.tab-bar-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 12px 0;
background: none;
border: none;
cursor: pointer;
color: #64748b;
transition: all 0.3s ease;
border-radius: 12px;
margin: 0 4px;
}
.tab-bar-item:hover {
color: #334155;
background-color: #f1f5f9;
}
.tab-bar-item.active {
color: #3182ce;
background-color: #ebf8ff;
box-shadow: 0 2px 6px rgba(49, 130, 206, 0.15);
transform: translateY(-2px);
}
.tab-icon {
font-size: 22px;
margin-bottom: 6px;
}
.tab-label {
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.tab-content {
flex: 1;
overflow: auto;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #ffffff;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
margin-bottom: -20px;
}

View File

@ -0,0 +1,74 @@
import React, { useState, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
import './TabView.css';
interface TabBarProps {
tabs: {
id: string;
label: string;
icon?: React.ReactNode | IconDefinition;
component: React.ComponentType<any>;
}[];
activeTab?: string;
onTabChange?: (tabId: string) => void;
}
export const TabView: React.FC<TabBarProps> = ({
tabs,
activeTab,
onTabChange
}) => {
const [active, setActive] = useState(activeTab || tabs[0]?.id || '');
const [ActiveComponent, setActiveComponent] = useState<React.ComponentType<any> | null>(null);
useEffect(() => {
if (activeTab && activeTab !== active) {
setActive(activeTab);
}
}, [activeTab, active]);
useEffect(() => {
const activeTabData = tabs.find(tab => tab.id === active);
if (activeTabData) {
setActiveComponent(() => activeTabData.component);
}
}, [active, tabs]);
const handleTabClick = (tabId: string) => {
setActive(tabId);
onTabChange?.(tabId);
};
return (
<div className="tab-container">
<div className="tab-content">
{ActiveComponent && <ActiveComponent />}
</div>
<div className="tab-bar">
{tabs.map(tab => (
<button
key={tab.id}
className={`tab-bar-item ${active === tab.id ? 'active' : ''}`}
onClick={() => handleTabClick(tab.id)}
aria-selected={active === tab.id}
role="tab"
>
{tab.icon && (
<span className="tab-icon">
{React.isValidElement(tab.icon) ? (
tab.icon
) : (
<FontAwesomeIcon icon={tab.icon as IconDefinition} />
)}
</span>
)}
<span className="tab-label">{tab.label}</span>
</button>
))}
</div>
</div>
);
};
export default TabView;

View File

@ -0,0 +1 @@
export { default as TabView } from './TabView';

View File

@ -0,0 +1,52 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
import {
faCheck,
faTimes,
faSpinner,
faEdit,
faTrash,
faPlus
} from '@fortawesome/free-solid-svg-icons';
import {
faCalendar,
faCircle
} from '@fortawesome/free-regular-svg-icons';
import {
faGithub,
faTwitter
} from '@fortawesome/free-brands-svg-icons';
import { IconName, IconPrefix } from '@fortawesome/fontawesome-svg-core';
// Add icons to library
library.add(
faCheck,
faTimes,
faSpinner,
faEdit,
faTrash,
faPlus,
faCalendar,
faCircle,
faGithub,
faTwitter
);
export { FontAwesomeIcon };
export const iconNames: Record<string, [IconPrefix, IconName]> = {
// Solid
check: ['fas', 'check'],
times: ['fas', 'times'],
spinner: ['fas', 'spinner'],
edit: ['fas', 'edit'],
trash: ['fas', 'trash'],
plus: ['fas', 'plus'],
// Regular
calendar: ['far', 'calendar'],
circle: ['far', 'circle'],
// Brands
github: ['fab', 'github'],
twitter: ['fab', 'twitter']
};

View File

@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import './components/ui/FontAwesomeIcons';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement

View File

@ -0,0 +1,56 @@
import React from 'react';
import { FontAwesomeIcon } from '../../components/ui/FontAwesomeIcons';
const Home: React.FC = () => {
return (
<div className="home-container">
<header className="home-header">
<h1>Welcome to Our Application</h1>
<p>Your centralized dashboard for all features</p>
</header>
<section className="features-section">
<div className="feature-card">
<div className="feature-icon">
<FontAwesomeIcon icon={['fas', 'plus']} />
</div>
<h2>Create New</h2>
<p>Start a new project or task</p>
</div>
<div className="feature-card">
<div className="feature-icon">
<FontAwesomeIcon icon={['far', 'calendar']} />
</div>
<h2>Schedule</h2>
<p>Manage your upcoming events</p>
</div>
<div className="feature-card">
<div className="feature-icon">
<FontAwesomeIcon icon={['fas', 'edit']} />
</div>
<h2>Edit</h2>
<p>Modify your existing content</p>
</div>
</section>
<section className="quick-links">
<h2>Quick Links</h2>
<ul>
<li>
<a href="#dashboard">Dashboard</a>
</li>
<li>
<a href="#profile">Profile Settings</a>
</li>
<li>
<a href="#help">Help & Support</a>
</li>
</ul>
</section>
</div>
);
};
export default Home;

4281
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

9
package.json Normal file
View File

@ -0,0 +1,9 @@
{
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/native": "^7.1.6"
}
}