Skip to content
Merged
Binary file added apps/desktop2/public/assets/hyprnote-pro.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 added apps/desktop2/public/assets/meet.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 added apps/desktop2/public/assets/webex.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 added apps/desktop2/public/assets/zoom.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/desktop2/src/components/main/body/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ function Header({ tabs }: { tabs: Tab[] }) {
onClick={handleNewNote}
variant="ghost"
size="icon"
className="text-color3"
className="text-neutral-500"
>
<PlusIcon size={16} />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { FloatingButton, formatTime } from "./shared";

type RemoteMeeting =
| { type: "zoom"; url: string | null }
| { type: "google-meet"; url: string | null };
| { type: "google-meet"; url: string | null }
| { type: "webex"; url: string | null };

export function ListenButton({ tab }: { tab: Extract<Tab, { type: "sessions" }> }) {
const { status, loading, stop } = useListener((state) => ({
Expand Down Expand Up @@ -56,7 +57,7 @@ function BeforeMeeingButton({ tab }: { tab: Extract<Tab, { type: "sessions" }> }
return (
<FloatingButton
onClick={handleClick}
icon={<Icon icon="streamline-logos:zoom-logo-1-block" className="w-5 h-5 text-blue-300" />}
icon={<img src="/assets/zoom.png" alt="Zoom" className="w-5 h-5" />}
>
{isNarrow ? "Join & Listen" : "Join Zoom & Start listening"}
</FloatingButton>
Expand All @@ -67,17 +68,27 @@ function BeforeMeeingButton({ tab }: { tab: Extract<Tab, { type: "sessions" }> }
return (
<FloatingButton
onClick={handleClick}
icon={<Icon icon="logos:google-meet" className="w-5 h-5" />}
icon={<img src="/assets/meet.png" alt="Google Meet" className="w-5 h-5" />}
>
{isNarrow ? "Join & Listen" : "Join Google Meet & Start listening"}
</FloatingButton>
);
}

if (remote?.type === "webex") {
return (
<FloatingButton
onClick={handleClick}
icon={<img src="/assets/webex.png" alt="Webex" className="w-5 h-5" />}
>
{isNarrow ? "Join & Listen" : "Join Webex & Start listening"}
</FloatingButton>
);
}

return (
<FloatingButton
onClick={handleClick}
icon={<Icon icon="lucide:mic" className="w-5 h-5" />}
>
Start listening
</FloatingButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { type ReactNode } from "react";

import { Button } from "@hypr/ui/components/ui/button";
import { cn } from "@hypr/ui/lib/utils";

import { type ReactNode } from "react";

export function FloatingButton({
icon,
Expand All @@ -18,17 +17,12 @@ export function FloatingButton({
}) {
return (
<Button
className={cn([
"flex flex-row items-center justify-center gap-1",
"text-white bg-black hover:bg-neutral-800",
"px-4 py-2 rounded-lg shadow-lg",
"min-w-[170px] h-10",
])}
className="shadow-lg"
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{icon && <span className="flex items-center flex-shrink-0 pt-1">{icon}</span>}
{icon || <div className="w-2 h-2 rounded-full bg-red-500 animate-pulse" />}
{children}
</Button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function FolderChain({ sessionId }: { sessionId: string }) {

return (
<Breadcrumb className="ml-1.5">
<BreadcrumbList className="text-color4 text-xs">
<BreadcrumbList className="text-neutral-700 text-xs">
{folderId && <FolderIcon className="w-3 h-3 mr-1" />}
{!folderId
? <RenderIfRootNotExist title={title} handleChangeTitle={handleChangeTitle} sessionId={sessionId} />
Expand Down Expand Up @@ -106,7 +106,7 @@ function RenderIfRootNotExist(
<>
<BreadcrumbItem>
<DropdownMenu>
<DropdownMenuTrigger className="text-color3 hover:text-color4 transition-colors outline-none">
<DropdownMenuTrigger className="text-neutral-500 hover:text-neutral-700 transition-colors outline-none">
Select folder
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
Expand Down Expand Up @@ -143,7 +143,7 @@ function TitleInput({ title, handleChangeTitle }: { title: string; handleChangeT
<input
type="text"
placeholder="Untitled"
className="truncate max-w-[80px] border-none bg-transparent text-color4 focus:outline-none focus:underline"
className="truncate max-w-[80px] border-none bg-transparent text-neutral-700 focus:outline-none focus:underline"
value={title}
onChange={(e) => handleChangeTitle(e.target.value)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function OthersButton(_: { sessionId: string }) {
</DropdownMenuSubContent>
</DropdownMenuSub>

<DropdownMenuItem onClick={handleToggleLock}>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
<LockIcon />
<span>Lock note</span>
<Switch checked={isLocked} onCheckedChange={handleToggleLock} className="ml-auto" />
Expand Down
236 changes: 233 additions & 3 deletions apps/desktop2/src/components/main/body/sessions/outer-header/share.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,239 @@
import { Avatar, AvatarFallback } from "@hypr/ui/components/ui/avatar";
import { Button } from "@hypr/ui/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@hypr/ui/components/ui/popover";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@hypr/ui/components/ui/select";
import { Separator } from "@hypr/ui/components/ui/separator";

import { CircleMinus, Link2Icon, SearchIcon } from "lucide-react";
import { useState } from "react";
import { getInitials } from "../../contacts/shared";

interface Person {
id: string;
name: string;
email?: string;
role: "viewer" | "editor";
isParticipant?: boolean;
}

export function ShareButton(_: { sessionId: string }) {
const [searchQuery, setSearchQuery] = useState("");
const [selectedPeople, setSelectedPeople] = useState<Person[]>([]);
const [invitedPeople, setInvitedPeople] = useState<Person[]>([
{ id: "1", name: "John Doe", email: "john@example.com", role: "editor", isParticipant: true },
{ id: "2", name: "Jane Smith", email: "jane@example.com", role: "editor", isParticipant: true },
]);

const searchResults: Person[] = searchQuery.trim()
? [
{ id: "3", name: "Alice Johnson", email: "alice@example.com", role: "viewer" as const },
{ id: "4", name: "Bob Wilson", email: "bob@example.com", role: "viewer" as const },
].filter((person) => person.name.toLowerCase().includes(searchQuery.toLowerCase()))
: [];

const handleSelectPerson = (person: Person) => {
if (!selectedPeople.find((p) => p.id === person.id)) {
setSelectedPeople([...selectedPeople, person]);
}
setSearchQuery("");
};

const handleRemoveSelected = (personId: string) => {
setSelectedPeople(selectedPeople.filter((p) => p.id !== personId));
};

const handleInvite = () => {
const newInvites = selectedPeople.filter(
(selected) => !invitedPeople.find((invited) => invited.id === selected.id),
);
setInvitedPeople([...invitedPeople, ...newInvites]);
setSelectedPeople([]);
// TODO: Implement actual invite functionality
console.log("Invite:", newInvites);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove console.log statements before merging.

Debug console.log statements should be removed from production code.

Apply this diff:

     setInvitedPeople([...invitedPeople, ...newInvites]);
     setSelectedPeople([]);
-    // TODO: Implement actual invite functionality
-    console.log("Invite:", newInvites);
+    // TODO: Implement actual invite functionality
   };
   const handleCopyLink = () => {
-    // TODO: Implement copy link functionality
-    console.log("Copy link");
+    // TODO: Implement copy link functionality
   };

Also applies to: 67-67

🤖 Prompt for AI Agents
In apps/desktop2/src/components/main/body/sessions/outer-header/share.tsx around
lines 52 and 67, there are debug console.log statements (e.g.,
console.log("Invite:", newInvites)) that must be removed before merging; delete
these console.log calls (or replace them with a proper logger conditional on dev
mode if runtime debug is required), ensure no other stray console.* remains in
the file, and run a quick build/test to confirm no references remain.

};

const handleRemovePerson = (personId: string) => {
setInvitedPeople(invitedPeople.filter((p) => p.id !== personId));
};

const handleRoleChange = (personId: string, role: "viewer" | "editor") => {
setInvitedPeople(
invitedPeople.map((p) => (p.id === personId ? { ...p, role } : p)),
);
};

const handleCopyLink = () => {
// TODO: Implement copy link functionality
console.log("Copy link");
};

return (
<Button size="sm" variant="ghost">
Share
</Button>
<Popover>
<PopoverTrigger asChild>
<Button size="sm" variant="ghost">
Share
</Button>
</PopoverTrigger>

<PopoverContent className="w-[360px] shadow-lg" align="end">
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<div className="flex items-center px-3 py-2 gap-2 rounded-md bg-neutral-50 border border-neutral-200">
<SearchIcon className="size-4 text-neutral-700 flex-shrink-0" />
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search for people"
className="w-full bg-transparent text-sm focus:outline-none placeholder:text-neutral-500"
/>
</div>

{searchQuery.trim() && searchResults.length > 0 && (
<div className="flex flex-col rounded-md border border-neutral-200 overflow-hidden">
{searchResults.map((person) => (
<button
key={person.id}
type="button"
onClick={() => handleSelectPerson(person)}
className="flex items-center justify-between px-3 py-2 text-sm text-left hover:bg-neutral-100 transition-colors w-full disabled:opacity-50"
disabled={selectedPeople.some((p) => p.id === person.id)
|| invitedPeople.some((p) => p.id === person.id)}
>
<div className="flex items-center gap-2">
<Avatar className="size-6">
<AvatarFallback className="text-xs bg-neutral-200 text-neutral-700 font-medium">
{getInitials(person.name)}
</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<span className="font-medium truncate">{person.name}</span>
{person.email && <span className="text-xs text-neutral-500">{person.email}</span>}
</div>
</div>
</button>
))}
</div>
)}
</div>

{selectedPeople.length > 0 && (
<div className="flex flex-col gap-2">
<div className="flex flex-col rounded-md border border-neutral-100 bg-neutral-50 overflow-hidden">
{selectedPeople.map((person) => (
<div
key={person.id}
className="flex items-center justify-between gap-3 py-2 px-3 hover:bg-neutral-100 group transition-colors"
>
<div className="flex items-center gap-2.5 relative min-w-0 flex-1">
<div className="relative size-7 flex items-center justify-center flex-shrink-0">
<div className="absolute inset-0 flex items-center justify-center transition-opacity group-hover:opacity-0">
<Avatar className="size-7">
<AvatarFallback className="text-xs bg-neutral-200 text-neutral-700 font-medium">
{getInitials(person.name)}
</AvatarFallback>
</Avatar>
</div>
<button
onClick={(e) => {
e.stopPropagation();
handleRemoveSelected(person.id);
}}
className="flex items-center justify-center text-red-400 hover:text-red-600 absolute inset-0 rounded-full opacity-0 group-hover:opacity-100 transition-opacity bg-white shadow-sm"
>
<CircleMinus className="size-4" />
</button>
</div>
<div className="flex flex-col min-w-0">
<span className="text-sm font-medium text-neutral-700 truncate">
{person.name}
</span>
{person.email && <span className="text-xs text-neutral-500 truncate">{person.email}</span>}
</div>
</div>
</div>
))}
</div>
</div>
)}

{selectedPeople.length > 0 && (
<Button onClick={handleInvite} className="w-full">
Invite {selectedPeople.length} {selectedPeople.length === 1 ? "person" : "people"}
</Button>
)}

{selectedPeople.length > 0 && <Separator />}

{invitedPeople.length > 0 && (
<div className="flex flex-col gap-2">
<div className="text-xs font-medium text-neutral-500">
People with access
</div>
<div className="flex flex-col rounded-md border border-neutral-100 bg-neutral-50 overflow-hidden max-h-[40vh] overflow-y-auto">
{invitedPeople.map((person) => (
<div
key={person.id}
className="flex items-center justify-between gap-3 py-2 px-3 hover:bg-neutral-100 group transition-colors"
>
<div className="flex items-center gap-2.5 relative min-w-0 flex-1">
<div className="relative size-7 flex items-center justify-center flex-shrink-0">
<div className="absolute inset-0 flex items-center justify-center transition-opacity group-hover:opacity-0">
<Avatar className="size-7">
<AvatarFallback className="text-xs bg-neutral-200 text-neutral-700 font-medium">
{getInitials(person.name)}
</AvatarFallback>
</Avatar>
</div>
<button
onClick={(e) => {
e.stopPropagation();
handleRemovePerson(person.id);
}}
className="flex items-center justify-center text-red-400 hover:text-red-600 absolute inset-0 rounded-full opacity-0 group-hover:opacity-100 transition-opacity bg-white shadow-sm"
>
<CircleMinus className="size-4" />
</button>
</div>
<div className="flex flex-col min-w-0">
<span className="text-sm font-medium text-neutral-700 truncate">
{person.name}
</span>
{person.email && <span className="text-xs text-neutral-500 truncate">{person.email}</span>}
</div>
</div>

<Select
value={person.role}
onValueChange={(value: "viewer" | "editor") => handleRoleChange(person.id, value)}
>
<SelectTrigger
className="w-[100px] h-8 text-xs focus:ring-0 focus:ring-offset-0"
onClick={(e) => e.stopPropagation()}
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="viewer">Viewer</SelectItem>
<SelectItem value="editor">Editor</SelectItem>
</SelectContent>
</Select>
</div>
))}
</div>
</div>
)}

<Button
variant="outline"
className="w-full"
onClick={handleCopyLink}
>
<Link2Icon className="size-4" />
Copy link
</Button>
</div>
</PopoverContent>
</Popover>
);
}
8 changes: 4 additions & 4 deletions apps/desktop2/src/components/main/body/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ export function TabItemBase(
className={clsx([
"flex items-center gap-2 cursor-pointer group",
"w-48 h-full pl-2 pr-1",
"bg-color1 rounded-lg border",
active ? "text-black border-black" : "text-color3 border-transparent",
"bg-neutral-50 rounded-lg border",
active ? "text-black border-black" : "text-neutral-500 border-transparent",
])}
>
<div className="flex items-center gap-2 text-sm flex-1 min-w-0">
Expand All @@ -75,8 +75,8 @@ export function TabItemBase(
className={clsx([
"flex-shrink-0 transition-opacity",
active
? "opacity-100 text-color4"
: "opacity-0 group-hover:opacity-100 text-color3",
? "opacity-100 text-neutral-700"
: "opacity-0 group-hover:opacity-100 text-neutral-500",
])}
size="icon"
variant="ghost"
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop2/src/components/main/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function LeftSidebar() {
"flex flex-row shrink-0",
"flex w-full items-center justify-end h-9 py-1",
"rounded-lg",
"pl-[72px] bg-color1",
"pl-[72px] bg-neutral-50",
])}
>
<Button size="icon" variant="ghost" onClick={leftsidebar.toggleExpanded}>
Expand Down
Loading
Loading