This commit is contained in:
lovebird 2026-04-07 10:42:36 +02:00
parent 1fac563618
commit 33adb738f3
11 changed files with 243 additions and 147 deletions

View File

@ -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 = () => {
<SWRConfig value={{ provider: () => new Map() }}>
<OidcProvider {...oidcConfig}>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<LogProvider>
<MediaRefreshProvider>
<TooltipProvider>
<Toaster />
<Sonner />
<ActionProvider>
<BrowserRouter>
<DragDropProvider>
<ProfilesProvider>
<WebSocketProvider url={import.meta.env.VITE_SERVER_IMAGE_API_URL}>
<StreamProvider url={import.meta.env.VITE_SERVER_IMAGE_API_URL}>
<StreamInvalidator />
<FeedCacheProvider>
<AppWrapper />
</FeedCacheProvider>
</StreamProvider>
</WebSocketProvider>
</ProfilesProvider>
</DragDropProvider>
</BrowserRouter>
</ActionProvider>
</TooltipProvider>
</MediaRefreshProvider>
</LogProvider>
</AuthProvider>
<AuthProvider>
<LogProvider>
<MediaRefreshProvider>
<TooltipProvider>
<Toaster />
<Sonner />
<ActionProvider>
<BrowserRouter>
<DragDropProvider>
<ProfilesProvider>
<WebSocketProvider url={import.meta.env.VITE_SERVER_IMAGE_API_URL}>
<StreamProvider url={import.meta.env.VITE_SERVER_IMAGE_API_URL}>
<StreamInvalidator />
<FeedCacheProvider>
<AppWrapper />
</FeedCacheProvider>
</StreamProvider>
</WebSocketProvider>
</ProfilesProvider>
</DragDropProvider>
</BrowserRouter>
</ActionProvider>
</TooltipProvider>
</MediaRefreshProvider>
</LogProvider>
</AuthProvider>
</QueryClientProvider>
</OidcProvider>
</SWRConfig>

View File

@ -39,6 +39,10 @@ interface MediaCardProps {
apiUrl?: string;
versionCount?: number;
preset?: CardPreset;
showTitle?: boolean;
showDescription?: boolean;
showAuthor?: boolean;
showActions?: boolean;
}
const MediaCard: React.FC<MediaCardProps> = ({
@ -67,7 +71,11 @@ const MediaCard: React.FC<MediaCardProps> = ({
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<MediaCardProps> = ({
job={job}
variant={variant}
apiUrl={apiUrl}
showTitle={showTitle}
showDescription={showDescription}
showAuthor={showAuthor}
showActions={showActions}
/>
);
}
@ -194,6 +206,10 @@ const MediaCard: React.FC<MediaCardProps> = ({
variant={variant}
apiUrl={apiUrl}
preset={preset}
showTitle={showTitle}
showDescription={showDescription}
showAuthor={showAuthor}
showActions={showActions}
/>
);
}
@ -222,6 +238,10 @@ const MediaCard: React.FC<MediaCardProps> = ({
apiUrl={apiUrl}
versionCount={versionCount}
preset={preset}
showTitle={showTitle}
showDescription={showDescription}
showAuthor={showAuthor}
showActions={showActions}
/>
);
};

View File

@ -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 = ({
<div className={`${variant === 'grid' ? "md:hidden" : ""} pb-2 space-y-2`}>
{/* Row 1: Avatar + Actions */}
<div className="flex items-center justify-between px-2 pt-2">
<UserAvatarBlock
userId={authorId}
avatarUrl={authorAvatarUrl}
displayName={author === 'User' ? undefined : author}
className="w-8 h-8"
showDate={false}
/>
<div className="flex items-center gap-1">
{showAuthor && (
<UserAvatarBlock
userId={authorId}
avatarUrl={authorAvatarUrl}
displayName={author === 'User' ? undefined : author}
className="w-8 h-8"
showDate={false}
/>
)}
{showActions && (
<div className="flex items-center gap-1">
<Button
size="icon"
variant="ghost"
@ -634,6 +644,7 @@ const VideoCard = ({
</>
)}
</div>
)}
</div>
{/* Likes */}
@ -641,11 +652,11 @@ const VideoCard = ({
{/* Caption / Description section */}
<div className="px-4 space-y-1">
{(!isLikelyFilename(title) && title) && (
{showTitle && (!isLikelyFilename(title) && title) && (
<div className="font-semibold text-sm">{title}</div>
)}
{description && (
{showDescription && description && (
<div className="text-sm text-foreground/90 line-clamp-3 pl-8">
<MarkdownRenderer content={description} className="prose-sm dark:prose-invert" />
</div>

View File

@ -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<FeedCardProps> = ({
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<FeedCardProps> = ({
return (
<article className="bg-background md:border md:mb-6 md:pb-0 overflow-hidden">
{/* Header: Author Info */}
<div className="px-3 py-2 flex items-center justify-between">
<UserAvatarBlock
userId={post.user_id}
avatarUrl={post.author?.avatar_url}
displayName={post.author?.display_name || post.author?.username}
showDate={false}
/>
<div className="text-xs text-muted-foreground">
{post.created_at ? formatDate(post.created_at) : ''}
</div>
</div>
{/* Media Carousel */}
<div className="relative" onTouchEnd={handleDoubleTap} onClick={handleDoubleTap}>
@ -116,18 +135,52 @@ export const FeedCard: React.FC<FeedCardProps> = ({
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 */}
<div className={cn(
"absolute inset-0 flex items-center justify-center pointer-events-none transition-opacity duration-300",
showHeartAnimation ? "opacity-100 scale-100" : "opacity-0 scale-50"
)}>
<Heart className="w-24 h-24 text-white fill-white drop-shadow-xl animate-bounce-short" />
</div>
</div>
{/* Actions Bar - Removed as actions are now per-item in the carousel */}
{/* Actions Bar */}
<div className="flex items-center px-2 pt-2">
<Button
size="icon"
variant="ghost"
onClick={handleLike}
className={cn("hover:text-red-500", isLiked && "text-red-500")}
>
<Heart className="h-6 w-6" fill={isLiked ? "currentColor" : "none"} />
</Button>
{likeCount > 0 && (
<span className="text-sm font-medium mr-1">{likeCount}</span>
)}
<Button
size="icon"
variant="ghost"
className="text-foreground"
onClick={() => {
// Pass ID to parent but if it's not implemented, navigate to details
onNavigate?.(post.id);
}}
>
<MessageCircle className="h-6 w-6 -rotate-90" />
</Button>
</div>
{/* Caption: Title & Description */}
<div className="px-4 pb-3 space-y-1">
{showTitle && post.title && (
<div className="font-bold text-sm">{post.title}</div>
)}
{showDescription && post.description && (
<div className="text-sm text-foreground/90 line-clamp-3">
<MarkdownRenderer content={post.description} className="prose-sm dark:prose-invert" />
</div>
)}
</div>
</article>
);
};

View File

@ -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<CarouselProps> = ({
@ -25,7 +29,11 @@ export const FeedCarousel: React.FC<CarouselProps> = ({
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<CarouselProps> = ({
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}

View File

@ -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<MobileFeedProps> = ({
source = 'home',
sourceId,
@ -33,7 +33,9 @@ export const MobileFeed: React.FC<MobileFeedProps> = ({
categoryIds,
contentType,
visibilityFilter,
center
center,
showTitle,
showDescription,
}) => {
const { user } = useAuth();
@ -140,6 +142,8 @@ export const MobileFeed: React.FC<MobileFeedProps> = ({
posts={posts}
currentUser={user}
onNavigate={onNavigate}
showTitle={showTitle}
showDescription={showDescription}
/>
));
}
@ -148,8 +152,8 @@ export const MobileFeed: React.FC<MobileFeedProps> = ({
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<MobileFeedProps> = ({
for (const group of orderedGroups) {
if (groups.has(group)) {
elements.push(
elements.push(
<div key={`group-${group}`} className="px-3 py-1.5 bg-muted/40 border-y text-xs font-semibold uppercase tracking-wider text-muted-foreground sticky top-0 z-10 backdrop-blur-md">
<T>{group}</T>
</div>
);
elements.push(
...groups.get(group)!.map((post: any, index: number) => (
);
elements.push(
...groups.get(group)!.map((post: any, index: number) => (
<FeedItemWrapper
key={post.id}
post={post}
@ -182,9 +186,11 @@ export const MobileFeed: React.FC<MobileFeedProps> = ({
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 (
<div ref={ref}>
<FeedCard
post={post}
currentUserId={currentUser?.id}
onNavigate={onNavigate}
showTitle={showTitle}
showDescription={showDescription}
/>
</div>
);

View File

@ -339,7 +339,7 @@ const HomeWidget: React.FC<HomeWidgetProps> = ({
return viewMode === 'list' ? (
<ListLayout key={refreshKey} sortBy={sortBy} navigationSource={feedSource} navigationSourceId={feedSourceId} categorySlugs={categorySlugs} categoryIds={propCategoryId ? [propCategoryId] : undefined} contentType={contentType} visibilityFilter={visibilityFilter} center={center} />
) : (
<MobileFeed source={feedSource} sourceId={feedSourceId} sortBy={sortBy} categorySlugs={categorySlugs} categoryIds={propCategoryId ? [propCategoryId] : undefined} contentType={contentType} visibilityFilter={visibilityFilter} onNavigate={(id) => window.location.href = `/post/${id}`} />
<MobileFeed source={feedSource} sourceId={feedSourceId} sortBy={sortBy} categorySlugs={categorySlugs} categoryIds={propCategoryId ? [propCategoryId] : undefined} contentType={contentType} visibilityFilter={visibilityFilter} onNavigate={(id) => window.location.href = `/post/${id}`} showTitle={showTitle} showDescription={showDescription} />
);
}

View File

@ -26,6 +26,10 @@ interface PageCardProps extends Omit<MediaRendererProps, 'created_at'> {
versionCount?: number;
preset?: CardPreset;
className?: string;
showTitle?: boolean;
showDescription?: boolean;
showAuthor?: boolean;
showActions?: boolean;
}
const PageCard: React.FC<PageCardProps> = ({
@ -52,7 +56,11 @@ const PageCard: React.FC<PageCardProps> = ({
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<PageCardProps> = ({
<div className="pb-2 space-y-2">
{/* Author + Actions row */}
<div className="flex items-center justify-between px-2 pt-2">
{preset?.showAuthor !== false && (
{(showAuthor ?? preset?.showAuthor) !== false && (
<UserAvatarBlock
userId={authorId}
avatarUrl={authorAvatarUrl}
@ -104,7 +112,7 @@ const PageCard: React.FC<PageCardProps> = ({
createdAt={created_at}
/>
)}
{preset?.showActions !== false && (
{(showActions ?? preset?.showActions) !== false && (
<div className="flex items-center gap-1">
<Button size="sm" variant="ghost" className="px-0 gap-1" onClick={handleLike}>
<Heart className={`h-5 w-5 ${isLiked ? "fill-red-500 text-red-500" : ""}`} />
@ -120,8 +128,10 @@ const PageCard: React.FC<PageCardProps> = ({
{/* Title & Description */}
<div className="px-4 space-y-1">
<div className="font-semibold text-sm">{title}</div>
{description && (
{(showTitle ?? preset?.showTitle) !== false && (
<div className="font-semibold text-sm">{title}</div>
)}
{(showDescription ?? preset?.showDescription) !== false && description && (
<div className="text-sm text-foreground/90 line-clamp-3">
<MarkdownRenderer content={description} className="prose-sm dark:prose-invert" />
</div>

View File

@ -20,6 +20,8 @@ import { ApiKeysSettings } from "@/modules/profile/components/ApiKeysSettings";
import { ProfileGallery } from "@/modules/profile/components/ProfileGallery";
// Lazy Loaded Modules
const FileBrowser = React.lazy(() => import('@/apps/filebrowser/FileBrowser'));
const LazyPurchasesList = React.lazy(() =>
import("@polymech/ecommerce").then(m => ({ default: m.PurchasesList }))
);
@ -110,14 +112,14 @@ const Profile = () => {
return (
<SidebarProvider>
<div className="min-h-screen flex w-full bg-background pt-14">
<div className="min-h-screen flex w-full bg-background">
<ProfileSidebar activeSection={activeSection} onSectionChange={setActiveSection} />
<main className="flex-1 p-4 overflow-auto">
<main className="flex-1 p-2 overflow-auto">
<div className="mx-auto">
<Routes>
<Route path="/" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>General Settings</T></CardTitle>
</CardHeader>
@ -140,11 +142,11 @@ const Profile = () => {
onSelectFromGallery: handleSelectFromGallery
}}
/>
</Card>
</div>
} />
<Route path="api-keys" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>API Keys</T></CardTitle>
</CardHeader>
@ -155,11 +157,11 @@ const Profile = () => {
onSubmit={handleProfileUpdate}
updating={updatingProfile}
/>
</Card>
</div>
} />
<Route path="variables" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>Variables</T></CardTitle>
</CardHeader>
@ -175,33 +177,33 @@ const Profile = () => {
}}
/>
</React.Suspense>
</Card>
</div>
} />
<Route path="addresses" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>Shipping Addresses</T></CardTitle>
</CardHeader>
<React.Suspense fallback={<div className="flex items-center justify-center py-12 text-muted-foreground"><T>Loading...</T></div>}>
<ShippingAddressManager userId={user.id} />
</React.Suspense>
</Card>
</div>
} />
<Route path="vendor" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>Vendor Profiles</T></CardTitle>
</CardHeader>
<React.Suspense fallback={<div className="flex items-center justify-center py-12 text-muted-foreground"><T>Loading...</T></div>}>
<VendorProfileManager userId={user.id} />
</React.Suspense>
</Card>
</div>
} />
<Route path="purchases" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>My Purchases</T></CardTitle>
</CardHeader>
@ -219,68 +221,68 @@ const Profile = () => {
}}
/>
</React.Suspense>
</Card>
</div>
} />
<Route path="contacts" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>Contacts</T></CardTitle>
</CardHeader>
<React.Suspense fallback={<div className="flex items-center justify-center py-12 text-muted-foreground"><T>Loading...</T></div>}>
<ContactsManager />
</React.Suspense>
</Card>
</div>
} />
<Route path="campaigns" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>Campaigns</T></CardTitle>
</CardHeader>
<React.Suspense fallback={<div className="flex items-center justify-center py-12 text-muted-foreground"><T>Loading...</T></div>}>
<CampaignsManager />
</React.Suspense>
</Card>
</div>
} />
<Route path="categories" element={
<Card className="flex flex-col h-[75vh] min-h-[600px]">
<div className="flex flex-col h-[75vh] min-h-[600px]">
<CardHeader>
<CardTitle><T>Categories</T></CardTitle>
</CardHeader>
<div className="flex-1 min-h-0 pl-6 pr-6 pb-6">
<div className="flex-1 min-h-0 px-0 pb-1 md:px-6 md:pb-6 relative overflow-hidden">
<React.Suspense fallback={<div className="flex items-center justify-center py-12 text-muted-foreground"><T>Loading...</T></div>}>
<CategoryManager isOpen={true} onClose={() => { }} asView={true} />
</React.Suspense>
</div>
</Card>
</div>
} />
<Route path="integrations" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>IMAP Integrations</T></CardTitle>
</CardHeader>
<React.Suspense fallback={<div className="flex items-center justify-center py-12 text-muted-foreground"><T>Loading...</T></div>}>
<GmailIntegrations />
</React.Suspense>
</Card>
</div>
} />
<Route path="smtp-servers" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>SMTP Servers</T></CardTitle>
</CardHeader>
<React.Suspense fallback={<div className="flex items-center justify-center py-12 text-muted-foreground"><T>Loading...</T></div>}>
<SmtpIntegrations />
</React.Suspense>
</Card>
</div>
} />
<Route path="gallery" element={
<Card>
<div>
<CardHeader>
<CardTitle><T>My Gallery</T></CardTitle>
</CardHeader>
@ -292,7 +294,25 @@ const Profile = () => {
onDelete={handleImageDelete}
onNavigate={navigate}
/>
</Card>
</div>
} />
<Route path="my-files/*" element={
<div className="flex flex-col h-[calc(100vh-140px)] min-h-[500px] overflow-hidden">
<CardHeader>
<CardTitle><T>My Files</T></CardTitle>
</CardHeader>
<div className="flex-1 min-h-0 px-2 pb-1 md:px-6 md:pb-6 relative overflow-hidden">
<React.Suspense fallback={<div className="flex items-center justify-center py-12 text-muted-foreground"><T>Loading...</T></div>}>
<FileBrowser
allowPanels={false}
mode="simple"
disableRoutingSync={true}
initialMount="home"
/>
</React.Suspense>
</div>
</div>
} />
</Routes>
</div>

View File

@ -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<ProfileSidebarProps> = ({
}) => {
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 (
<Sidebar collapsible="icon">
<SidebarContent>
@ -50,24 +46,6 @@ export const ProfileSidebar: React.FC<ProfileSidebarProps> = ({
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarGroup className="mt-auto">
<SidebarGroupContent>
<SidebarMenu>
{galleryItem && (
<SidebarMenuItem>
<SidebarMenuButton
onClick={() => onSectionChange('gallery')}
className={activeSection === 'gallery' ? "bg-muted text-primary font-medium" : "hover:bg-muted/50"}
>
<galleryItem.icon className="h-4 w-4" />
{!isCollapsed && <span><T>{galleryItem.label}</T></span>}
</SidebarMenuButton>
</SidebarMenuItem>
)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
);

View File

@ -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 }
];