fix: fix all build blocking errors for bun run build
This commit is contained in:
@@ -18,7 +18,6 @@
|
|||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"@tanstack/react-query": "^5.90.2",
|
"@tanstack/react-query": "^5.90.2",
|
||||||
"@tanstack/react-query-devtools": "^5.90.2",
|
"@tanstack/react-query-devtools": "^5.90.2",
|
||||||
"bcrypt": "^6.0.0",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@@ -501,8 +500,6 @@
|
|||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.7", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.7", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ=="],
|
||||||
|
|
||||||
"bcrypt": ["bcrypt@6.0.0", "", { "dependencies": { "node-addon-api": "^8.3.0", "node-gyp-build": "^4.8.4" } }, "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg=="],
|
|
||||||
|
|
||||||
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
|
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
|
||||||
|
|
||||||
"bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
|
"bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
|
||||||
@@ -1051,9 +1048,7 @@
|
|||||||
|
|
||||||
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
||||||
|
|
||||||
"node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
|
"node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="],
|
||||||
|
|
||||||
"node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
|
|
||||||
|
|
||||||
"node-releases": ["node-releases@2.0.21", "", {}, "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw=="],
|
"node-releases": ["node-releases@2.0.21", "", {}, "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw=="],
|
||||||
|
|
||||||
@@ -1431,8 +1426,6 @@
|
|||||||
|
|
||||||
"hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
"hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||||
|
|
||||||
"iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="],
|
|
||||||
|
|
||||||
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||||
|
|
||||||
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"@tanstack/react-query": "^5.90.2",
|
"@tanstack/react-query": "^5.90.2",
|
||||||
"@tanstack/react-query-devtools": "^5.90.2",
|
"@tanstack/react-query-devtools": "^5.90.2",
|
||||||
"bcrypt": "^6.0.0",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router";
|
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router";
|
||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router";
|
|
||||||
import { ChevronDown, ChevronRight, Plus, Edit } from "lucide-react";
|
import { ChevronDown, ChevronRight, Plus, Edit } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +9,6 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { CategoryWithChannels } from "@/types/api";
|
import { CategoryWithChannels } from "@/types/api";
|
||||||
import { Channel } from "@/types/database";
|
|
||||||
import ChannelItem from "@/components/channel/ChannelItem";
|
import ChannelItem from "@/components/channel/ChannelItem";
|
||||||
|
|
||||||
interface CategoryHeaderProps {
|
interface CategoryHeaderProps {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const Avatar: React.FC<AvatarProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserInitials = (username: string, nickname?: string) => {
|
const getUserInitials = (username: string, nickname: string | null) => {
|
||||||
const name = nickname || username;
|
const name = nickname || username;
|
||||||
return name
|
return name
|
||||||
.split(" ")
|
.split(" ")
|
||||||
@@ -116,7 +116,7 @@ const Avatar: React.FC<AvatarProps> = ({
|
|||||||
size === "xl" && "text-lg",
|
size === "xl" && "text-lg",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getUserInitials(user.username, user.nickname)}
|
{getUserInitials(user.username, user.nickname ? user.nickname : null)}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</ShadcnAvatar>
|
</ShadcnAvatar>
|
||||||
|
|
||||||
|
|||||||
@@ -1,276 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
|
||||||
|
|
||||||
// Basic dropdown styles
|
|
||||||
const dropdownStyles = {
|
|
||||||
content: `
|
|
||||||
min-w-[220px]
|
|
||||||
bg-white dark:bg-gray-800
|
|
||||||
border border-gray-200 dark:border-gray-700
|
|
||||||
rounded-md
|
|
||||||
p-1
|
|
||||||
shadow-lg
|
|
||||||
z-50
|
|
||||||
animate-in
|
|
||||||
fade-in-80
|
|
||||||
data-[state=open]:animate-in
|
|
||||||
data-[state=closed]:animate-out
|
|
||||||
data-[state=closed]:fade-out-0
|
|
||||||
data-[state=open]:fade-in-0
|
|
||||||
data-[state=closed]:zoom-out-95
|
|
||||||
data-[state=open]:zoom-in-95
|
|
||||||
data-[side=bottom]:slide-in-from-top-2
|
|
||||||
data-[side=left]:slide-in-from-right-2
|
|
||||||
data-[side=right]:slide-in-from-left-2
|
|
||||||
data-[side=top]:slide-in-from-bottom-2
|
|
||||||
`,
|
|
||||||
item: `
|
|
||||||
relative
|
|
||||||
flex
|
|
||||||
cursor-pointer
|
|
||||||
select-none
|
|
||||||
items-center
|
|
||||||
rounded-sm
|
|
||||||
px-2
|
|
||||||
py-1.5
|
|
||||||
text-sm
|
|
||||||
outline-none
|
|
||||||
transition-colors
|
|
||||||
hover:bg-gray-100 dark:hover:bg-gray-700
|
|
||||||
focus:bg-gray-100 dark:focus:bg-gray-700
|
|
||||||
data-[disabled]:pointer-events-none
|
|
||||||
data-[disabled]:opacity-50
|
|
||||||
`,
|
|
||||||
separator: `
|
|
||||||
-mx-1
|
|
||||||
my-1
|
|
||||||
h-px
|
|
||||||
bg-gray-200 dark:bg-gray-700
|
|
||||||
`,
|
|
||||||
label: `
|
|
||||||
px-2
|
|
||||||
py-1.5
|
|
||||||
text-sm
|
|
||||||
font-semibold
|
|
||||||
text-gray-500 dark:text-gray-400
|
|
||||||
`,
|
|
||||||
destructive: `
|
|
||||||
text-red-600 dark:text-red-400
|
|
||||||
hover:bg-red-50 dark:hover:bg-red-900/20
|
|
||||||
focus:bg-red-50 dark:focus:bg-red-900/20
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Utility to combine class names
|
|
||||||
const cn = (...classes: (string | undefined | false)[]) => {
|
|
||||||
return classes.filter(Boolean).join(" ");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Root dropdown menu
|
|
||||||
export const DirectDropdownMenu = DropdownMenu.Root;
|
|
||||||
export const DirectDropdownMenuTrigger = DropdownMenu.Trigger;
|
|
||||||
|
|
||||||
// Content component with styling
|
|
||||||
interface DirectDropdownMenuContentProps
|
|
||||||
extends React.ComponentProps<typeof DropdownMenu.Content> {
|
|
||||||
className?: string;
|
|
||||||
sideOffset?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DirectDropdownMenuContent = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenu.Content>,
|
|
||||||
DirectDropdownMenuContentProps
|
|
||||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
||||||
<DropdownMenu.Portal>
|
|
||||||
<DropdownMenu.Content
|
|
||||||
ref={ref}
|
|
||||||
sideOffset={sideOffset}
|
|
||||||
className={cn(
|
|
||||||
dropdownStyles.content.replace(/\s+/g, " ").trim(),
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</DropdownMenu.Portal>
|
|
||||||
));
|
|
||||||
DirectDropdownMenuContent.displayName = "DirectDropdownMenuContent";
|
|
||||||
|
|
||||||
// Menu item component
|
|
||||||
interface DirectDropdownMenuItemProps
|
|
||||||
extends React.ComponentProps<typeof DropdownMenu.Item> {
|
|
||||||
className?: string;
|
|
||||||
destructive?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DirectDropdownMenuItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenu.Item>,
|
|
||||||
DirectDropdownMenuItemProps
|
|
||||||
>(({ className, destructive, ...props }, ref) => (
|
|
||||||
<DropdownMenu.Item
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
dropdownStyles.item.replace(/\s+/g, " ").trim(),
|
|
||||||
destructive && dropdownStyles.destructive.replace(/\s+/g, " ").trim(),
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
DirectDropdownMenuItem.displayName = "DirectDropdownMenuItem";
|
|
||||||
|
|
||||||
// Separator component
|
|
||||||
interface DirectDropdownMenuSeparatorProps
|
|
||||||
extends React.ComponentProps<typeof DropdownMenu.Separator> {
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DirectDropdownMenuSeparator = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenu.Separator>,
|
|
||||||
DirectDropdownMenuSeparatorProps
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<DropdownMenu.Separator
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
dropdownStyles.separator.replace(/\s+/g, " ").trim(),
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
DirectDropdownMenuSeparator.displayName = "DirectDropdownMenuSeparator";
|
|
||||||
|
|
||||||
// Label component
|
|
||||||
interface DirectDropdownMenuLabelProps
|
|
||||||
extends React.ComponentProps<typeof DropdownMenu.Label> {
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DirectDropdownMenuLabel = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenu.Label>,
|
|
||||||
DirectDropdownMenuLabelProps
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<DropdownMenu.Label
|
|
||||||
ref={ref}
|
|
||||||
className={cn(dropdownStyles.label.replace(/\s+/g, " ").trim(), className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
DirectDropdownMenuLabel.displayName = "DirectDropdownMenuLabel";
|
|
||||||
|
|
||||||
// Checkbox item component
|
|
||||||
interface DirectDropdownMenuCheckboxItemProps
|
|
||||||
extends React.ComponentProps<typeof DropdownMenu.CheckboxItem> {
|
|
||||||
className?: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DirectDropdownMenuCheckboxItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenu.CheckboxItem>,
|
|
||||||
DirectDropdownMenuCheckboxItemProps
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
dropdownStyles.item.replace(/\s+/g, " ").trim(),
|
|
||||||
"pl-8",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
||||||
<DropdownMenu.ItemIndicator>
|
|
||||||
<CheckIcon className="h-4 w-4" />
|
|
||||||
</DropdownMenu.ItemIndicator>
|
|
||||||
</span>
|
|
||||||
{children}
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
));
|
|
||||||
DirectDropdownMenuCheckboxItem.displayName = "DirectDropdownMenuCheckboxItem";
|
|
||||||
|
|
||||||
// Sub menu components
|
|
||||||
export const DirectDropdownMenuSub = DropdownMenu.Sub;
|
|
||||||
|
|
||||||
interface DirectDropdownMenuSubTriggerProps
|
|
||||||
extends React.ComponentProps<typeof DropdownMenu.SubTrigger> {
|
|
||||||
className?: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DirectDropdownMenuSubTrigger = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenu.SubTrigger>,
|
|
||||||
DirectDropdownMenuSubTriggerProps
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<DropdownMenu.SubTrigger
|
|
||||||
ref={ref}
|
|
||||||
className={cn(dropdownStyles.item.replace(/\s+/g, " ").trim(), className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
|
||||||
</DropdownMenu.SubTrigger>
|
|
||||||
));
|
|
||||||
DirectDropdownMenuSubTrigger.displayName = "DirectDropdownMenuSubTrigger";
|
|
||||||
|
|
||||||
interface DirectDropdownMenuSubContentProps
|
|
||||||
extends React.ComponentProps<typeof DropdownMenu.SubContent> {
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DirectDropdownMenuSubContent = React.forwardRef<
|
|
||||||
React.ElementRef<typeof DropdownMenu.SubContent>,
|
|
||||||
DirectDropdownMenuSubContentProps
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<DropdownMenu.SubContent
|
|
||||||
ref={ref}
|
|
||||||
className={cn(
|
|
||||||
dropdownStyles.content.replace(/\s+/g, " ").trim(),
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
DirectDropdownMenuSubContent.displayName = "DirectDropdownMenuSubContent";
|
|
||||||
|
|
||||||
// Example usage component to test the dropdown
|
|
||||||
export const DirectDropdownExample: React.FC = () => {
|
|
||||||
const [isOpen, setIsOpen] = React.useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-4">
|
|
||||||
<DirectDropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
|
||||||
<DirectDropdownMenuTrigger asChild>
|
|
||||||
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
|
||||||
Open Menu
|
|
||||||
</button>
|
|
||||||
</DirectDropdownMenuTrigger>
|
|
||||||
|
|
||||||
<DirectDropdownMenuContent align="end" className="w-56">
|
|
||||||
<DirectDropdownMenuLabel>My Account</DirectDropdownMenuLabel>
|
|
||||||
<DirectDropdownMenuSeparator />
|
|
||||||
|
|
||||||
<DirectDropdownMenuItem
|
|
||||||
onClick={() => console.log("Profile clicked")}
|
|
||||||
>
|
|
||||||
<span>Profile</span>
|
|
||||||
</DirectDropdownMenuItem>
|
|
||||||
|
|
||||||
<DirectDropdownMenuItem
|
|
||||||
onClick={() => console.log("Settings clicked")}
|
|
||||||
>
|
|
||||||
<span>Settings</span>
|
|
||||||
</DirectDropdownMenuItem>
|
|
||||||
|
|
||||||
<DirectDropdownMenuSeparator />
|
|
||||||
|
|
||||||
<DirectDropdownMenuItem
|
|
||||||
destructive
|
|
||||||
onClick={() => console.log("Logout clicked")}
|
|
||||||
>
|
|
||||||
<span>Log out</span>
|
|
||||||
</DirectDropdownMenuItem>
|
|
||||||
</DirectDropdownMenuContent>
|
|
||||||
</DirectDropdownMenu>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
// src/components/layout/MemberList.tsx - Enhanced with role management
|
import React from "react";
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { Crown, Shield, UserIcon } from "lucide-react";
|
import { Crown, Shield, UserIcon } from "lucide-react";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
@@ -28,6 +27,7 @@ interface MemberItemProps {
|
|||||||
member: User;
|
member: User;
|
||||||
instanceId: string;
|
instanceId: string;
|
||||||
isOwner?: boolean;
|
isOwner?: boolean;
|
||||||
|
currentUserRole: "member" | "mod" | "admin";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the user's role for this specific instance
|
// Get the user's role for this specific instance
|
||||||
@@ -155,7 +155,7 @@ const MemberList: React.FC = () => {
|
|||||||
if (!acc[roleInfo.name]) {
|
if (!acc[roleInfo.name]) {
|
||||||
acc[roleInfo.name] = [];
|
acc[roleInfo.name] = [];
|
||||||
}
|
}
|
||||||
acc[roleInfo.name].push(member);
|
acc[roleInfo.name].push(member as User);
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{} as Record<string, User[]>,
|
{} as Record<string, User[]>,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { useUiStore } from "@/stores/uiStore";
|
|||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
import ServerIcon from "@/components/server/ServerIcon";
|
import ServerIcon from "@/components/server/ServerIcon";
|
||||||
import { getAccessibleInstances, isGlobalAdmin } from "@/utils/permissions";
|
import { getAccessibleInstances, isGlobalAdmin } from "@/utils/permissions";
|
||||||
import { CreateServerModal } from "../modals/CreateServerModal";
|
|
||||||
|
|
||||||
const ServerSidebar: React.FC = () => {
|
const ServerSidebar: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { Settings, Mic, MicOff, Headphones } from "lucide-react";
|
import { Settings, Mic, MicOff, Headphones } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
|||||||
@@ -9,17 +9,8 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Hash, Volume2, Loader2 } from "lucide-react";
|
import { Hash, Volume2, Loader2 } from "lucide-react";
|
||||||
import { useCreateChannel } from "@/hooks/useServers";
|
import { useCreateChannel } from "@/hooks/useServers";
|
||||||
import { useCategoriesByInstance } from "@/hooks/useCategories"; // New hook
|
|
||||||
import { CategoryWithChannels } from "@/types/api";
|
|
||||||
|
|
||||||
interface CreateChannelModalProps {
|
interface CreateChannelModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -31,7 +22,6 @@ interface CreateChannelModalProps {
|
|||||||
export const CreateChannelModal: React.FC<CreateChannelModalProps> = ({
|
export const CreateChannelModal: React.FC<CreateChannelModalProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
instanceId,
|
|
||||||
defaultCategoryId,
|
defaultCategoryId,
|
||||||
}) => {
|
}) => {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
@@ -39,25 +29,8 @@ export const CreateChannelModal: React.FC<CreateChannelModalProps> = ({
|
|||||||
const [type, setType] = useState<"text" | "voice">("text");
|
const [type, setType] = useState<"text" | "voice">("text");
|
||||||
const [categoryId, setCategoryId] = useState(defaultCategoryId || "");
|
const [categoryId, setCategoryId] = useState(defaultCategoryId || "");
|
||||||
|
|
||||||
// Fetch categories using the new API
|
|
||||||
const {
|
|
||||||
data: categories,
|
|
||||||
isLoading: categoriesLoading,
|
|
||||||
error: categoriesError,
|
|
||||||
} = useCategoriesByInstance(instanceId);
|
|
||||||
|
|
||||||
const createChannelMutation = useCreateChannel();
|
const createChannelMutation = useCreateChannel();
|
||||||
|
|
||||||
// Update categoryId when defaultCategoryId changes or categories load
|
|
||||||
useEffect(() => {
|
|
||||||
if (defaultCategoryId) {
|
|
||||||
setCategoryId(defaultCategoryId);
|
|
||||||
} else if (categories && categories.length > 0 && !categoryId) {
|
|
||||||
// Auto-select first category if none selected
|
|
||||||
setCategoryId(categories[0].id);
|
|
||||||
}
|
|
||||||
}, [defaultCategoryId, categories, categoryId]);
|
|
||||||
|
|
||||||
// Reset form when modal opens/closes
|
// Reset form when modal opens/closes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
@@ -98,12 +71,6 @@ export const CreateChannelModal: React.FC<CreateChannelModalProps> = ({
|
|||||||
<DialogTitle>Create Channel</DialogTitle>
|
<DialogTitle>Create Channel</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{categoriesError && (
|
|
||||||
<div className="p-3 text-sm text-destructive bg-destructive/10 rounded-md">
|
|
||||||
Failed to load categories. Please try again.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Channel Type</Label>
|
<Label>Channel Type</Label>
|
||||||
@@ -151,46 +118,6 @@ export const CreateChannelModal: React.FC<CreateChannelModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label>Category</Label>
|
|
||||||
<Select
|
|
||||||
value={categoryId}
|
|
||||||
onValueChange={setCategoryId}
|
|
||||||
required
|
|
||||||
disabled={categoriesLoading}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={
|
|
||||||
categoriesLoading
|
|
||||||
? "Loading categories..."
|
|
||||||
: "Select a category"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{categoriesLoading ? (
|
|
||||||
<SelectItem value="loading" disabled>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
) : categories && categories.length > 0 ? (
|
|
||||||
categories.map((category) => (
|
|
||||||
<SelectItem key={category.id} value={category.id}>
|
|
||||||
{category.name}
|
|
||||||
</SelectItem>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<SelectItem value="no-categories" disabled>
|
|
||||||
No categories available
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<Button type="button" variant="outline" onClick={onClose}>
|
<Button type="button" variant="outline" onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
@@ -201,7 +128,6 @@ export const CreateChannelModal: React.FC<CreateChannelModalProps> = ({
|
|||||||
!name.trim() ||
|
!name.trim() ||
|
||||||
!categoryId ||
|
!categoryId ||
|
||||||
createChannelMutation.isPending ||
|
createChannelMutation.isPending ||
|
||||||
categoriesLoading ||
|
|
||||||
categoryId === "loading" ||
|
categoryId === "loading" ||
|
||||||
categoryId === "no-categories"
|
categoryId === "no-categories"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export const CreateServerModal: React.FC = () => {
|
|||||||
{
|
{
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
icon: icon.trim() || undefined,
|
icon: icon.trim() || undefined,
|
||||||
description: description.trim() || undefined,
|
// description: description.trim() || undefined,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
||||||
import { categoryApi } from "@/lib/api-client";
|
|
||||||
|
|
||||||
// Get categories by instance ID
|
|
||||||
export const useCategoriesByInstance = (instanceId: string | undefined) => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ["categories", instanceId],
|
|
||||||
queryFn: () =>
|
|
||||||
instanceId
|
|
||||||
? categoryApi.getCategoriesByInstance(instanceId)
|
|
||||||
: Promise.resolve([]),
|
|
||||||
enabled: !!instanceId,
|
|
||||||
staleTime: 500 * 1, // 5 minutes
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get single category by ID
|
|
||||||
export const useCategoryById = (categoryId: string | undefined) => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: ["category", categoryId],
|
|
||||||
queryFn: () =>
|
|
||||||
categoryId
|
|
||||||
? categoryApi.getCategoryById(categoryId)
|
|
||||||
: Promise.resolve(null),
|
|
||||||
enabled: !!categoryId,
|
|
||||||
staleTime: 500 * 1,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create category mutation
|
|
||||||
export const useCreateCategory = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: categoryApi.createCategory,
|
|
||||||
onSuccess: (data, variables) => {
|
|
||||||
// Invalidate categories for the instance
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["categories", variables.instanceId],
|
|
||||||
});
|
|
||||||
// Also invalidate instance details to refresh the sidebar
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["instance", variables.instanceId],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error("Failed to create category:", error);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update category mutation
|
|
||||||
export const useUpdateCategory = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({
|
|
||||||
categoryId,
|
|
||||||
...data
|
|
||||||
}: {
|
|
||||||
categoryId: string;
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
position?: number;
|
|
||||||
userId: string;
|
|
||||||
}) => categoryApi.updateCategory(categoryId, data),
|
|
||||||
onSuccess: (data, variables) => {
|
|
||||||
// Update the specific category in cache
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["category", variables.categoryId],
|
|
||||||
});
|
|
||||||
// Invalidate categories list
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["categories"] });
|
|
||||||
// Invalidate instance details
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["instance"] });
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error("Failed to update category:", error);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Delete category mutation
|
|
||||||
export const useDeleteCategory = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({
|
|
||||||
categoryId,
|
|
||||||
userId,
|
|
||||||
}: {
|
|
||||||
categoryId: string;
|
|
||||||
userId: string;
|
|
||||||
}) => categoryApi.deleteCategory(categoryId, userId),
|
|
||||||
onSuccess: (data, variables) => {
|
|
||||||
// Remove the category from cache
|
|
||||||
queryClient.removeQueries({
|
|
||||||
queryKey: ["category", variables.categoryId],
|
|
||||||
});
|
|
||||||
// Invalidate categories list
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["categories"] });
|
|
||||||
// Invalidate instance details
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["instance"] });
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error("Failed to delete category:", error);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Delete all categories by instance mutation
|
|
||||||
export const useDeleteAllCategoriesByInstance = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: ({
|
|
||||||
instanceId,
|
|
||||||
userId,
|
|
||||||
}: {
|
|
||||||
instanceId: string;
|
|
||||||
userId: string;
|
|
||||||
}) => categoryApi.deleteAllCategoriesByInstance(instanceId, userId),
|
|
||||||
onSuccess: (data, variables) => {
|
|
||||||
// Remove all categories for this instance from cache
|
|
||||||
queryClient.removeQueries({
|
|
||||||
queryKey: ["categories", variables.instanceId],
|
|
||||||
});
|
|
||||||
queryClient.removeQueries({ queryKey: ["category"] });
|
|
||||||
// Invalidate instance details
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["instance", variables.instanceId],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
console.error("Failed to delete all categories:", error);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -187,7 +187,7 @@ const MessageComponent: React.FC<MessageProps> = ({
|
|||||||
<div className="text-concord-primary leading-relaxed prose prose-sm max-w-none dark:prose-invert">
|
<div className="text-concord-primary leading-relaxed prose prose-sm max-w-none dark:prose-invert">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
components={{
|
components={{
|
||||||
code: ({ node, className, children, ...props }) => {
|
code: ({ className, children }) => {
|
||||||
const match = /language-(\w+)/.exec(className || "");
|
const match = /language-(\w+)/.exec(className || "");
|
||||||
return match ? (
|
return match ? (
|
||||||
<div className="flex flex-row flex-1 max-w-2/3 flex-wrap !bg-transparent">
|
<div className="flex flex-row flex-1 max-w-2/3 flex-wrap !bg-transparent">
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
User,
|
User,
|
||||||
Shield,
|
Shield,
|
||||||
Mic,
|
Mic,
|
||||||
Eye,
|
|
||||||
Settings,
|
Settings,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Moon,
|
Moon,
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import { BackendUser } from "@/lib/api-client";
|
|
||||||
import { Message } from "./database";
|
|
||||||
|
|
||||||
// API types
|
// API types
|
||||||
export type {
|
export type {
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
@@ -39,8 +36,3 @@ export interface User {
|
|||||||
role: string;
|
role: string;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message with user for chat components
|
|
||||||
export interface MessageWithUser extends Message {
|
|
||||||
user?: User | BackendUser;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { User, Role } from "@/types/database";
|
import { User } from "@/types/database";
|
||||||
|
|
||||||
export type UserPermission =
|
export type UserPermission =
|
||||||
| "view_instance"
|
| "view_instance"
|
||||||
|
|||||||
Reference in New Issue
Block a user