Skip to content

Commit bcefc7b

Browse files
authored
Merge pull request #67 from zecrypt-io/feature/shivil/solving-sessionkey-missings
Changed the nav-bar UI to collapsible
2 parents bbf7d78 + ae12040 commit bcefc7b

6 files changed

Lines changed: 607 additions & 334 deletions

File tree

packages/frontend-web/components/dashboard-layout.tsx

Lines changed: 18 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { useTranslator } from "@/hooks/use-translations";
5050
import { secureSetItem } from '@/libs/local-storage-utils';
5151
import { SearchModal } from "@/components/search-modal";
5252
import { logout } from "@/libs/utils";
53+
import { SidebarNav } from "@/components/sidebar-nav";
5354

5455
interface DashboardLayoutProps {
5556
children: React.ReactNode;
@@ -345,14 +346,7 @@ export function DashboardLayout({ children, locale = "en" }: DashboardLayoutProp
345346
const [showFavoritesDialog, setShowFavoritesDialog] = useState(false);
346347
const [favoriteTags, setFavoriteTags] = useState(["Personal", "Work", "Banking"]);
347348
const [showSearchModal, setShowSearchModal] = useState(false);
348-
349-
const [collapsedCategories, setCollapsedCategories] = useState<Record<string, boolean>>({
350-
overview: false,
351-
general: false,
352-
security: false,
353-
business: false,
354-
licenses: false
355-
});
349+
const [currentLocale, setCurrentLocale] = useState(locale);
356350

357351
const languageLabels: Record<string, string> = {
358352
af: "Afrikaans",
@@ -388,45 +382,11 @@ export function DashboardLayout({ children, locale = "en" }: DashboardLayoutProp
388382
"zh-Hant": "Chinese Traditional (繁體中文)",
389383
};
390384

391-
const selectedWorkspaceId = useSelector((state: RootState) => state.workspace.selectedWorkspaceId);
392-
const selectedProjectId = useSelector((state: RootState) => state.workspace.selectedProjectId);
393-
const selectedProject = useSelector((state: RootState) =>
394-
Array.isArray(state.workspace.workspaces) && selectedWorkspaceId && selectedProjectId
395-
? state.workspace.workspaces
396-
.find((ws) => ws.workspaceId === selectedWorkspaceId)
397-
?.projects.find((p) => p.project_id === selectedProjectId)
398-
: null
399-
);
400-
const defaultProject = useSelector((state: RootState) =>
401-
Array.isArray(state.workspace.workspaces) && selectedWorkspaceId
402-
? state.workspace.workspaces
403-
.find((ws) => ws.workspaceId === selectedWorkspaceId)
404-
?.projects.find((p) => p.is_default)
405-
: null
406-
);
407-
const displayProject = selectedProject || defaultProject;
408-
409-
const normalizedFeatures = displayProject
410-
? { ...defaultFeatures, ...(displayProject.features || {}) }
411-
: defaultFeatures;
412-
413-
const selectedWorkspace = useSelector((state: RootState) =>
414-
Array.isArray(state.workspace.workspaces)
415-
? state.workspace.workspaces.find((ws) => ws.workspaceId === selectedWorkspaceId)
416-
: null
417-
);
418-
const workspaceName = selectedWorkspace?.name || "Personal Workspace";
419-
420-
const toggleCategory = (categoryId: string) => {
421-
setCollapsedCategories(prev => ({
422-
...prev,
423-
[categoryId]: !prev[categoryId]
424-
}));
425-
};
426-
427-
const removeTag = (tagToRemove: string) => {
428-
setFavoriteTags(favoriteTags.filter((tag) => tag !== tagToRemove));
429-
};
385+
const sortedLocales = [...locales].sort((a, b) => {
386+
const nameA = languageLabels[a] || a;
387+
const nameB = languageLabels[b] || b;
388+
return nameA.localeCompare(nameB);
389+
});
430390

431391
const handleLogout = async () => {
432392
await logout({
@@ -450,8 +410,6 @@ export function DashboardLayout({ children, locale = "en" }: DashboardLayoutProp
450410
setCurrentLocale(newLocale);
451411
};
452412

453-
const [currentLocale, setCurrentLocale] = useState(locale);
454-
455413
useEffect(() => {
456414
if (locale) {
457415
setCurrentLocale(locale);
@@ -607,185 +565,26 @@ export function DashboardLayout({ children, locale = "en" }: DashboardLayoutProp
607565
};
608566
}, [currentLocale, router]);
609567

610-
const sortedLocales = [...locales].sort((a, b) => {
611-
const nameA = languageLabels[a] || a;
612-
const nameB = languageLabels[b] || b;
613-
return nameA.localeCompare(nameB);
614-
});
615-
616-
const enabledMenuItems: SearchModule[] = displayProject
617-
? navigationCategories.flatMap(category =>
618-
category.items.filter(item =>
619-
'always_visible' in item ? item.always_visible :
620-
'feature_key' in item && item.feature_key && normalizedFeatures[item.feature_key as keyof typeof defaultFeatures]?.enabled
621-
).map(item => ({
622-
key: 'feature_key' in item && item.feature_key ? item.feature_key : item.key,
623-
labelKey: item.labelKey,
624-
path: item.path,
625-
icon: item.icon
626-
}))
627-
)
628-
: [];
629-
630-
const getVisibleCategories = () => {
631-
return navigationCategories.map(category => ({
632-
...category,
633-
items: category.items.filter(item =>
634-
'always_visible' in item ? item.always_visible :
635-
'feature_key' in item && item.feature_key && normalizedFeatures[item.feature_key as keyof typeof defaultFeatures]?.enabled
636-
)
637-
})).filter(category => category.items.length > 0);
638-
};
639-
640-
const visibleCategories = getVisibleCategories();
641-
642568
return (
643569
<div className="flex min-h-screen bg-background">
644570
<SearchModal
645571
isOpen={showSearchModal}
646572
onClose={() => setShowSearchModal(false)}
647-
modules={enabledMenuItems}
573+
modules={[]}
648574
locale={currentLocale}
649575
onSelectModule={(path) => router.push(`/${currentLocale}${path}`)}
650576
/>
651577

652-
<div className="hidden md:flex w-64 flex-col border-r border-border">
653-
<div className="flex h-14 items-center border-b border-border px-4">
654-
<Link href={`/${currentLocale}/dashboard`} className="flex items-center gap-2 font-semibold">
655-
<Lock className="h-5 w-5" />
656-
<span>Zecrypt</span>
657-
</Link>
658-
<div className="ml-auto flex items-center gap-1">
659-
<Button
660-
variant="ghost"
661-
size="icon"
662-
className="h-8 w-8"
663-
onClick={() => document.dispatchEvent(new KeyboardEvent("keydown", { key: "k", metaKey: true }))}
664-
>
665-
<Command className="h-4 w-4" />
666-
</Button>
667-
</div>
668-
</div>
669-
670-
<div className="flex-1 overflow-auto py-2 flex flex-col">
671-
<div className="px-3 py-2">
672-
<div className="mb-4">
673-
<label className="px-2 text-xs font-semibold text-muted-foreground mb-2 block">{translate("project", "dashboard")}</label>
674-
<Button
675-
variant="outline"
676-
className="w-full justify-between hover:bg-accent/50 transition-colors"
677-
onClick={() => setShowProjectDialog(true)}
678-
disabled={!selectedWorkspaceId}
679-
>
680-
<div className="flex items-center gap-2 overflow-hidden">
681-
<div
682-
className="h-4 w-4 rounded-full"
683-
style={{ backgroundColor: displayProject?.color || "#4f46e5" }}
684-
></div>
685-
<span className="truncate">{displayProject?.name || translate("no_project_selected", "dashboard")}</span>
686-
</div>
687-
<ChevronDown className="h-4 w-4 opacity-50" />
688-
</Button>
689-
</div>
690-
691-
<div className="space-y-4">
692-
<Link
693-
href={`/${currentLocale}/dashboard`}
694-
className={cn(
695-
"flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
696-
pathname === `/${currentLocale}/dashboard`
697-
? "bg-primary text-primary-foreground"
698-
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
699-
)}
700-
>
701-
<Home className="h-4 w-4" />
702-
{translate("overview", "dashboard")}
703-
</Link>
704-
705-
{visibleCategories.map((category) => (
706-
<div key={category.id}>
707-
<div className="flex items-center justify-between mb-2">
708-
<h3 className="px-2 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
709-
{translate(category.labelKey, "dashboard")}
710-
</h3>
711-
<Button
712-
variant="ghost"
713-
size="sm"
714-
className="h-5 w-5 p-0 hover:bg-accent/50 transition-colors"
715-
onClick={() => toggleCategory(category.id)}
716-
>
717-
{collapsedCategories[category.id] ? (
718-
<ChevronRight className="h-3 w-3" />
719-
) : (
720-
<ChevronDown className="h-3 w-3" />
721-
)}
722-
</Button>
723-
</div>
724-
<div className={cn(
725-
"space-y-1 transition-all duration-200 ease-in-out overflow-hidden",
726-
!collapsedCategories[category.id]
727-
? "opacity-100 max-h-96"
728-
: "opacity-0 max-h-0"
729-
)}>
730-
{category.items.map((item) => (
731-
<Link
732-
key={item.key}
733-
href={`/${currentLocale}${item.path}`}
734-
className={cn(
735-
"flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
736-
pathname === `/${currentLocale}${item.path}`
737-
? "bg-primary text-primary-foreground"
738-
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
739-
)}
740-
>
741-
{item.icon}
742-
{translate(item.labelKey, "dashboard")}
743-
</Link>
744-
))}
745-
</div>
746-
</div>
747-
))}
748-
</div>
749-
</div>
750-
751-
<div className="p-3 border-t border-border mt-auto">
752-
<DropdownMenu>
753-
<DropdownMenuTrigger asChild>
754-
<div className="flex items-center gap-3 rounded-md px-2 py-1.5 cursor-pointer hover:bg-accent">
755-
<Avatar className="h-8 w-8">
756-
<AvatarImage src={user?.profileImageUrl || "/placeholder.svg?height=32&width=32"} alt={user?.displayName || "User"} />
757-
<AvatarFallback>
758-
{user?.displayName
759-
? user.displayName.split(" ").map((n) => n[0]).join("").toUpperCase().substring(0, 2)
760-
: "U"}
761-
</AvatarFallback>
762-
</Avatar>
763-
<div className="overflow-hidden">
764-
<p className="text-sm font-medium truncate">{user?.displayName || "User"}</p>
765-
<p className="text-xs text-muted-foreground truncate">{user?.primaryEmail || "user@example.com"}</p>
766-
</div>
767-
<ChevronDown className="ml-auto h-4 w-4" />
768-
</div>
769-
</DropdownMenuTrigger>
770-
<DropdownMenuContent align="end" className="w-56">
771-
<DropdownMenuLabel>My Account</DropdownMenuLabel>
772-
<DropdownMenuSeparator />
773-
<DropdownMenuItem asChild>
774-
<Link href={`/${currentLocale}/dashboard/user-settings`}>
775-
<Settings className="mr-2 h-4 w-4" />
776-
<span>{translate("settings", "dashboard")}</span>
777-
</Link>
778-
</DropdownMenuItem>
779-
<DropdownMenuSeparator />
780-
<DropdownMenuItem onClick={handleLogout}>
781-
<LogOut className="mr-2 h-4 w-4" />
782-
<span>{translate("logout", "dashboard")}</span>
783-
</DropdownMenuItem>
784-
</DropdownMenuContent>
785-
</DropdownMenu>
786-
</div>
787-
</div>
788-
</div>
578+
<SidebarNav
579+
currentLocale={currentLocale}
580+
onGeneratePassword={() => setShowGeneratePassword(true)}
581+
onProjectDialog={() => setShowProjectDialog(true)}
582+
onLogout={handleLogout}
583+
onLanguageChange={switchLanguage}
584+
languageLabels={languageLabels}
585+
sortedLocales={sortedLocales}
586+
user={user}
587+
/>
789588

790589
<div className="flex flex-1 flex-col">
791590
<header className="flex h-14 items-center gap-4 border-b border-border px-4 lg:px-6">
@@ -830,11 +629,6 @@ export function DashboardLayout({ children, locale = "en" }: DashboardLayoutProp
830629
</Tooltip>
831630
</TooltipProvider>
832631

833-
{/* <div className="flex items-center gap-2 px-3 py-1.5">
834-
<Users className="h-4 w-4 text-muted-foreground" />
835-
<span className="text-sm font-medium">{workspaceName}</span>
836-
</div> */}
837-
838632
<DropdownMenu>
839633
<DropdownMenuTrigger asChild>
840634
<Button variant="ghost" size="icon" className="rounded-full h-8 w-8">
@@ -874,7 +668,6 @@ export function DashboardLayout({ children, locale = "en" }: DashboardLayoutProp
874668

875669
{showGeneratePassword && <GeneratePasswordDialog onClose={() => setShowGeneratePassword(false)} />}
876670
{showProjectDialog && <ProjectDialog onClose={() => setShowProjectDialog(false)} />}
877-
{/* <KeyboardShortcutsHelp /> */}
878671
</div>
879672
);
880673
}

packages/frontend-web/components/encryption-setup-modal.tsx

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Button } from "@/components/ui/button";
1313
import { Input } from "@/components/ui/input";
1414
import { Label } from "@/components/ui/label";
1515
import { Alert, AlertDescription } from "@/components/ui/alert";
16-
import { AlertTriangle, KeyRound, Eye, EyeOff, Lock } from "lucide-react";
16+
import { AlertTriangle, KeyRound, Eye, EyeOff, Lock, Download, RefreshCw } from "lucide-react";
1717
import { useTranslations } from "next-intl";
1818
import {
1919
generateRsaKeyPair,
@@ -25,6 +25,7 @@ import {
2525
import { updateUserKeys } from "@/libs/api-client";
2626
import { toast } from "@/components/ui/use-toast";
2727
import { secureSetItem } from '@/libs/local-storage-utils';
28+
import { generateStrongPassword } from "@/libs/password-utils";
2829

2930
interface EncryptionSetupModalProps {
3031
isOpen: boolean;
@@ -46,6 +47,22 @@ export function EncryptionSetupModal({
4647
const [error, setError] = useState<string | null>(null);
4748
const [showPassword, setShowPassword] = useState(false);
4849

50+
const handleGeneratePassword = () => {
51+
const generatedPassword = generateStrongPassword();
52+
setPassword(generatedPassword);
53+
setConfirmPassword(generatedPassword);
54+
};
55+
56+
const handleDownloadPassword = () => {
57+
const element = document.createElement("a");
58+
const file = new Blob([`Your Zecrypt Encryption Password:\n\n${password}\n\nIMPORTANT: Keep this password safe. You will need it to access your encrypted data.`], {type: 'text/plain'});
59+
element.href = URL.createObjectURL(file);
60+
element.download = "zecrypt-encryption-password.txt";
61+
document.body.appendChild(element);
62+
element.click();
63+
document.body.removeChild(element);
64+
};
65+
4966
const handleSubmit = async () => {
5067
// Validate password
5168
if (password.length < 8) {
@@ -152,9 +169,21 @@ export function EncryptionSetupModal({
152169
</Alert>
153170

154171
<div className="space-y-2">
155-
<Label htmlFor="password" className="text-sm font-medium">
156-
{tAuth("encryption_password")}
157-
</Label>
172+
<div className="flex items-center justify-between">
173+
<Label htmlFor="password" className="text-sm font-medium">
174+
{tAuth("encryption_password")}
175+
</Label>
176+
<Button
177+
type="button"
178+
variant="outline"
179+
size="sm"
180+
className="h-8 gap-2"
181+
onClick={handleGeneratePassword}
182+
>
183+
<RefreshCw className="h-4 w-4" />
184+
{tAuth("generate_password")}
185+
</Button>
186+
</div>
158187
<div className="relative">
159188
<Input
160189
id="password"
@@ -205,9 +234,23 @@ export function EncryptionSetupModal({
205234
<p className="text-sm font-medium text-destructive">{error}</p>
206235
)}
207236

208-
<p className="text-sm text-muted-foreground">
209-
{tAuth("encryption_password_requirements")}
210-
</p>
237+
<div className="flex items-center justify-between">
238+
<p className="text-sm text-muted-foreground">
239+
{tAuth("encryption_password_requirements")}
240+
</p>
241+
{password && (
242+
<Button
243+
type="button"
244+
variant="outline"
245+
size="sm"
246+
className="h-8 gap-2"
247+
onClick={handleDownloadPassword}
248+
>
249+
<Download className="h-4 w-4" />
250+
{tAuth("download_password")}
251+
</Button>
252+
)}
253+
</div>
211254
</div>
212255

213256
<DialogFooter>

0 commit comments

Comments
 (0)