Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[133] Private view toggle #136

Merged
merged 5 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified client/public/favicon.ico
Binary file not shown.
Binary file modified client/public/logo192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified client/public/logo512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 2 additions & 25 deletions client/src/components/auth/UserDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,20 @@
import React, { useRef, useState } from 'react';
import { LogOut, User, Globe } from 'lucide-react';
import { LogOut, User } from 'lucide-react';
import { useAuth } from '../../hooks/useAuth';
import { useOutsideClick } from '../../hooks/useOutsideClick';
import { Link, useLocation } from 'react-router-dom';
import { Link } 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();

if (user?.id === 0) {
return (<></>)
}

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">
Expand All @@ -46,15 +32,6 @@ export const UserDropdown: React.FC = () => {
className="absolute right-0 mt-1 w-48 bg-light-surface dark:bg-dark-surface rounded-md shadow-lg
border border-light-border dark:border-dark-border py-1 z-50"
>
<Link
to={switchViewProps.to}
onClick={() => setIsOpen(false)}
className="w-full px-4 py-2 text-sm text-left text-light-text dark:text-dark-text hover:bg-light-hover
dark:hover:bg-dark-hover flex items-center gap-2"
>
{switchViewProps.icon}
<span>{switchViewProps.text}</span>
</Link>
<button
onClick={() => {
setIsOpen(false);
Expand Down
42 changes: 19 additions & 23 deletions client/src/components/snippets/view/common/StorageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,47 @@
import React, { useState } from 'react';
import { Globe, Lock } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { APP_VERSION } from '../../../../constants/settings';
import ViewSwitch from './ViewSwitch';
import { ROUTES } from '../../../../constants/routes';

interface StorageHeaderProps {
isPublicView: boolean;
}

const StorageHeader: React.FC<StorageHeaderProps> = ({ isPublicView }) => {
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
const navigate = useNavigate();

const tooltipText = isPublicView
? "You're viewing publicly shared snippets. These snippets are read-only and visible to everyone."
: "You're viewing your private snippets. Only you can see and modify these snippets.";

const handleViewToggle = (checked: boolean) => {
navigate(checked ? ROUTES.PUBLIC_SNIPPETS : ROUTES.HOME);
};

return (
<div>
<div className="flex flex-col gap-2">
<h1 className="text-4xl font-bold text-light-text dark:text-dark-text flex items-baseline gap-2">
<img src="/logo512.png" alt="ByteStash Logo" className="w-7 h-7" />
ByteStash
<span className="text-sm text-light-text-secondary dark:text-dark-text-secondary">v{APP_VERSION}</span>
</h1>

<div
className="relative mt-1"
className="relative inline-block"
onMouseEnter={() => setIsTooltipVisible(true)}
onMouseLeave={() => setIsTooltipVisible(false)}
>
<div className="flex items-center gap-1.5">
{isPublicView ? (
<Globe className="w-3.5 h-3.5 text-light-primary dark:text-dark-primary" aria-label="Public View" />
) : (
<Lock className="w-3.5 h-3.5 text-emerald-600 dark:text-emerald-400" aria-label="Private View" />
)}
<span className={`text-sm ${
isPublicView
? 'text-light-primary dark:text-dark-primary'
: 'text-emerald-600 dark:text-emerald-400'
}`}>
{isPublicView ? 'Viewing public snippets' : 'Viewing private snippets'}
</span>
</div>
<ViewSwitch checked={isPublicView} onChange={handleViewToggle} />

{isTooltipVisible && (
<div
className={`absolute left-0 top-full mt-2 w-64 rounded-lg border p-3 text-sm z-50 shadow-lg ${
isPublicView
? 'border-light-primary/20 dark:border-dark-primary/20 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200'
: 'border-emerald-600/20 dark:border-emerald-400/20 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200'
}`}
className="absolute left-1/2 top-full mt-3 w-64 -translate-x-1/2 rounded-lg border border-light-border
dark:border-dark-border bg-light-surface dark:bg-dark-surface p-3 text-sm z-50 shadow-lg
text-light-text dark:text-dark-text before:content-[''] before:absolute before:-top-2 before:left-1/2
before:-translate-x-1/2 before:border-8 before:border-transparent before:border-b-light-surface
dark:before:border-b-dark-surface"
role="tooltip"
>
{tooltipText}
Expand All @@ -56,4 +52,4 @@ const StorageHeader: React.FC<StorageHeaderProps> = ({ isPublicView }) => {
);
};

export default StorageHeader;
export default StorageHeader;
61 changes: 61 additions & 0 deletions client/src/components/snippets/view/common/ViewSwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { Globe, Lock } from 'lucide-react';

interface ViewSwitchProps {
checked: boolean;
onChange: (checked: boolean) => void;
}

const ViewSwitch: React.FC<ViewSwitchProps> = ({ checked, onChange }) => {
return (
<div className="flex items-center gap-3 text-sm text-light-text dark:text-dark-text w-full">
<div
className="flex gap-0.5 rounded-lg bg-light-surface dark:bg-dark-surface px-0.5 py-0.5 w-full"
role="group"
>
<button
type="button"
onClick={() => onChange(false)}
className={`
flex items-center justify-center gap-1 px-2 py-0.5 rounded-md transition-all duration-200 flex-1
${!checked
? 'bg-light-hover dark:bg-dark-hover'
: 'hover:bg-light-hover/50 dark:hover:bg-dark-hover/50'
}
`}
>
<Lock
className={`
stroke-[2] transition-colors duration-200
${!checked ? 'text-emerald-500' : 'text-light-text/50 dark:text-dark-text/50'}
`}
size={14}
/>
<span className="text-xs font-medium">Private</span>
</button>
<button
type="button"
onClick={() => onChange(true)}
className={`
flex items-center justify-center gap-1 px-2 py-0.5 rounded-md transition-all duration-200 flex-1
${checked
? 'bg-light-hover dark:bg-dark-hover'
: 'hover:bg-light-hover/50 dark:hover:bg-dark-hover/50'
}
`}
>
<Globe
className={`
stroke-[2] transition-colors duration-200
${checked ? 'text-light-primary dark:text-dark-primary' : 'text-light-text/50 dark:text-dark-text/50'}
`}
size={14}
/>
<span className="text-xs font-medium">Public</span>
</button>
</div>
</div>
);
};

export default ViewSwitch;
Binary file modified media/logo.webp
Binary file not shown.