From 33adb738f3a56958b875c035b4182959d7c82a58 Mon Sep 17 00:00:00 2001 From: Babayaga Date: Tue, 7 Apr 2026 10:42:36 +0200 Subject: [PATCH] profile --- packages/ui/src/App.tsx | 54 ++++++------- packages/ui/src/components/MediaCard.tsx | 22 +++++- packages/ui/src/components/VideoCard.tsx | 35 +++++--- packages/ui/src/components/feed/FeedCard.tsx | 79 ++++++++++++++++--- .../ui/src/components/feed/FeedCarousel.tsx | 14 +++- .../ui/src/components/feed/MobileFeed.tsx | 58 ++++++-------- .../ui/src/components/widgets/HomeWidget.tsx | 2 +- packages/ui/src/modules/pages/PageCard.tsx | 20 +++-- .../{pages => modules/profile}/Profile.tsx | 74 ++++++++++------- .../profile/components/ProfileSidebar.tsx | 24 +----- packages/ui/src/modules/profile/types.ts | 8 +- 11 files changed, 243 insertions(+), 147 deletions(-) rename packages/ui/src/{pages => modules/profile}/Profile.tsx (86%) diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index 7e91bc68..2b44ff06 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -30,7 +30,7 @@ import AuthZ from "./pages/AuthZ"; const UpdatePassword = React.lazy(() => import("./pages/UpdatePassword")); -import Profile from "./pages/Profile"; +import Profile from "./modules/profile/Profile"; const Post = React.lazy(() => import("./modules/posts/PostPage")); import UserProfile from "./pages/UserProfile"; @@ -279,32 +279,32 @@ const App = () => { new Map() }}> - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/src/components/MediaCard.tsx b/packages/ui/src/components/MediaCard.tsx index a7140005..e0fef386 100644 --- a/packages/ui/src/components/MediaCard.tsx +++ b/packages/ui/src/components/MediaCard.tsx @@ -39,6 +39,10 @@ interface MediaCardProps { apiUrl?: string; versionCount?: number; preset?: CardPreset; + showTitle?: boolean; + showDescription?: boolean; + showAuthor?: boolean; + showActions?: boolean; } const MediaCard: React.FC = ({ @@ -67,7 +71,11 @@ const MediaCard: React.FC = ({ variant = 'grid', apiUrl, versionCount, - preset + preset, + showTitle, + showDescription, + showAuthor, + showActions }) => { const normalizedType = normalizeMediaType(type); // Render based on type @@ -105,6 +113,10 @@ const MediaCard: React.FC = ({ job={job} variant={variant} apiUrl={apiUrl} + showTitle={showTitle} + showDescription={showDescription} + showAuthor={showAuthor} + showActions={showActions} /> ); } @@ -194,6 +206,10 @@ const MediaCard: React.FC = ({ variant={variant} apiUrl={apiUrl} preset={preset} + showTitle={showTitle} + showDescription={showDescription} + showAuthor={showAuthor} + showActions={showActions} /> ); } @@ -222,6 +238,10 @@ const MediaCard: React.FC = ({ apiUrl={apiUrl} versionCount={versionCount} preset={preset} + showTitle={showTitle} + showDescription={showDescription} + showAuthor={showAuthor} + showActions={showActions} /> ); }; diff --git a/packages/ui/src/components/VideoCard.tsx b/packages/ui/src/components/VideoCard.tsx index b3ead817..ca0572d9 100644 --- a/packages/ui/src/components/VideoCard.tsx +++ b/packages/ui/src/components/VideoCard.tsx @@ -49,6 +49,10 @@ interface VideoCardProps { variant?: 'grid' | 'feed'; showPlayButton?: boolean; apiUrl?: string; + showTitle?: boolean; + showDescription?: boolean; + showAuthor?: boolean; + showActions?: boolean; } const VideoCard = ({ @@ -73,7 +77,11 @@ const VideoCard = ({ created_at, job, variant = 'grid', - apiUrl + apiUrl, + showTitle = true, + showDescription = true, + showAuthor = true, + showActions = true }: VideoCardProps) => { const { user } = useAuth(); const navigate = useNavigate(); @@ -574,15 +582,17 @@ const VideoCard = ({
{/* Row 1: Avatar + Actions */}
- - -
+ {showAuthor && ( + + )} + {showActions && ( +
+ )}
{/* Likes */} @@ -641,11 +652,11 @@ const VideoCard = ({ {/* Caption / Description section */}
- {(!isLikelyFilename(title) && title) && ( + {showTitle && (!isLikelyFilename(title) && title) && (
{title}
)} - {description && ( + {showDescription && description && (
diff --git a/packages/ui/src/components/feed/FeedCard.tsx b/packages/ui/src/components/feed/FeedCard.tsx index 11bf4344..b4cc5ddf 100644 --- a/packages/ui/src/components/feed/FeedCard.tsx +++ b/packages/ui/src/components/feed/FeedCard.tsx @@ -1,12 +1,16 @@ import React, { useState } from 'react'; import { FeedPost } from '@/modules/posts/client-posts'; import { FeedCarousel } from './FeedCarousel'; -import { Heart } from 'lucide-react'; +import { Heart, MessageCircle } from 'lucide-react'; +import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; -import * as db from '@/lib/db'; import { useNavigate } from "react-router-dom"; import { normalizeMediaType } from "@/lib/mediaRegistry"; import { toggleLike } from '@/modules/posts/client-pictures'; +import UserAvatarBlock from '@/components/UserAvatarBlock'; +import MarkdownRenderer from '@/components/MarkdownRenderer'; +import { formatDate } from '@/utils/textUtils'; +import { T } from '@/i18n'; interface FeedCardProps { post: FeedPost; @@ -15,13 +19,17 @@ interface FeedCardProps { onComment?: () => void; onShare?: () => void; onNavigate?: (id: string) => void; + showTitle?: boolean; + showDescription?: boolean; } export const FeedCard: React.FC = ({ post, currentUserId, onLike, - onNavigate + onNavigate, + showTitle = false, + showDescription = false, }) => { const navigate = useNavigate(); // Initialize from precomputed status (post.cover.is_liked or post.is_liked) @@ -104,7 +112,18 @@ export const FeedCard: React.FC = ({ return (
- + {/* Header: Author Info */} +
+ +
+ {post.created_at ? formatDate(post.created_at) : ''} +
+
{/* Media Carousel */}
@@ -116,18 +135,52 @@ export const FeedCard: React.FC = ({ authorId={post.user_id} authorAvatarUrl={post.author?.avatar_url} onItemClick={handleItemClick} + showContent={false} // Silence internal card UI + showAuthor={false} + showActions={false} + showTitle={false} + showDescription={false} /> - - {/* Double tap heart animation overlay */} -
- -
- {/* Actions Bar - Removed as actions are now per-item in the carousel */} + {/* Actions Bar */} +
+ + {likeCount > 0 && ( + {likeCount} + )} + + +
+ + {/* Caption: Title & Description */} +
+ {showTitle && post.title && ( +
{post.title}
+ )} + {showDescription && post.description && ( +
+ +
+ )} +
); }; diff --git a/packages/ui/src/components/feed/FeedCarousel.tsx b/packages/ui/src/components/feed/FeedCarousel.tsx index 10a97ed7..70e27bb6 100644 --- a/packages/ui/src/components/feed/FeedCarousel.tsx +++ b/packages/ui/src/components/feed/FeedCarousel.tsx @@ -14,6 +14,10 @@ interface CarouselProps { authorAvatarUrl?: string | null; onItemClick?: (id: string) => void; showContent?: boolean; + showTitle?: boolean; + showDescription?: boolean; + showAuthor?: boolean; + showActions?: boolean; } export const FeedCarousel: React.FC = ({ @@ -25,7 +29,11 @@ export const FeedCarousel: React.FC = ({ authorId, authorAvatarUrl, onItemClick, - showContent = true + showContent = true, + showTitle, + showDescription, + showAuthor, + showActions }) => { const [emblaRef, emblaApi] = useEmblaCarousel({ loop: false }); const [selectedIndex, setSelectedIndex] = useState(0); @@ -92,6 +100,10 @@ export const FeedCarousel: React.FC = ({ description={item.description} created_at={item.created_at} showContent={showContent} + showTitle={showTitle} + showDescription={showDescription} + showAuthor={showAuthor} + showActions={showActions} onClick={onItemClick} job={item.job} responsive={item.responsive} diff --git a/packages/ui/src/components/feed/MobileFeed.tsx b/packages/ui/src/components/feed/MobileFeed.tsx index 0a1180dd..9f895b80 100644 --- a/packages/ui/src/components/feed/MobileFeed.tsx +++ b/packages/ui/src/components/feed/MobileFeed.tsx @@ -20,10 +20,10 @@ interface MobileFeedProps { contentType?: 'posts' | 'pages' | 'pictures' | 'files'; visibilityFilter?: 'invisible' | 'private'; center?: boolean; + showTitle?: boolean; + showDescription?: boolean; } -const PRELOAD_BUFFER = 3; - export const MobileFeed: React.FC = ({ source = 'home', sourceId, @@ -33,7 +33,9 @@ export const MobileFeed: React.FC = ({ categoryIds, contentType, visibilityFilter, - center + center, + showTitle, + showDescription, }) => { const { user } = useAuth(); @@ -140,6 +142,8 @@ export const MobileFeed: React.FC = ({ posts={posts} currentUser={user} onNavigate={onNavigate} + showTitle={showTitle} + showDescription={showDescription} /> )); } @@ -148,8 +152,8 @@ export const MobileFeed: React.FC = ({ if (item.type === 'page-vfs-folder') return 'Folders'; if (item._searchSource === 'picture') return 'Pictures'; if (item._searchSource === 'file') { - if (item.thumbnail_url || item.cover || (item.pictures && item.pictures.length > 0)) return 'Pictures'; - return 'Files'; + if (item.thumbnail_url || item.cover || (item.pictures && item.pictures.length > 0)) return 'Pictures'; + return 'Files'; } if (item._searchSource === 'page') return 'Pages'; if (item._searchSource === 'post') return 'Posts'; @@ -168,13 +172,13 @@ export const MobileFeed: React.FC = ({ for (const group of orderedGroups) { if (groups.has(group)) { - elements.push( + elements.push(
{group}
- ); - elements.push( - ...groups.get(group)!.map((post: any, index: number) => ( + ); + elements.push( + ...groups.get(group)!.map((post: any, index: number) => ( = ({ posts={posts} currentUser={user} onNavigate={onNavigate} + showTitle={showTitle} + showDescription={showDescription} /> - )) - ); + )) + ); } } @@ -204,38 +210,24 @@ const FeedItemWrapper: React.FC<{ index: number, posts: FeedPost[], currentUser: any, - onNavigate?: (id: string) => void -}> = ({ post, index, posts, currentUser, onNavigate }) => { - const { ref, inView } = useInView({ + onNavigate?: (id: string) => void; + showTitle?: boolean; + showDescription?: boolean; +}> = ({ post, index, posts, currentUser, onNavigate, showTitle, showDescription }) => { + const { ref } = useInView({ triggerOnce: false, - rootMargin: '200px 0px', // Trigger slightly before + rootMargin: '200px 0px', threshold: 0.1 }); - useEffect(() => { - /* - if (inView) { - // Preload next 5 posts' Main Image - const bufferEnd = Math.min(index + 1 + PRELOAD_BUFFER, posts.length); - for (let i = index + 1; i < bufferEnd; i++) { - const nextPost = posts[i]; - if (nextPost.cover?.image_url) { - const img = new Image(); - img.src = nextPost.cover.image_url; - } - // If the next post has multiple images, maybe preload the second one too? - // Keeping it light: only cover for now. - } - } - */ - }, [inView, index, posts]); - return (
); diff --git a/packages/ui/src/components/widgets/HomeWidget.tsx b/packages/ui/src/components/widgets/HomeWidget.tsx index 5d5b44a6..e39302bb 100644 --- a/packages/ui/src/components/widgets/HomeWidget.tsx +++ b/packages/ui/src/components/widgets/HomeWidget.tsx @@ -339,7 +339,7 @@ const HomeWidget: React.FC = ({ return viewMode === 'list' ? ( ) : ( - window.location.href = `/post/${id}`} /> + window.location.href = `/post/${id}`} showTitle={showTitle} showDescription={showDescription} /> ); } diff --git a/packages/ui/src/modules/pages/PageCard.tsx b/packages/ui/src/modules/pages/PageCard.tsx index 65a7d9f9..25ebb600 100644 --- a/packages/ui/src/modules/pages/PageCard.tsx +++ b/packages/ui/src/modules/pages/PageCard.tsx @@ -26,6 +26,10 @@ interface PageCardProps extends Omit { versionCount?: number; preset?: CardPreset; className?: string; + showTitle?: boolean; + showDescription?: boolean; + showAuthor?: boolean; + showActions?: boolean; } const PageCard: React.FC = ({ @@ -52,7 +56,11 @@ const PageCard: React.FC = ({ versionCount, preset, type, - className + className, + showTitle, + showDescription, + showAuthor, + showActions }) => { // Determine image source // If url is missing or empty, fallback to picsum @@ -94,7 +102,7 @@ const PageCard: React.FC = ({
{/* Author + Actions row */}
- {preset?.showAuthor !== false && ( + {(showAuthor ?? preset?.showAuthor) !== false && ( = ({ createdAt={created_at} /> )} - {preset?.showActions !== false && ( + {(showActions ?? preset?.showActions) !== false && (
} /> +
SMTP Servers Loading...
}> - +
} /> +
My Gallery @@ -292,7 +294,25 @@ const Profile = () => { onDelete={handleImageDelete} onNavigate={navigate} /> - +
+ } /> + + + + My Files + +
+ Loading...
}> + + +
+
} />
diff --git a/packages/ui/src/modules/profile/components/ProfileSidebar.tsx b/packages/ui/src/modules/profile/components/ProfileSidebar.tsx index 8a4afe56..ce6061eb 100644 --- a/packages/ui/src/modules/profile/components/ProfileSidebar.tsx +++ b/packages/ui/src/modules/profile/components/ProfileSidebar.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { Images } from "lucide-react"; import { T } from "@/i18n"; import { Sidebar, @@ -25,10 +24,7 @@ export const ProfileSidebar: React.FC = ({ }) => { const { state } = useSidebar(); const isCollapsed = state === "collapsed"; - - const mainItems = PROFILE_MENU_ITEMS.filter(item => item.id !== 'gallery'); - const galleryItem = PROFILE_MENU_ITEMS.find(item => item.id === 'gallery'); - + const mainItems = PROFILE_MENU_ITEMS; return ( @@ -50,24 +46,6 @@ export const ProfileSidebar: React.FC = ({ - - - - - {galleryItem && ( - - onSectionChange('gallery')} - className={activeSection === 'gallery' ? "bg-muted text-primary font-medium" : "hover:bg-muted/50"} - > - - {!isCollapsed && {galleryItem.label}} - - - )} - - - ); diff --git a/packages/ui/src/modules/profile/types.ts b/packages/ui/src/modules/profile/types.ts index 9b950a85..2bc27116 100644 --- a/packages/ui/src/modules/profile/types.ts +++ b/packages/ui/src/modules/profile/types.ts @@ -1,7 +1,7 @@ -import { User, Key, Hash, MapPin, Building2, BookUser, Send, Plug, ShoppingBag, Images, FolderTree } from "lucide-react"; +import { User, Key, Hash, MapPin, Building2, BookUser, Send, Plug, ShoppingBag, Images, FolderTree, FolderOpen } from "lucide-react"; import { translate } from "@/i18n"; -export type ActiveSection = 'general' | 'api-keys' | 'variables' | 'addresses' | 'vendor' | 'gallery' | 'purchases' | 'contacts' | 'campaigns' | 'integrations' | 'smtp-servers' | 'categories'; +export type ActiveSection = 'general' | 'api-keys' | 'variables' | 'addresses' | 'vendor' | 'gallery' | 'purchases' | 'contacts' | 'campaigns' | 'integrations' | 'smtp-servers' | 'categories' | 'my-files'; export interface ProfileData { username: string; @@ -14,7 +14,7 @@ export interface ProfileData { export const PROFILE_MENU_ITEMS = [ { id: 'general' as ActiveSection, label: 'General', icon: User }, { id: 'api-keys' as ActiveSection, label: 'API Keys', icon: Key }, - { id: 'variables' as ActiveSection, label: 'Hash Variables', icon: Hash }, + { id: 'variables' as ActiveSection, label: 'Variables', icon: Hash }, { id: 'categories' as ActiveSection, label: 'Categories', icon: FolderTree }, { id: 'addresses' as ActiveSection, label: 'Shipping Addresses', icon: MapPin }, { id: 'vendor' as ActiveSection, label: 'Vendor Profiles', icon: Building2 }, @@ -23,5 +23,5 @@ export const PROFILE_MENU_ITEMS = [ { id: 'integrations' as ActiveSection, label: 'IMAP Integrations', icon: Plug }, { id: 'smtp-servers' as ActiveSection, label: 'SMTP Servers', icon: Send }, { id: 'purchases' as ActiveSection, label: 'Purchases', icon: ShoppingBag }, - { id: 'gallery' as ActiveSection, label: 'Gallery', icon: Images }, + { id: 'my-files' as ActiveSection, label: 'My Files', icon: FolderOpen } ];