Add button from react-cons
This commit is contained in:
parent
7ba3dd6819
commit
eb9ac54e0c
11
frontend/package-lock.json
generated
11
frontend/package-lock.json
generated
@ -10,7 +10,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-dom": "^19.1.0"
|
"react-dom": "^19.1.0",
|
||||||
|
"react-icons": "^5.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.25.0",
|
"@eslint/js": "^9.25.0",
|
||||||
@ -2393,6 +2394,14 @@
|
|||||||
"react": "^19.1.0"
|
"react": "^19.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-icons": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "17.0.2",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
|
|||||||
@ -12,7 +12,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
"react-dom": "^19.1.0"
|
"react-dom": "^19.1.0",
|
||||||
|
"react-icons": "^5.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.25.0",
|
"@eslint/js": "^9.25.0",
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import SectionGrid from './components/SectionGrid';
|
import SectionGrid from './components/SectionGrid';
|
||||||
import ControlPanel from './components/ControlPanel';
|
import AddAppModal from './components/AddAppModal';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [sections, setSections] = useState([]);
|
const [sections, setSections] = useState([]);
|
||||||
@ -20,7 +20,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<h1 className="main-title">🔷 Navix</h1>
|
<h1 className="main-title">🔷 Navix</h1>
|
||||||
<ControlPanel onUpdate={fetchSections} />
|
<AddAppModal onAdded={() => window.location.reload()} />
|
||||||
{sections.map((section) => (
|
{sections.map((section) => (
|
||||||
<SectionGrid key={section.name} section={section} />
|
<SectionGrid key={section.name} section={section} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
66
frontend/src/components/AddAppModal.jsx
Normal file
66
frontend/src/components/AddAppModal.jsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { addAppToSection } from '../services/api';
|
||||||
|
import '../style/AddAppModal.css';
|
||||||
|
import { IoIosAddCircleOutline } from "react-icons/io";
|
||||||
|
|
||||||
|
function AddAppModal({ onAdded }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [spin, setSpin] = useState(false);
|
||||||
|
|
||||||
|
const [section, setSection] = useState('');
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [icon, setIcon] = useState('');
|
||||||
|
const [description, setDescription] = useState('');
|
||||||
|
const [url, setUrl] = useState('');
|
||||||
|
|
||||||
|
const handleOpen = () => {
|
||||||
|
setSpin(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setSpin(false);
|
||||||
|
setOpen(true);
|
||||||
|
}, 500); // match spin animation duration
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
await addAppToSection({ section, app: { name, icon, description, url } });
|
||||||
|
setOpen(false);
|
||||||
|
setSection('');
|
||||||
|
setName('');
|
||||||
|
setIcon('');
|
||||||
|
setDescription('');
|
||||||
|
setUrl('');
|
||||||
|
if (onAdded) onAdded();
|
||||||
|
} catch (err) {
|
||||||
|
alert('Failed to add app');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IoIosAddCircleOutline
|
||||||
|
className={`add-button ${spin ? 'spin' : ''}`}
|
||||||
|
onClick={handleOpen}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{open && (
|
||||||
|
<div className="modal-overlay" onClick={() => setOpen(false)}>
|
||||||
|
<div className="modal" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<h2>Add New App</h2>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<input type="text" placeholder="Section" value={section} onChange={e => setSection(e.target.value)} required />
|
||||||
|
<input type="text" placeholder="App name" value={name} onChange={e => setName(e.target.value)} required />
|
||||||
|
<input type="text" placeholder="Icon URL" value={icon} onChange={e => setIcon(e.target.value)} />
|
||||||
|
<input type="text" placeholder="Description" value={description} onChange={e => setDescription(e.target.value)} />
|
||||||
|
<input type="text" placeholder="App URL" value={url} onChange={e => setUrl(e.target.value)} required />
|
||||||
|
<button type="submit">Add</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddAppModal;
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { addAppToSection } from '../services/api';
|
|
||||||
import '../style/ControlPanel.css';
|
|
||||||
|
|
||||||
function ControlPanel({ onUpdate }) {
|
|
||||||
const [section, setSection] = useState('');
|
|
||||||
const [name, setName] = useState('');
|
|
||||||
const [icon, setIcon] = useState('');
|
|
||||||
const [description, setDescription] = useState('');
|
|
||||||
const [url, setUrl] = useState('');
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!section || !name || !url) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await addAppToSection({
|
|
||||||
section,
|
|
||||||
app: { name, icon, description, url }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (onUpdate) onUpdate();
|
|
||||||
setSection('');
|
|
||||||
setName('');
|
|
||||||
setIcon('');
|
|
||||||
setDescription('');
|
|
||||||
setUrl('');
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to add app:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form className="control-panel" onSubmit={handleSubmit}>
|
|
||||||
<input type="text" placeholder="Section name" value={section} onChange={(e) => setSection(e.target.value)} />
|
|
||||||
<input type="text" placeholder="App name" value={name} onChange={(e) => setName(e.target.value)} />
|
|
||||||
<input type="text" placeholder="Icon URL" value={icon} onChange={(e) => setIcon(e.target.value)} />
|
|
||||||
<input type="text" placeholder="Description" value={description} onChange={(e) => setDescription(e.target.value)} />
|
|
||||||
<input type="text" placeholder="App URL" value={url} onChange={(e) => setUrl(e.target.value)} />
|
|
||||||
<button type="submit">Add App</button>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ControlPanel;
|
|
||||||
84
frontend/src/style/AddAppModal.css
Normal file
84
frontend/src/style/AddAppModal.css
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
.add-button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
font-size: 2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 999;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background: #1e1e1e;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 340px;
|
||||||
|
height: 420px;
|
||||||
|
box-shadow: 0 0 10px #000;
|
||||||
|
|
||||||
|
/* Flexbox centering */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal input {
|
||||||
|
width: 90%;
|
||||||
|
height: 35px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal button {
|
||||||
|
width: 90%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-button.spin {
|
||||||
|
animation: spin 0.5s linear;
|
||||||
|
}
|
||||||
@ -1,27 +0,0 @@
|
|||||||
.control-panel {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
background-color: #1e1e1e;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-panel input,
|
|
||||||
.control-panel button {
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-panel button {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-panel button:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user