Minor UI Edits
This commit is contained in:
parent
315746852b
commit
0f671fa1b1
|
@ -40,3 +40,4 @@
|
|||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,13 +21,14 @@ export const FilesComponent: React.FC<FilesComponentProps> = ({ isDarkMode }) =>
|
|||
const [success, setSuccess] = useState<string | null>(null);
|
||||
const [openDropdown, setOpenDropdown] = useState<number | null>(null);
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
const [shareDuration, setShareDuration] = useState(7); // durata di default 7 giorni
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const dropdownRef = useRef<HTMLTableDataCellElement | null>(null);
|
||||
|
||||
// Recupera il token solo una volta al mount o quando cambia
|
||||
useEffect(() => {
|
||||
const storedToken = localStorage.getItem('token');
|
||||
const storedToken = localStorage.getItem('litecloud_token'); // <-- usa la chiave corretta
|
||||
setToken(storedToken);
|
||||
}, []);
|
||||
|
||||
|
@ -54,7 +55,7 @@ export const FilesComponent: React.FC<FilesComponentProps> = ({ isDarkMode }) =>
|
|||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const response = await apiCall('http://192.168.1.120:8082/api/files');
|
||||
const response = await apiCall('/api/files');
|
||||
const filesData = await response.json();
|
||||
setFiles(filesData);
|
||||
} catch (err) {
|
||||
|
@ -90,8 +91,8 @@ export const FilesComponent: React.FC<FilesComponentProps> = ({ isDarkMode }) =>
|
|||
formData.append('file', file);
|
||||
formData.append('filename', file.name);
|
||||
|
||||
const token = getAuthToken();
|
||||
const response = await fetch('http://192.168.1.120:8082/api/upload', {
|
||||
if (!token) throw new Error('No auth token found');
|
||||
const response = await fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
|
@ -106,7 +107,7 @@ export const FilesComponent: React.FC<FilesComponentProps> = ({ isDarkMode }) =>
|
|||
throw new Error('Upload failed');
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to upload file');
|
||||
setError('Failed to upload file - ' + (err instanceof Error ? err.message : 'Unknown error'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (fileInputRef.current) {
|
||||
|
@ -115,24 +116,26 @@ export const FilesComponent: React.FC<FilesComponentProps> = ({ isDarkMode }) =>
|
|||
}
|
||||
};
|
||||
|
||||
const handleFileAction = async (action: 'share' | 'delete', fileId: number) => {
|
||||
const handleFileAction = async (
|
||||
action: 'share' | 'delete',
|
||||
fileId: number,
|
||||
duration?: number
|
||||
) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
if (action === 'share') {
|
||||
const response = await apiCall('http://192.168.1.120:8082/api/share', {
|
||||
const response = await apiCall('/api/share', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
file_id: fileId,
|
||||
expires_in_days: 7
|
||||
expires_in_days: duration || 7
|
||||
}),
|
||||
});
|
||||
const shareId = await response.json();
|
||||
setSuccess(`Share link created: /shared/${shareId}`);
|
||||
|
||||
} else if (action === 'delete') {
|
||||
await apiCall(`http://192.168.1.120:8082/api/files/${fileId}`, {
|
||||
await apiCall(`/api/files/${fileId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
setSuccess('File deleted successfully');
|
||||
|
@ -146,9 +149,28 @@ export const FilesComponent: React.FC<FilesComponentProps> = ({ isDarkMode }) =>
|
|||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleString();
|
||||
};
|
||||
const formatDate = (dateString: string) => {
|
||||
const now = new Date();
|
||||
const date = new Date(dateString);
|
||||
const diff = (now.getTime() - date.getTime()) / 1000;
|
||||
|
||||
const isSameDay = (d1: Date, d2: Date) =>
|
||||
d1.getFullYear() === d2.getFullYear() &&
|
||||
d1.getMonth() === d2.getMonth() &&
|
||||
d1.getDate() === d2.getDate();
|
||||
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(now.getDate() - 1);
|
||||
|
||||
if (diff < 10) return "now";
|
||||
|
||||
if (isSameDay(date, yesterday)) {
|
||||
return `yesterday at ${date.toLocaleTimeString()}`;
|
||||
}
|
||||
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
|
@ -193,7 +215,6 @@ export const FilesComponent: React.FC<FilesComponentProps> = ({ isDarkMode }) =>
|
|||
}`}
|
||||
>
|
||||
<Upload size={18} />
|
||||
Upload File
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -204,44 +225,47 @@ export const FilesComponent: React.FC<FilesComponentProps> = ({ isDarkMode }) =>
|
|||
)}
|
||||
|
||||
<div className={`table-container ${isDarkMode ? 'table-container-dark' : 'table-container-light'}`}>
|
||||
<table className="data-table">
|
||||
<table className="data-table full-width-table">
|
||||
<thead className={isDarkMode ? 'table-header-dark' : 'table-header-light'}>
|
||||
<tr>
|
||||
<th className="table-header-cell">Name</th>
|
||||
<th className="table-header-cell">Size</th>
|
||||
<th className="table-header-cell">Uploaded</th>
|
||||
<th className="table-header-cell">Actions</th>
|
||||
<th className="table-header-cell actions-header-cell"></th> {/* Nessuna scritta */}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className={`table-body ${isDarkMode ? 'table-body-dark' : 'table-body-light'}`}>
|
||||
<tbody className={`table-body ${isDarkMode ? 'table-body-dark' : 'table-body-light'}`}>
|
||||
{files.map((file) => (
|
||||
<tr key={file.id} className={isDarkMode ? 'table-row-dark' : 'table-row-light'}>
|
||||
<td className={`table-cell ${isDarkMode ? 'text-white' : 'text-gray-900'}`}>
|
||||
{file.original_name}
|
||||
</td>
|
||||
<td className={`table-cell ${isDarkMode ? 'text-gray-300' : 'text-gray-500'}`}>
|
||||
{formatFileSize(file.size)}
|
||||
</td>
|
||||
<td className={`table-cell ${isDarkMode ? 'text-gray-300' : 'text-gray-500'}`}>
|
||||
{formatDate(file.uploaded_at)}
|
||||
</td>
|
||||
<td className="table-cell relative" ref={dropdownRef}>
|
||||
<td className={`table-cell ${isDarkMode ? 'text-white' : 'text-gray-900'}`}>{file.original_name}</td>
|
||||
<td className={`table-cell ${isDarkMode ? 'text-gray-300' : 'text-gray-500'}`}>{formatFileSize(file.size)}</td>
|
||||
<td className={`table-cell ${isDarkMode ? 'text-gray-300' : 'text-gray-500'}`}>{formatDate(file.uploaded_at)}</td>
|
||||
<td className="table-cell relative actions-cell" ref={dropdownRef}>
|
||||
<button
|
||||
onClick={() => setOpenDropdown(openDropdown === file.id ? null : file.id)}
|
||||
className={`actions-button ${
|
||||
isDarkMode ? 'actions-button-dark' : 'actions-button-light'
|
||||
}`}
|
||||
className={`actions-button ellipsis-button ${isDarkMode ? 'actions-button-dark' : 'actions-button-light'}`}
|
||||
aria-label="Actions"
|
||||
>
|
||||
Actions
|
||||
<ChevronDown size={16} />
|
||||
<span className="ellipsis-dot"></span>
|
||||
<span className="ellipsis-dot"></span>
|
||||
<span className="ellipsis-dot"></span>
|
||||
</button>
|
||||
|
||||
{openDropdown === file.id && (
|
||||
<div className={`dropdown-menu ${
|
||||
isDarkMode ? 'dropdown-menu-dark' : 'dropdown-menu-light'
|
||||
}`}>
|
||||
<div className={`dropdown-menu ${isDarkMode ? 'dropdown-menu-dark' : 'dropdown-menu-light'} custom-dropdown`}>
|
||||
<div className="dropdown-section">
|
||||
<label className="dropdown-label">Share duration:</label>
|
||||
<select
|
||||
className="dropdown-select"
|
||||
value={shareDuration}
|
||||
onChange={e => setShareDuration(Number(e.target.value))}
|
||||
>
|
||||
<option value={1}>1 day</option>
|
||||
<option value={3}>3 days</option>
|
||||
<option value={7}>7 days</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleFileAction('share', file.id)}
|
||||
onClick={() => handleFileAction('share', file.id, shareDuration)}
|
||||
className={`dropdown-item ${
|
||||
isDarkMode ? 'dropdown-item-dark' : 'dropdown-item-light'
|
||||
}`}
|
||||
|
|
|
@ -22,7 +22,7 @@ export const Login: React.FC<LoginProps> = ({
|
|||
setError(null);
|
||||
|
||||
try {
|
||||
const res = await fetch("http://192.168.1.120:8082/api/login", {
|
||||
const res = await fetch("/api/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username, password }),
|
||||
|
|
|
@ -32,6 +32,7 @@ export const MainPage: React.FC<MainPageProps> = ({
|
|||
};
|
||||
|
||||
const renderActiveView = () => {
|
||||
console.log('isDarkMode:', isDarkMode);
|
||||
switch (activeView) {
|
||||
case 'files':
|
||||
return <FilesComponent isDarkMode={isDarkMode} />;
|
||||
|
|
|
@ -22,7 +22,7 @@ export const Register: React.FC<RegisterProps> = ({
|
|||
setSuccess(false);
|
||||
|
||||
try {
|
||||
const res = await fetch("http://localhost:8000/api/register", {
|
||||
const res = await fetch("/api/register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username, password }),
|
||||
|
|
|
@ -21,7 +21,7 @@ export const SharesComponent: React.FC<SharesComponentProps> = ({ isDarkMode })
|
|||
const [success, setSuccess] = useState<string | null>(null);
|
||||
|
||||
const getAuthToken = () => {
|
||||
return localStorage.getItem('authToken') || '';
|
||||
return localStorage.getItem('litecloud_token') || '';
|
||||
};
|
||||
|
||||
const apiCall = async (url: string, options: RequestInit = {}) => {
|
||||
|
|
|
@ -633,6 +633,103 @@ body, html, #root {
|
|||
background: rgba(254, 226, 226, 1);
|
||||
}
|
||||
|
||||
/* Dropdown menu custom style for FileComponent */
|
||||
.custom-dropdown {
|
||||
background: rgba(40, 40, 60, 0.85);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.25);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
padding: 0.5rem 0.75rem;
|
||||
min-width: 160px;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 110%;
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown-menu-light.custom-dropdown {
|
||||
background: rgba(255,255,255,0.95);
|
||||
color: #222;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.dropdown-item-dark {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dropdown-item-light {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background: rgba(147, 51, 234, 0.08);
|
||||
}
|
||||
|
||||
.dropdown-item-danger {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.dropdown-item-danger:hover {
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
}
|
||||
|
||||
.dropdown-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.dropdown-label {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown-select {
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #d1d5db;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
background: #fff;
|
||||
color: #222;
|
||||
outline: none;
|
||||
transition: border 0.2s;
|
||||
}
|
||||
|
||||
.dropdown-menu-dark .dropdown-select {
|
||||
background: #23233a;
|
||||
color: #fff;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
.dropdown-menu-dark .dropdown-label {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dropdown-menu-light .dropdown-label {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
/* Upload Button */
|
||||
.upload-button {
|
||||
position: fixed;
|
||||
|
@ -654,6 +751,22 @@ body, html, #root {
|
|||
transition: all 0.2s ease;
|
||||
z-index: 30;
|
||||
}
|
||||
.sidebar-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.sidebar-dark .sidebar-title {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sidebar-light .sidebar-title {
|
||||
color: #1f2937; /* gray-800 */
|
||||
}
|
||||
|
||||
.upload-button:hover {
|
||||
transform: scale(1.1);
|
||||
|
@ -696,6 +809,54 @@ body, html, #root {
|
|||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Ellipsis button (3 dots) style */
|
||||
.ellipsis-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
padding: 0;
|
||||
}
|
||||
.ellipsis-button:hover {
|
||||
background: rgba(147, 51, 234, 0.08);
|
||||
}
|
||||
.ellipsis-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #bdbdbd;
|
||||
border-radius: 50%;
|
||||
margin: 0 1.5px;
|
||||
display: inline-block;
|
||||
}
|
||||
.actions-button-dark .ellipsis-dot {
|
||||
background: #fff;
|
||||
}
|
||||
.actions-button-light .ellipsis-dot {
|
||||
background: #222;
|
||||
}
|
||||
|
||||
/* Migliora la larghezza delle colonne e padding */
|
||||
.full-width-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
.table-header-cell, .table-cell {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
.actions-header-cell {
|
||||
width: 48px;
|
||||
}
|
||||
.actions-cell {
|
||||
text-align: right;
|
||||
min-width: 48px;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 1024px) {
|
||||
.app-layout {
|
||||
|
|
Loading…
Reference in a new issue