inital commit
This commit is contained in:
parent
33eb86d6ed
commit
b870378fae
9
.cursor/rules/react.mdc
Normal file
9
.cursor/rules/react.mdc
Normal 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
|
||||
@ -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
3
.gitignore
vendored
@ -1 +1,2 @@
|
||||
var
|
||||
var
|
||||
node_modules
|
||||
|
||||
@ -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;'
|
||||
75
frontend/package-lock.json
generated
75
frontend/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
72
frontend/src/components/navigation/TabView.css
Normal file
72
frontend/src/components/navigation/TabView.css
Normal 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;
|
||||
}
|
||||
74
frontend/src/components/navigation/TabView.tsx
Normal file
74
frontend/src/components/navigation/TabView.tsx
Normal 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;
|
||||
1
frontend/src/components/navigation/index.ts
Normal file
1
frontend/src/components/navigation/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as TabView } from './TabView';
|
||||
52
frontend/src/components/ui/FontAwesomeIcons.tsx
Normal file
52
frontend/src/components/ui/FontAwesomeIcons.tsx
Normal 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']
|
||||
};
|
||||
@ -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
|
||||
|
||||
56
frontend/src/pages/home/Home.tsx
Normal file
56
frontend/src/pages/home/Home.tsx
Normal 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
4281
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
package.json
Normal file
9
package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user