Skip to content

Commit

Permalink
General UI and UX improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
jordan-dalby committed Nov 21, 2024
1 parent 5f71b63 commit c61dd09
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 89 deletions.
92 changes: 65 additions & 27 deletions client/src/components/auth/UserDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,82 @@
import React, { useRef, useState } from 'react';
import { LogOut, User } from 'lucide-react';
import { LogOut, User, Globe } from 'lucide-react';
import { useAuth } from '../../hooks/useAuth';
import { useOutsideClick } from '../../hooks/useOutsideClick';
import { Link, useLocation } from 'react-router-dom';

export const UserDropdown: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const { user, logout } = useAuth();
const location = useLocation();

useOutsideClick(dropdownRef, () => setIsOpen(false));

const isPublicView = location.pathname.startsWith('/public');
const switchViewProps = isPublicView
? {
to: "/",
icon: <User size={16} />,
text: "My snippets"
}
: {
to: "/public/snippets",
icon: <Globe size={16} />,
text: "Public snippets"
};

if (user) {
return (
<div ref={dropdownRef} className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 px-3 py-1.5 bg-gray-800 hover:bg-gray-700
rounded-md transition-colors text-sm"
>
<User size={16} />
<span>{user?.username}</span>
</button>

{isOpen && (
<div
className="absolute right-0 mt-1 w-48 bg-gray-800 rounded-md shadow-lg
border border-gray-700 py-1 z-50"
>
<Link
to={switchViewProps.to}
onClick={() => setIsOpen(false)}
className="w-full px-4 py-2 text-sm text-left text-gray-300 hover:bg-gray-700
flex items-center gap-2"
>
{switchViewProps.icon}
<span>{switchViewProps.text}</span>
</Link>
<button
onClick={() => {
setIsOpen(false);
logout();
}}
className="w-full px-4 py-2 text-sm text-left text-gray-300 hover:bg-gray-700
flex items-center gap-2"
>
<LogOut size={16} />
<span>Sign out</span>
</button>
</div>
)}
</div>
);
}

return (
<div ref={dropdownRef} className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 px-3 py-1.5 bg-gray-800 hover:bg-gray-700
rounded-md transition-colors text-sm"
<Link
to="/login"
className="flex items-center gap-2 px-3 py-1.5 bg-gray-800 hover:bg-gray-700 rounded-md transition-colors text-sm"
>
<User size={16} />
<span>{user?.username}</span>
</button>

{isOpen && (
<div
className="absolute right-0 mt-1 w-48 bg-gray-800 rounded-md shadow-lg
border border-gray-700 py-1 z-50"
>
<button
onClick={() => {
setIsOpen(false);
logout();
}}
className="w-full px-4 py-2 text-sm text-left text-gray-300 hover:bg-gray-700
flex items-center gap-2"
>
<LogOut size={16} />
<span>Sign out</span>
</button>
</div>
)}
<span>Sign in</span>
</Link>
</div>
);
)
};
57 changes: 41 additions & 16 deletions client/src/components/snippets/list/SnippetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface SnippetCardProps {
onDelete: (id: string) => void;
onEdit: (snippet: Snippet) => void;
onShare: (snippet: Snippet) => void;
onDuplicate: (snippet: Snippet) => void;
onCategoryClick: (category: string) => void;
compactView: boolean;
showCodePreview: boolean;
Expand All @@ -34,6 +35,7 @@ export const SnippetCard: React.FC<SnippetCardProps> = ({
onDelete,
onEdit,
onShare,
onDuplicate,
onCategoryClick,
compactView,
showCodePreview,
Expand All @@ -56,7 +58,8 @@ export const SnippetCard: React.FC<SnippetCardProps> = ({
}
};

const handleDeleteConfirm = () => {
const handleDeleteConfirm = (e?: React.MouseEvent) => {
e?.stopPropagation();
onDelete(snippet.id);
setIsDeleteModalOpen(false);
};
Expand All @@ -81,6 +84,21 @@ export const SnippetCard: React.FC<SnippetCardProps> = ({
window.open(`/snippets/${snippet.id}`, '_blank');
};

const handleDelete = (e: React.MouseEvent) => {
e.stopPropagation();
setIsDeleteModalOpen(true);
};

const handleDeleteModalClose = (e?: React.MouseEvent) => {
e?.stopPropagation();
setIsDeleteModalOpen(false);
};

const handleDuplicate = (e: React.MouseEvent) => {
e.stopPropagation();
onDuplicate(snippet);
};

const currentFragment = snippet.fragments[currentFragmentIndex];

return (
Expand All @@ -98,7 +116,7 @@ export const SnippetCard: React.FC<SnippetCardProps> = ({
<span>Public</span>
</div>
)}
{!isPublicView && (snippet.share_count || 0 ) > 0 && (
{!isPublicView && (snippet.share_count || 0) > 0 && (
<div className="flex items-center gap-1 text-blue-400 mr-4">
<Users size={12} />
<span>Shared</span>
Expand Down Expand Up @@ -136,10 +154,17 @@ export const SnippetCard: React.FC<SnippetCardProps> = ({

<div className="opacity-0 group-hover:opacity-100 transition-opacity">
<SnippetCardMenu
onEdit={() => onEdit(snippet)}
onDelete={() => setIsDeleteModalOpen(true)}
onShare={() => onShare(snippet)}
onEdit={(e: React.MouseEvent) => {
e.stopPropagation();
onEdit(snippet);
}}
onDelete={handleDelete}
onShare={(e: React.MouseEvent) => {
e.stopPropagation();
onShare(snippet);
}}
onOpenInNewTab={handleOpenInNewTab}
onDuplicate={handleDuplicate}
isPublicView={isPublicView}
/>
</div>
Expand Down Expand Up @@ -205,18 +230,18 @@ export const SnippetCard: React.FC<SnippetCardProps> = ({
</div>
)}
</div>

<ConfirmationModal
isOpen={isDeleteModalOpen}
onClose={() => setIsDeleteModalOpen(false)}
onConfirm={handleDeleteConfirm}
title="Confirm Deletion"
message={`Are you sure you want to delete "${snippet.title}"? This action cannot be undone.`}
confirmLabel="Delete"
cancelLabel="Cancel"
variant="danger"
/>
</div>

<ConfirmationModal
isOpen={isDeleteModalOpen}
onClose={handleDeleteModalClose}
onConfirm={handleDeleteConfirm}
title="Confirm Deletion"
message={`Are you sure you want to delete "${snippet.title}"? This action cannot be undone.`}
confirmLabel="Delete"
cancelLabel="Cancel"
variant="danger"
/>
</>
);
};
38 changes: 30 additions & 8 deletions client/src/components/snippets/list/SnippetCardMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useState, useRef } from 'react';
import { Share, Pencil, Trash2, ExternalLink, MoreVertical } from 'lucide-react';
import { Share, Pencil, Trash2, ExternalLink, MoreVertical, Copy } from 'lucide-react';
import { IconButton } from '../../common/buttons/IconButton';
import { useOutsideClick } from '../../../hooks/useOutsideClick';

interface SnippetCardMenuProps {
onEdit: () => void;
onDelete: () => void;
onShare: () => void;
onEdit: (e: React.MouseEvent) => void;
onDelete: (e: React.MouseEvent) => void;
onShare: (e: React.MouseEvent) => void;
onOpenInNewTab: () => void;
onDuplicate: (e: React.MouseEvent) => void;
isPublicView: boolean;
}

Expand All @@ -16,6 +17,7 @@ const SnippetCardMenu: React.FC<SnippetCardMenuProps> = ({
onDelete,
onShare,
onOpenInNewTab,
onDuplicate,
isPublicView
}) => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
Expand All @@ -27,6 +29,16 @@ const SnippetCardMenu: React.FC<SnippetCardMenuProps> = ({
if (isPublicView) {
return (
<div className="top-4 right-4 flex items-center gap-1">
<IconButton
icon={<Copy size={16} />}
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
onDuplicate(e);
}}
variant="custom"
size="sm"
className="bg-gray-700 hover:bg-gray-600"
/>
<IconButton
icon={<ExternalLink size={16} />}
onClick={(e: React.MouseEvent) => {
Expand All @@ -47,7 +59,7 @@ const SnippetCardMenu: React.FC<SnippetCardMenuProps> = ({
icon={<Pencil size={16} />}
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
onEdit();
onEdit(e);
}}
variant="custom"
size="sm"
Expand All @@ -57,14 +69,13 @@ const SnippetCardMenu: React.FC<SnippetCardMenuProps> = ({
icon={<Trash2 size={16} className="hover:text-red-500" />}
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
onDelete();
onDelete(e);
}}
variant="custom"
size="sm"
className="bg-gray-700 hover:bg-gray-600"
/>

{/* More Actions Dropdown */}
<div className="relative">
<IconButton
ref={buttonRef}
Expand Down Expand Up @@ -99,14 +110,25 @@ const SnippetCardMenu: React.FC<SnippetCardMenuProps> = ({
<button
onClick={(e) => {
e.stopPropagation();
onShare();
onShare(e);
setIsDropdownOpen(false);
}}
className="w-full px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 flex items-center gap-2"
>
<Share size={16} />
Share snippet
</button>
<button
onClick={(e) => {
e.stopPropagation();
onDuplicate(e);
setIsDropdownOpen(false);
}}
className="w-full px-4 py-2 text-sm text-gray-300 hover:bg-gray-700 flex items-center gap-2"
>
<Copy size={16} />
Duplicate snippet
</button>
</div>
)}
</div>
Expand Down
9 changes: 6 additions & 3 deletions client/src/components/snippets/list/SnippetList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ export interface SnippetListProps {
onOpen: (snippet: Snippet) => void;
onDelete: (id: string) => void;
onEdit: (snippet: Snippet) => void;
onCategoryClick: (category: string) => void;
onShare: (snippet: Snippet) => void;
onDuplicate: (snippet: Snippet) => void;
onCategoryClick: (category: string) => void;
compactView: boolean;
showCodePreview: boolean;
previewLines: number;
Expand All @@ -25,8 +26,9 @@ const SnippetList: React.FC<SnippetListProps> = ({
onOpen,
onDelete,
onEdit,
onCategoryClick,
onShare,
onDuplicate,
onCategoryClick,
compactView,
showCodePreview,
previewLines,
Expand Down Expand Up @@ -56,8 +58,9 @@ const SnippetList: React.FC<SnippetListProps> = ({
onOpen={onOpen}
onDelete={onDelete}
onEdit={onEdit}
onCategoryClick={onCategoryClick}
onShare={onShare}
onDuplicate={onDuplicate}
onCategoryClick={onCategoryClick}
compactView={compactView}
showCodePreview={showCodePreview}
previewLines={previewLines}
Expand Down
Loading

0 comments on commit c61dd09

Please sign in to comment.