This commit is contained in:
borrageiros 2025-05-14 21:07:48 +02:00
parent 3aa27efa36
commit 2e59823caa
4 changed files with 528 additions and 316 deletions

View File

@ -85,7 +85,8 @@
"deleteFailed": "Failed to delete drawing.", "deleteFailed": "Failed to delete drawing.",
"loadUsersFailed": "Failed to load users.", "loadUsersFailed": "Failed to load users.",
"userNotSelected": "Please select a user to share with.", "userNotSelected": "Please select a user to share with.",
"shareFailed": "Failed to share drawing." "shareFailed": "Failed to share drawing.",
"unshareFailed": "Failed to unshare drawing."
}, },
"shareModal": { "shareModal": {
"title": "Share Drawing", "title": "Share Drawing",
@ -94,7 +95,11 @@
"cancelButton": "Cancel", "cancelButton": "Cancel",
"noUsersFound": "No users available to share with or match your search.", "noUsersFound": "No users available to share with or match your search.",
"loadingUsers": "Loading users...", "loadingUsers": "Loading users...",
"sharingButton": "Sharing..." "sharingButton": "Sharing...",
"shareTab": "Share",
"manageSharesTab": "Manage Access",
"noSharedUsers": "This drawing is not shared with anyone yet.",
"unsharingButton": "Removing access..."
} }
}, },
"editor": { "editor": {

View File

@ -85,7 +85,8 @@
"deleteFailed": "Error al eliminar el dibujo.", "deleteFailed": "Error al eliminar el dibujo.",
"loadUsersFailed": "Error al cargar usuarios.", "loadUsersFailed": "Error al cargar usuarios.",
"userNotSelected": "Por favor, selecciona un usuario para compartir.", "userNotSelected": "Por favor, selecciona un usuario para compartir.",
"shareFailed": "Error al compartir el dibujo." "shareFailed": "Error al compartir el dibujo.",
"unshareFailed": "Error al dejar de compartir el dibujo."
}, },
"shareModal": { "shareModal": {
"title": "Compartir Dibujo", "title": "Compartir Dibujo",
@ -94,7 +95,11 @@
"cancelButton": "Cancelar", "cancelButton": "Cancelar",
"noUsersFound": "No hay usuarios disponibles para compartir o que coincidan con tu búsqueda.", "noUsersFound": "No hay usuarios disponibles para compartir o que coincidan con tu búsqueda.",
"loadingUsers": "Cargando usuarios...", "loadingUsers": "Cargando usuarios...",
"sharingButton": "Compartiendo..." "sharingButton": "Compartiendo...",
"shareTab": "Compartir",
"manageSharesTab": "Gestionar Accesos",
"noSharedUsers": "Este dibujo no está compartido con nadie todavía.",
"unsharingButton": "Eliminando acceso..."
} }
}, },
"editor": { "editor": {

View File

@ -1,417 +1,488 @@
.cardContainer { .cardContainer {
border: 1px solid #e0e0e0; background-color: var(--card-bg, #ffffff);
border-radius: 8px; border-radius: 8px;
background-color: #ffffff; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 1rem; overflow: hidden;
transition: box-shadow 0.2s ease-in-out, transform 0.2s ease-in-out, background-color 0.3s ease, border-color 0.3s ease; transition: transform 0.2s ease, box-shadow 0.2s ease;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
} }
:global(.dark) .cardContainer { :global(.dark) .cardContainer {
background-color: #1e1e1e; --card-bg: #1e1e1e;
border-color: #333; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
} }
.cardContainer:hover { .cardContainer:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
} }
:global(.dark) .cardContainer:hover { :global(.dark) .cardContainer:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
} }
.titleContainer { .titleContainer {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 0.75rem; padding: 1rem;
border-bottom: 1px solid var(--border-color, #eaeaea);
}
:global(.dark) .titleContainer {
--border-color: #333;
}
.cardTitle {
margin: 0;
font-size: 1.2rem;
font-weight: 600;
color: var(--text-primary, #333);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:global(.dark) .cardTitle {
--text-primary: #e0e0e0;
} }
.titleLink { .titleLink {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
flex-grow: 1; flex: 1;
margin-right: 0.5rem; min-width: 0;
} }
.cardTitle { .cardContent {
font-size: 1.2rem; padding: 1rem;
font-weight: 600; flex: 1;
color: #333; display: flex;
flex-direction: column;
}
.cardDate {
margin: 0; margin: 0;
word-break: break-word;
}
:global(.dark) .cardTitle {
color: #e0e0e0;
}
.editButton {
background: none;
border: none;
padding: 0.25rem;
cursor: pointer;
color: #555;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.editButton:hover {
color: #000;
background-color: #f0f0f0;
}
.actionButtonsContainer {
display: flex;
gap: 0.25rem;
}
.iconButton {
background: none;
border: none;
padding: 0.25rem;
cursor: pointer;
color: #555;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background-color 0.2s ease, color 0.2s ease;
}
:global(.dark) .iconButton {
color: #aaa;
}
.iconButton:hover {
color: #000;
background-color: #f0f0f0;
}
:global(.dark) .iconButton:hover {
color: #fff;
background-color: #333;
}
.deleteButton:hover {
color: #d9534f;
}
:global(.dark) .deleteButton:hover {
color: #ff7875;
}
.inlineEditForm {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.inlineInput {
padding: 0.5rem 0.7rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
flex-grow: 1;
background-color: #fff;
color: #333;
}
:global(.dark) .inlineInput {
background-color: #2a2a2a;
border-color: #444;
color: #e0e0e0;
}
.inlineInput:focus {
outline: none;
border-color: #0070f3;
box-shadow: 0 0 0 2px rgba(0, 112, 243, 0.2);
}
:global(.dark) .inlineInput:focus {
border-color: #0070f3;
box-shadow: 0 0 0 2px rgba(0, 112, 243, 0.3);
}
.formActionButton {
padding: 0.5rem 0.9rem;
border: none;
border-radius: 4px;
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 500; color: var(--text-secondary, #666);
cursor: pointer;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out, transform 0.1s ease;
} }
.formActionButton:active { :global(.dark) .cardDate {
transform: translateY(1px); --text-secondary: #aaa;
}
.confirmButton {
background-color: #28a745;
color: white;
}
.confirmButton:hover {
background-color: #218838;
}
.cancelButton {
background-color: #6c757d;
color: white;
}
.cancelButton:hover {
background-color: #5a6268;
} }
.cardLinkUnderTitle { .cardLinkUnderTitle {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
flex: 1;
display: flex;
flex-direction: column;
} }
.cardContent { .actionButtonsContainer {
margin-top: auto; display: flex;
gap: 0.5rem;
} }
.cardDate { .iconButton {
font-size: 0.8rem; background: none;
color: #777; border: none;
margin-top: 0.5rem; cursor: pointer;
}
:global(.dark) .cardDate {
color: #aaa;
}
.renameErrorText {
color: #d9534f;
font-size: 0.8rem;
margin-bottom: 0.5rem;
}
:global(.dark) .renameErrorText {
color: #ff7875;
}
.thumbnailPlaceholder {
width: 100%;
height: 150px;
background-color: #f0f0f0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: #999; padding: 0.25rem;
border-radius: 4px; border-radius: 4px;
margin-bottom: 12px; color: var(--icon-color, #666);
transition: background-color 0.2s ease, color 0.2s ease;
} }
:global(.dark) .thumbnailPlaceholder { :global(.dark) .iconButton {
background-color: #2a2a2a; --icon-color: #aaa;
color: #aaa;
} }
.cardActions { .iconButton:hover {
margin-top: 16px; background-color: var(--icon-hover-bg, rgba(0, 0, 0, 0.05));
display: flex; color: var(--icon-hover-color, #333);
justify-content: flex-end; }
:global(.dark) .iconButton:hover {
--icon-hover-bg: rgba(255, 255, 255, 0.1);
--icon-hover-color: #fff;
}
.deleteButton:hover {
color: var(--delete-color, #e53935);
}
:global(.dark) .deleteButton:hover {
--delete-color: #f44336;
}
.inlineEditForm {
padding: 0.75rem;
width: 100%;
border-bottom: 1px solid var(--border-color, #eaeaea);
}
:global(.dark) .inlineEditForm {
--border-color: #333;
}
.inlineInput {
width: 100%;
padding: 0.5rem;
border: 1px solid var(--input-border, #ccc);
border-radius: 4px;
font-size: 1rem;
background-color: var(--input-bg, #fff);
color: var(--input-color, #333);
}
:global(.dark) .inlineInput {
--input-border: #555;
--input-bg: #2a2a2a;
--input-color: #e0e0e0;
}
.inlineInput:focus {
outline: none;
border-color: var(--input-focus-border, #0070f3);
box-shadow: 0 0 0 2px var(--input-focus-shadow, rgba(0, 112, 243, 0.2));
}
:global(.dark) .inlineInput:focus {
--input-focus-border: #0070f3;
--input-focus-shadow: rgba(0, 112, 243, 0.3);
}
.renameErrorText {
color: var(--error-color, #ff4d4f);
font-size: 0.85rem;
margin: 0.5rem 0;
text-align: center;
}
:global(.dark) .renameErrorText {
--error-color: #ff7875;
} }
.modalErrorText { .modalErrorText {
color: #d9534f; color: var(--error-color, #ff4d4f);
font-size: 0.9rem; font-size: 0.9rem;
margin: 0.5rem 0; margin: 1rem 0;
padding: 0.5rem; padding: 0.5rem;
background-color: rgba(217, 83, 79, 0.1); background-color: var(--error-bg, rgba(255, 77, 79, 0.1));
border-radius: 4px; border-radius: 4px;
text-align: center;
} }
:global(.dark) .modalErrorText { :global(.dark) .modalErrorText {
color: #ff7875; --error-color: #ff7875;
background-color: rgba(255, 120, 117, 0.15); --error-bg: rgba(255, 77, 79, 0.2);
} }
.modalFooter { .modalFooter {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 0.75rem; gap: 0.75rem;
margin-top: 1rem; margin-top: 1.5rem;
} }
.modalButtonCancel { .modalButtonCancel {
padding: 0.6rem 1.2rem; padding: 0.5rem 1rem;
border-radius: 6px; background-color: var(--cancel-btn-bg, #f5f5f5);
font-weight: 500; color: var(--cancel-btn-color, #333);
border: 1px solid var(--cancel-btn-border, #d9d9d9);
border-radius: 4px;
cursor: pointer; cursor: pointer;
background-color: #f8f9fa; font-size: 0.9rem;
color: #333; transition: all 0.2s;
border: 1px solid #ced4da;
transition: background-color 0.2s ease, border-color 0.2s ease;
} }
:global(.dark) .modalButtonCancel { :global(.dark) .modalButtonCancel {
background-color: #333; --cancel-btn-bg: #333;
color: #e0e0e0; --cancel-btn-color: #e0e0e0;
border-color: #555; --cancel-btn-border: #444;
} }
.modalButtonCancel:hover { .modalButtonCancel:hover {
background-color: #e2e6ea; background-color: var(--cancel-btn-hover-bg, #e6e6e6);
border-color: #dae0e5;
} }
:global(.dark) .modalButtonCancel:hover { :global(.dark) .modalButtonCancel:hover {
background-color: #444; --cancel-btn-hover-bg: #444;
border-color: #666; }
.modalButtonCancel:disabled {
opacity: 0.6;
cursor: not-allowed;
} }
.modalButtonConfirm { .modalButtonConfirm {
padding: 0.6rem 1.2rem; padding: 0.5rem 1rem;
border-radius: 6px; background-color: var(--confirm-btn-bg, #1890ff);
font-weight: 500;
cursor: pointer;
background-color: #d9534f;
color: white; color: white;
border: 1px solid #d9534f; border: none;
transition: background-color 0.2s ease, border-color 0.2s ease; border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.2s;
}
.modalButtonConfirm.delete {
--confirm-btn-bg: #ff4d4f;
} }
.modalButtonConfirm:hover { .modalButtonConfirm:hover {
background-color: #c9302c; background-color: var(--confirm-btn-hover-bg, #40a9ff);
border-color: #ac2925; }
.modalButtonConfirm.delete:hover {
--confirm-btn-hover-bg: #ff7875;
}
:global(.dark) .modalButtonConfirm:hover {
filter: brightness(1.1);
}
.modalButtonConfirm:disabled {
opacity: 0.6;
cursor: not-allowed;
} }
/* Share modal */
.shareModalContent { .shareModalContent {
display: flex; margin-bottom: 1rem;
flex-direction: column;
gap: 1rem;
} }
.shareSearchInput { .shareSearchInput {
padding: 0.75rem;
border: 1px solid #ced4da;
border-radius: 6px;
font-size: 0.95rem;
width: 100%; width: 100%;
transition: border-color 0.2s ease, box-shadow 0.2s ease; padding: 0.75rem;
border: 1px solid var(--input-border, #d9d9d9);
border-radius: 4px;
font-size: 0.9rem;
margin-bottom: 1rem;
background-color: var(--input-bg, #fff);
color: var(--input-color, #333);
}
:global(.dark) .shareSearchInput {
--input-border: #444;
--input-bg: #2a2a2a;
--input-color: #e0e0e0;
} }
.shareSearchInput:focus { .shareSearchInput:focus {
outline: none; outline: none;
border-color: #0070f3; border-color: var(--input-focus-border, #1890ff);
box-shadow: 0 0 0 2px rgba(0, 112, 243, 0.2); box-shadow: 0 0 0 2px var(--input-focus-shadow, rgba(24, 144, 255, 0.2));
}
:global(.dark) .shareSearchInput {
background-color: #2a2a2a;
border-color: #444;
color: #e0e0e0;
} }
:global(.dark) .shareSearchInput:focus { :global(.dark) .shareSearchInput:focus {
border-color: #0070f3; --input-focus-border: #1890ff;
box-shadow: 0 0 0 2px rgba(0, 112, 243, 0.3); --input-focus-shadow: rgba(24, 144, 255, 0.3);
} }
.shareSearchInput:disabled { .shareSearchInput:disabled {
background-color: #f8f9fa; background-color: var(--input-disabled-bg, #f5f5f5);
cursor: not-allowed; cursor: not-allowed;
opacity: 0.7;
} }
:global(.dark) .shareSearchInput:disabled { :global(.dark) .shareSearchInput:disabled {
background-color: #333; --input-disabled-bg: #333;
} }
.userListShare { .userListShare {
list-style: none; list-style: none;
padding: 0; padding: 0;
margin: 0; margin: 0;
max-height: 250px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
border: 1px solid #ced4da; border: 1px solid var(--list-border, #d9d9d9);
border-radius: 6px; border-radius: 4px;
} }
:global(.dark) .userListShare { :global(.dark) .userListShare {
border-color: #444; --list-border: #444;
} }
.userListItemShare { .userListItemShare {
padding: 0.75rem 1rem; padding: 0.75rem;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s ease; border-bottom: 1px solid var(--item-border, #f0f0f0);
border-bottom: 1px solid #eeeeee; background-color: var(--item-bg, #fff);
color: var(--item-color, #333);
transition: background-color 0.2s;
}
:global(.dark) .userListItemShare {
--item-border: #333;
--item-bg: #2a2a2a;
--item-color: #e0e0e0;
} }
.userListItemShare:last-child { .userListItemShare:last-child {
border-bottom: none; border-bottom: none;
} }
:global(.dark) .userListItemShare {
border-bottom-color: #333;
}
.userListItemShare:hover { .userListItemShare:hover {
background-color: #f8f9fa; background-color: var(--item-hover-bg, #f5f5f5);
} }
:global(.dark) .userListItemShare:hover { :global(.dark) .userListItemShare:hover {
background-color: #333; --item-hover-bg: #333;
} }
.selectedUser { .userListItemShare.selectedUser {
background-color: #e8f0fe; background-color: var(--selected-bg, #e6f7ff);
color: var(--selected-color, #1890ff);
}
:global(.dark) .userListItemShare.selectedUser {
--selected-bg: rgba(24, 144, 255, 0.2);
--selected-color: #1890ff;
}
.userListItemShare.disabledItem {
opacity: 0.7;
cursor: not-allowed;
}
.tabsContainer {
display: flex;
margin-bottom: 1rem;
border-bottom: 1px solid var(--tabs-border, #d9d9d9);
}
:global(.dark) .tabsContainer {
--tabs-border: #444;
}
.tabButton {
padding: 0.75rem 1.5rem;
background: none;
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
font-size: 0.95rem;
color: var(--tab-color, #666);
transition: all 0.2s;
}
:global(.dark) .tabButton {
--tab-color: #aaa;
}
.tabButton:hover {
color: var(--tab-hover-color, #1890ff);
}
:global(.dark) .tabButton:hover {
--tab-hover-color: #40a9ff;
}
.tabButton.activeTab {
color: var(--active-tab-color, #1890ff);
border-bottom: 2px solid var(--active-tab-border, #1890ff);
font-weight: 500; font-weight: 500;
} }
.selectedUser:hover { :global(.dark) .tabButton.activeTab {
background-color: #d8e5fd; --active-tab-color: #1890ff;
--active-tab-border: #1890ff;
} }
:global(.dark) .selectedUser { .userListManage {
background-color: #1a3a6c; list-style: none;
padding: 0;
margin: 0;
max-height: 200px;
overflow-y: auto;
border: 1px solid var(--list-border, #d9d9d9);
border-radius: 4px;
} }
:global(.dark) .selectedUser:hover { :global(.dark) .userListManage {
background-color: #254b85; --list-border: #444;
} }
.disabledItem { .userListItemManage {
padding: 0.75rem;
border-bottom: 1px solid var(--item-border, #f0f0f0);
background-color: var(--item-bg, #fff);
color: var(--item-color, #333);
display: flex;
justify-content: space-between;
align-items: center;
}
:global(.dark) .userListItemManage {
--item-border: #333;
--item-bg: #2a2a2a;
--item-color: #e0e0e0;
}
.userListItemManage:last-child {
border-bottom: none;
}
.userInfo {
flex: 1;
}
.unshareButton {
background: none;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0.4rem;
border-radius: 4px;
color: var(--unshare-color, #666);
transition: all 0.2s;
}
:global(.dark) .unshareButton {
--unshare-color: #aaa;
}
.unshareButton:hover {
background-color: var(--unshare-hover-bg, rgba(255, 77, 79, 0.1));
color: var(--unshare-hover-color, #ff4d4f);
}
:global(.dark) .unshareButton:hover {
--unshare-hover-bg: rgba(255, 77, 79, 0.2);
--unshare-hover-color: #ff7875;
}
.unshareButton.disabledButton {
opacity: 0.6; opacity: 0.6;
cursor: not-allowed; cursor: not-allowed;
} }
.disabledItem:hover { @media (max-width: 768px) {
background-color: inherit; .userListShare, .userListManage {
max-height: 150px;
}
.tabButton {
padding: 0.6rem 1rem;
font-size: 0.9rem;
}
} }
:global(.dark) .disabledItem:hover { @media (max-width: 480px) {
background-color: inherit; .userListItemManage {
} padding: 0.6rem;
font-size: 0.9rem;
/* Estilo para botones deshabilitados */ }
.modalButtonConfirm:disabled,
.modalButtonCancel:disabled { .tabButton {
opacity: 0.6; padding: 0.5rem 0.75rem;
cursor: not-allowed; font-size: 0.85rem;
} }
.modalButtonConfirm:disabled:hover,
.modalButtonCancel:disabled:hover {
background-color: inherit;
border-color: inherit;
}
:global(.dark) .modalButtonConfirm:disabled:hover,
:global(.dark) .modalButtonCancel:disabled:hover {
background-color: inherit;
border-color: inherit;
} }

View File

@ -8,6 +8,7 @@ import {
deleteDrawing as apiDeleteDrawing, deleteDrawing as apiDeleteDrawing,
getUsers as apiGetUsers, getUsers as apiGetUsers,
shareDrawingWithUser as apiShareDrawing, shareDrawingWithUser as apiShareDrawing,
unshareDrawingWithUser as apiUnshareDrawing,
} from '@/lib/api'; } from '@/lib/api';
import { IUser } from '@/models/User'; import { IUser } from '@/models/User';
import styles from './DrawingCard.module.css'; import styles from './DrawingCard.module.css';
@ -42,11 +43,14 @@ export default function DrawingCard({
// States for the share modal // States for the share modal
const [showShareModal, setShowShareModal] = useState(false); const [showShareModal, setShowShareModal] = useState(false);
const [usersForSharing, setUsersForSharing] = useState<IUser[]>([]); const [usersForSharing, setUsersForSharing] = useState<IUser[]>([]);
const [sharedWithUsers, setSharedWithUsers] = useState<IUser[]>([]);
const [shareSearchTerm, setShareSearchTerm] = useState(''); const [shareSearchTerm, setShareSearchTerm] = useState('');
const [selectedUserIdToShare, setSelectedUserIdToShare] = useState<string | null>(null); const [selectedUserIdToShare, setSelectedUserIdToShare] = useState<string | null>(null);
const [shareError, setShareError] = useState<string | null>(null); const [shareError, setShareError] = useState<string | null>(null);
const [isLoadingUsers, setIsLoadingUsers] = useState(false); const [isLoadingUsers, setIsLoadingUsers] = useState(false);
const [isSharing, setIsSharing] = useState(false); const [isSharing, setIsSharing] = useState(false);
const [isUnsharing, setIsUnsharing] = useState(false);
const [activeTab, setActiveTab] = useState<'share' | 'manage'>('share');
const handleCancelEdit = useCallback(() => { const handleCancelEdit = useCallback(() => {
setIsEditing(false); setIsEditing(false);
@ -164,19 +168,37 @@ export default function DrawingCard({
setShareError(null); setShareError(null);
setSelectedUserIdToShare(null); setSelectedUserIdToShare(null);
setShareSearchTerm(''); setShareSearchTerm('');
if (usersForSharing.length === 0) { // Load users only if they haven't been loaded before setActiveTab('share');
setIsLoadingUsers(true); setIsLoadingUsers(true);
try {
const response = await apiGetUsers(); const response = await apiGetUsers();
setIsLoadingUsers(false);
if (response.data) { if (response.data) {
// Filter out the owner and already shared users // Get all users
const filteredUsers = response.data.filter( const allUsers = response.data;
(user) => user._id !== drawing.owner_id && !drawing.shared_with.includes(user._id as string)
// Filter users that already have access
const sharedWithUserIds = drawing.shared_with || [];
const usersWithAccess = allUsers.filter(user =>
sharedWithUserIds.includes(user._id as string)
); );
setUsersForSharing(filteredUsers);
// Filter users that don't have access yet (excluding the owner)
const usersWithoutAccess = allUsers.filter(user =>
user._id !== drawing.owner_id &&
!sharedWithUserIds.includes(user._id as string)
);
setSharedWithUsers(usersWithAccess);
setUsersForSharing(usersWithoutAccess);
} else { } else {
setShareError(response.error || t('drawingCard.errors.loadUsersFailed') || "Failed to load users."); setShareError(response.error || t('drawingCard.errors.loadUsersFailed') || "Failed to load users.");
} }
} catch (err) {
setShareError(t('drawingCard.errors.loadUsersFailed') || "Failed to load users.");
console.error("Load users error:", err);
} finally {
setIsLoadingUsers(false);
} }
}; };
@ -194,17 +216,61 @@ export default function DrawingCard({
} }
setIsSharing(true); setIsSharing(true);
setShareError(null); setShareError(null);
const response = await apiShareDrawing(drawing._id, selectedUserIdToShare);
setIsSharing(false); try {
if (response.data) { const response = await apiShareDrawing(drawing._id, selectedUserIdToShare);
setShowShareModal(false); if (response.data) {
if (onDrawingShared) { // Find the user that was shared with
onDrawingShared(response.data); const sharedUser = usersForSharing.find(user => user._id === selectedUserIdToShare);
if (sharedUser) {
// Add to shared users list
setSharedWithUsers(prev => [...prev, sharedUser]);
// Remove from available users list
setUsersForSharing(prev => prev.filter(user => user._id !== selectedUserIdToShare));
}
if (onDrawingShared) {
onDrawingShared(response.data);
}
setSelectedUserIdToShare(null);
} else {
setShareError(response.error || t('drawingCard.errors.shareFailed') || "Failed to share drawing.");
} }
setUsersForSharing(prevUsers => prevUsers.filter(u => u._id !== selectedUserIdToShare)); } catch (err) {
setSelectedUserIdToShare(null); setShareError(t('drawingCard.errors.shareFailed') || "Failed to share drawing.");
} else { console.error("Share error:", err);
setShareError(response.error || t('drawingCard.errors.shareFailed') || "Failed to share drawing."); } finally {
setIsSharing(false);
}
};
const handleUnshareDrawing = async (userId: string) => {
setIsUnsharing(true);
setShareError(null);
try {
const response = await apiUnshareDrawing(drawing._id, userId);
if (response.data) {
// Find the user that was unshared
const unsharedUser = sharedWithUsers.find(user => user._id === userId);
if (unsharedUser) {
// Remove from shared users list
setSharedWithUsers(prev => prev.filter(user => user._id !== userId));
// Add to available users list
setUsersForSharing(prev => [...prev, unsharedUser]);
}
if (onDrawingShared) {
onDrawingShared(response.data);
}
} else {
setShareError(response.error || t('drawingCard.errors.unshareFailed') || "Failed to unshare drawing.");
}
} catch (err) {
setShareError(t('drawingCard.errors.unshareFailed') || "Failed to unshare drawing.");
console.error("Unshare error:", err);
} finally {
setIsUnsharing(false);
} }
}; };
@ -213,6 +279,11 @@ export default function DrawingCard({
user.email.toLowerCase().includes(shareSearchTerm.toLowerCase()) user.email.toLowerCase().includes(shareSearchTerm.toLowerCase())
); );
const filteredSharedWithUsers = sharedWithUsers.filter((user) =>
user.username.toLowerCase().includes(shareSearchTerm.toLowerCase()) ||
user.email.toLowerCase().includes(shareSearchTerm.toLowerCase())
);
const modalTitle = i18nCardIsLoading const modalTitle = i18nCardIsLoading
? "Confirm Deletion" ? "Confirm Deletion"
: t('drawingCard.confirmDelete.title') || "Confirm Deletion"; : t('drawingCard.confirmDelete.title') || "Confirm Deletion";
@ -251,6 +322,15 @@ export default function DrawingCard({
const shareModalNoUsersFoundText = i18nCardIsLoading const shareModalNoUsersFoundText = i18nCardIsLoading
? "No users available to share with or match your search." ? "No users available to share with or match your search."
: t('drawingCard.shareModal.noUsersFound') || "No users available to share with or match your search."; : t('drawingCard.shareModal.noUsersFound') || "No users available to share with or match your search.";
const manageSharesTabText = i18nCardIsLoading
? "Manage Shares"
: t('drawingCard.shareModal.manageSharesTab') || "Manage Shares";
const shareTabText = i18nCardIsLoading
? "Share"
: t('drawingCard.shareModal.shareTab') || "Share";
const noSharedUsersText = i18nCardIsLoading
? "This drawing is not shared with anyone yet."
: t('drawingCard.shareModal.noSharedUsers') || "This drawing is not shared with anyone yet.";
return ( return (
<> <>
@ -322,50 +402,101 @@ export default function DrawingCard({
title={shareModalTitle} title={shareModalTitle}
> >
<div className={styles.shareModalContent}> <div className={styles.shareModalContent}>
<div className={styles.tabsContainer}>
<button
className={`${styles.tabButton} ${activeTab === 'share' ? styles.activeTab : ''}`}
onClick={() => setActiveTab('share')}
>
{shareTabText}
</button>
<button
className={`${styles.tabButton} ${activeTab === 'manage' ? styles.activeTab : ''}`}
onClick={() => setActiveTab('manage')}
>
{manageSharesTabText}
</button>
</div>
<input <input
type="text" type="text"
placeholder={shareModalSearchPlaceholder} placeholder={shareModalSearchPlaceholder}
value={shareSearchTerm} value={shareSearchTerm}
onChange={(e) => setShareSearchTerm(e.target.value)} onChange={(e) => setShareSearchTerm(e.target.value)}
className={styles.shareSearchInput} className={styles.shareSearchInput}
disabled={isLoadingUsers || isSharing} disabled={isLoadingUsers || isSharing || isUnsharing}
/> />
{isLoadingUsers && <p>{t('drawingCard.shareModal.loadingUsers') || 'Loading users...'}</p>} {isLoadingUsers && <p>{t('drawingCard.shareModal.loadingUsers') || 'Loading users...'}</p>}
{shareError && <p className={styles.modalErrorText}>{shareError}</p>} {shareError && <p className={styles.modalErrorText}>{shareError}</p>}
{!isLoadingUsers && filteredUsersForSharing.length === 0 && ( {activeTab === 'share' && !isLoadingUsers && (
<p>{shareModalNoUsersFoundText}</p> <>
)} {filteredUsersForSharing.length === 0 && (
<p>{shareModalNoUsersFoundText}</p>
)}
{!isLoadingUsers && filteredUsersForSharing.length > 0 && ( {filteredUsersForSharing.length > 0 && (
<ul className={styles.userListShare}> <ul className={styles.userListShare}>
{filteredUsersForSharing.map((user) => ( {filteredUsersForSharing.map((user) => (
<li <li
key={user._id as string} key={user._id as string}
onClick={() => !isSharing && setSelectedUserIdToShare(user._id as string)} onClick={() => !isSharing && setSelectedUserIdToShare(user._id as string)}
className={`${styles.userListItemShare} ${selectedUserIdToShare === user._id ? styles.selectedUser : ''} ${isSharing ? styles.disabledItem : ''}`} className={`${styles.userListItemShare} ${selectedUserIdToShare === user._id ? styles.selectedUser : ''} ${isSharing ? styles.disabledItem : ''}`}
> >
{user.username} ({user.email}) {user.username} ({user.email})
</li> </li>
))} ))}
</ul> </ul>
)}
</>
)}
{activeTab === 'manage' && !isLoadingUsers && (
<>
{filteredSharedWithUsers.length === 0 && (
<p>{noSharedUsersText}</p>
)}
{filteredSharedWithUsers.length > 0 && (
<ul className={styles.userListManage}>
{filteredSharedWithUsers.map((user) => (
<li key={user._id as string} className={styles.userListItemManage}>
<span className={styles.userInfo}>
{user.username} ({user.email})
</span>
<button
onClick={() => !isUnsharing && handleUnshareDrawing(user._id as string)}
className={`${styles.unshareButton} ${isUnsharing ? styles.disabledButton : ''}`}
disabled={isUnsharing}
>
<Icon name="user-x" size={16} />
</button>
</li>
))}
</ul>
)}
</>
)} )}
</div> </div>
<div className={styles.modalFooter}> <div className={styles.modalFooter}>
<button <button
onClick={handleCloseShareModal} onClick={handleCloseShareModal}
className={styles.modalButtonCancel} className={styles.modalButtonCancel}
disabled={isSharing} disabled={isSharing || isUnsharing}
> >
{shareModalCancelButtonText} {shareModalCancelButtonText}
</button> </button>
<button
onClick={handleConfirmShare} {activeTab === 'share' && (
className={styles.modalButtonConfirm} <button
disabled={!selectedUserIdToShare || isLoadingUsers || isSharing} onClick={handleConfirmShare}
> className={styles.modalButtonConfirm}
{isSharing ? (t('drawingCard.shareModal.sharingButton') || 'Sharing...') : shareModalConfirmButtonText} disabled={!selectedUserIdToShare || isLoadingUsers || isSharing}
</button> >
{isSharing ? (t('drawingCard.shareModal.sharingButton') || 'Sharing...') : shareModalConfirmButtonText}
</button>
)}
</div> </div>
</Modal> </Modal>
</> </>