Minor UI Edits

This commit is contained in:
Mercurio 2025-06-05 23:53:43 +02:00
parent 315746852b
commit 0f671fa1b1
7 changed files with 228 additions and 41 deletions

View file

@ -40,3 +40,4 @@
.read-the-docs {
color: #888;
}

View file

@ -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'
}`}

View file

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

View file

@ -32,6 +32,7 @@ export const MainPage: React.FC<MainPageProps> = ({
};
const renderActiveView = () => {
console.log('isDarkMode:', isDarkMode);
switch (activeView) {
case 'files':
return <FilesComponent isDarkMode={isDarkMode} />;

View file

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

View file

@ -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 = {}) => {

View file

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