Add button from react-cons

This commit is contained in:
dvirlabs 2025-06-03 07:45:42 +03:00
parent 7ba3dd6819
commit eb9ac54e0c
7 changed files with 164 additions and 76 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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} />
))} ))}

View 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;

View File

@ -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;

View 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;
}

View File

@ -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;
}