diff --git a/dashboard/src/features/sidebar/sidebar.tsx b/dashboard/src/features/sidebar/sidebar.tsx
@@ -5,11 +5,9 @@ import {
import { useIsCurrentRoute } from "@marzneshin/hooks";
import type { FC } from "react";
import { sidebarItems as sidebarItemsSudoAdmin, sidebarItemsNonSudoAdmin } from ".";
-import { cn } from "@marzneshin/utils";
+import { projectInfo, cn } from "@marzneshin/utils";
import { useAuth } from "@marzneshin/features/auth";
-import {
- SupportUs,
-} from "@marzneshin/features/support-us";
+import { SupportUs } from "@marzneshin/features/support-us";
interface DashboardSidebarProps {
collapsed: boolean;
@@ -57,9 +55,9 @@ export const DashboardSidebar: FC<DashboardSidebarProps> = ({
</Sidebar.Body>
<Sidebar.Footer>
{collapsed ?
- <SupportUs variant="view" structure="popover" />
+ <SupportUs variant="view" donationLink={projectInfo.donationLink} structure="popover" />
:
- <SupportUs variant="local-storage" structure="card" />
+ <SupportUs variant="local-storage" donationLink={projectInfo.donationLink} structure="card" />
}
</Sidebar.Footer>
</div>
diff --git a/dashboard/src/features/support-us/card.tsx b/dashboard/src/features/support-us/card.tsx
@@ -0,0 +1,47 @@
+import type { FC, HTMLAttributes } from "react";
+import { SupportUsVariation } from "./types";
+import { X, HeartHandshake } from "lucide-react";
+import { Button, Card, CardTitle, CardContent } from "@marzneshin/components";
+import { cn } from "@marzneshin/utils";
+import { useTranslation } from "react-i18next";
+
+export interface SupportUsCardProps extends HTMLAttributes<HTMLDivElement> {
+ variant?: SupportUsVariation;
+ donationLink: string;
+ setSupportUsOpen: (state: boolean) => void;
+}
+
+export const SupportUsCard: FC<SupportUsCardProps> = ({ className, variant, setSupportUsOpen, donationLink }) => {
+ const { t } = useTranslation();
+ return (
+ <Card>
+ <CardContent className={cn("p-4 flex flex-col w-fit gap-2 text-muted-foreground text-sm", className)}>
+ <CardTitle className="flex-row flex items-center justify-between w-full text-primary">
+ <div className="text-medium flex-row flex gap-1">
+ <HeartHandshake /> {t('support-us.title')}
+ </div>
+ {variant !== "view" && (
+ <Button
+ variant="ghost"
+ className="size-6 p-0 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none text-muted-foreground"
+ onMouseDown={() => setSupportUsOpen(false)}
+ >
+ <X className="size-4" />
+ <span className="sr-only">Close</span>
+ </Button>
+ )}
+ </CardTitle>
+ <p>{t('support-us.desc')}</p>
+ <Button size="sm" variant="secondary" className="w-fit" asChild>
+ <a
+ href={donationLink}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {t('support-us.donate')}
+ </a>
+ </Button>
+ </CardContent>
+ </Card>
+ );
+}
diff --git a/dashboard/src/features/support-us/donation-button.tsx b/dashboard/src/features/support-us/donation-button.tsx
@@ -0,0 +1,18 @@
+import { useTranslation } from "react-i18next";
+import { Button } from "@marzneshin/components";
+import type { FC } from "react";
+
+export const DonationButton: FC<{ donationLink: string }> = ({ donationLink }) => {
+ const { t } = useTranslation();
+ return (
+ <Button size="sm" variant="secondary" className="w-fit" asChild>
+ <a
+ href={donationLink}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {t('support-us.donate')}
+ </a>
+ </Button>
+ )
+}
diff --git a/dashboard/src/features/support-us/index.tsx b/dashboard/src/features/support-us/index.tsx
@@ -1,80 +1,31 @@
-import { X, HeartHandshake } from "lucide-react";
-import { Button, Card, CardTitle, CardContent, PopoverTrigger, Popover, PopoverContent } from "@marzneshin/components";
import { type FC, type HTMLAttributes } from "react";
-import { cn } from "@marzneshin/utils";
-import { useTranslation } from "react-i18next";
import { SupportUsVariation, SupportUsStructure } from "./types";
import { useSupportUs } from "./use-support-us";
+import { SupportUsCard } from "./card";
+import { SupportUsPopover } from "./popover";
export interface SupportUsProps extends HTMLAttributes<HTMLDivElement> {
+ donationLink: string;
open?: boolean;
variant?: SupportUsVariation;
structure?: SupportUsStructure;
}
-export const SupportUs: FC<SupportUsProps> = ({ open = true, variant = "status", className, structure = "card" }) => {
+export const SupportUs: FC<SupportUsProps> = ({ open = true, variant = "status", className, structure = "card", donationLink }) => {
const [supportUsOpen, setSupportUsOpen] = useSupportUs(variant, open);
- const { t } = useTranslation();
-
- const CardStructure = () => (
- <Card>
- <CardContent className={cn("p-4 flex flex-col w-fit gap-2 text-muted-foreground text-sm", className)}>
- <CardTitle className="flex-row flex items-center justify-between w-full text-primary">
- <div className="text-medium flex-row flex gap-1">
- <HeartHandshake /> {t('support-us.title')}
- </div>
- {variant !== "view" && (
- <Button
- variant="ghost"
- className="size-6 p-0 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none text-muted-foreground"
- onMouseDown={() => setSupportUsOpen(false)}
- >
- <X className="size-4" />
- <span className="sr-only">Close</span>
- </Button>
- )}
- </CardTitle>
- <p>{t('support-us.desc')}</p>
- <Button size="sm" variant="secondary" className="w-fit" asChild>
- <a
- href="https://github.com/khodedawsh/marzneshin#donation"
- target="_blank"
- rel="noopener noreferrer"
- >
- {t('support-us.donate')}
- </a>
- </Button>
- </CardContent>
- </Card>
- );
-
- const PopoverStructure = () => (
- <Popover>
- <PopoverTrigger asChild>
- <Button size="icon" variant="secondary">
- <HeartHandshake />
- </Button>
- </PopoverTrigger>
- <PopoverContent sideOffset={10} className={cn("p-4 flex flex-col w-80 gap-2 text-muted-foreground text-sm", className)}>
- <div className="flex-row flex items-center justify-between w-full text-primary">
- <div className="text-medium flex-row flex gap-1">
- <HeartHandshake /> {t('support-us.title')}
- </div>
- </div>
- <p>{t('support-us.desc')}</p>
- <Button size="sm" variant="secondary" className="w-fit" asChild>
- <a
- href="https://github.com/khodedawsh/marzneshin#donation"
- target="_blank"
- rel="noopener noreferrer"
- >
- {t('support-us.donate')}
- </a>
- </Button>
- </PopoverContent>
- </Popover>
- );
if (!supportUsOpen) return null;
- return structure === "popover" ? <PopoverStructure /> : <CardStructure />;
+ return structure === "popover" ? (
+ <SupportUsPopover
+ className={className}
+ donationLink={donationLink}
+ />
+ ) : (
+ <SupportUsCard
+ variant={variant}
+ className={className}
+ donationLink={donationLink}
+ setSupportUsOpen={setSupportUsOpen}
+ />
+ );
};
diff --git a/dashboard/src/features/support-us/popover.tsx b/dashboard/src/features/support-us/popover.tsx
@@ -0,0 +1,33 @@
+import { HeartHandshake } from "lucide-react";
+import { Button, PopoverTrigger, Popover, PopoverContent } from "@marzneshin/components";
+import { type FC, type HTMLAttributes } from "react";
+import { cn } from "@marzneshin/utils";
+import { useTranslation } from "react-i18next";
+import { DonationButton } from "./donation-button";
+
+export interface SupportUsPopoverProps extends HTMLAttributes<HTMLDivElement> {
+ donationLink: string;
+}
+
+export const SupportUsPopover: FC<SupportUsPopoverProps> = ({ className, donationLink }) => {
+ const { t } = useTranslation();
+ return (
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button size="icon" variant="secondary">
+ <HeartHandshake />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent sideOffset={10} className={cn("p-4 flex flex-col w-80 gap-2 text-muted-foreground text-sm", className)}>
+ <div className="flex-row flex items-center justify-between w-full text-primary">
+ <div className="text-medium flex-row flex gap-1">
+ <HeartHandshake /> {t('support-us.title')}
+ </div>
+ </div>
+ <p>{t('support-us.desc')}</p>
+ <DonationButton donationLink={donationLink} />
+ </PopoverContent>
+ </Popover>
+ );
+}
+
diff --git a/dashboard/src/features/support-us/support-us.stories.tsx b/dashboard/src/features/support-us/support-us.stories.tsx
@@ -11,7 +11,7 @@ const meta: Meta<typeof SupportUs> = {
export default meta;
type Story = StoryObj<typeof SupportUs>;
-
+const donationLink = "https://localhost.com/donation";
/**
* This variation does not provide X button to close the card.
@@ -19,7 +19,8 @@ type Story = StoryObj<typeof SupportUs>;
export const View: Story = {
args: {
open: true,
- variant: "view"
+ variant: "view",
+ donationLink
}
}
@@ -31,7 +32,9 @@ export const View: Story = {
export const LocalStorage: Story = {
args: {
open: true,
- variant: "local-storage"
+ variant: "local-storage",
+ donationLink
+
}
}
@@ -43,7 +46,8 @@ export const LocalStorage: Story = {
export const Status: Story = {
args: {
open: true,
- variant: "status"
+ variant: "status",
+ donationLink
}
}
@@ -54,8 +58,8 @@ Status.play = async ({ canvasElement }) => {
await expect(canvas.getByText(/Support Us/i)).toBeInTheDocument();
// Check the Donation link
- const donationLink = canvas.getByRole('link', { name: /donate/i });
- await expect(donationLink).toHaveAttribute('href', 'https://github.com/khodedawsh/marzneshin#donation');
+ const donationLinkButton = canvas.getByRole('link', { name: /donate/i });
+ await expect(donationLinkButton).toHaveAttribute('href', donationLink);
// Simulate closing the support card
const closeButton = canvas.getByRole('button', { name: /Close/i });
diff --git a/dashboard/src/features/support-us/support-us.test.tsx b/dashboard/src/features/support-us/support-us.test.tsx
@@ -11,10 +11,12 @@ vi.mock('./use-support-us', () => ({
const useSupportUs = originalUseSupportUs as unknown as ReturnType<typeof vi.fn>;
describe('SupportUs Component', () => {
+ const donationLink = "https://localhost.com/donation";
+
it('renders the SupportUs component when localStorage is true', () => {
useSupportUs.mockReturnValue([true, vi.fn()]);
- render(<SupportUs variant="local-storage" />);
+ render(<SupportUs donationLink={donationLink} variant="local-storage" />);
expect(screen.getByText(/support-us.title/i)).toBeInTheDocument();
expect(screen.getByText(/support-us.desc/i)).toBeInTheDocument();
@@ -24,7 +26,7 @@ describe('SupportUs Component', () => {
it('does not render the SupportUs component when localStorage is false', () => {
useSupportUs.mockReturnValue([false, vi.fn()]);
- render(<SupportUs variant="status" />);
+ render(<SupportUs donationLink={donationLink} variant="status" />);
expect(screen.queryByText(/support-us.title/i)).not.toBeInTheDocument();
});
@@ -33,7 +35,7 @@ describe('SupportUs Component', () => {
const setSupportUsOpen = vi.fn();
useSupportUs.mockReturnValue([true, setSupportUsOpen]);
- render(<SupportUs variant="local-storage" />);
+ render(<SupportUs donationLink={donationLink} variant="local-storage" />);
const closeButton = screen.getByRole('button', { name: /Close/i });
fireEvent.mouseDown(closeButton);
@@ -42,7 +44,7 @@ describe('SupportUs Component', () => {
});
it('renders the SupportUs component in view mode without close button', () => {
- render(<SupportUs variant="view" />);
+ render(<SupportUs donationLink={donationLink} variant="view" />);
expect(screen.getByText(/support-us.title/i)).toBeInTheDocument();
expect(screen.queryByRole('button', { name: /Close/i })).not.toBeInTheDocument();
@@ -51,9 +53,9 @@ describe('SupportUs Component', () => {
it('redirects to the donation link when the donation button is clicked', () => {
useSupportUs.mockReturnValue([true, vi.fn()]);
- render(<SupportUs variant="local-storage" />);
+ render(<SupportUs donationLink={donationLink} variant="local-storage" />);
- const donationLink = screen.getByRole('link', { name: /support-us.donate/i });
- expect(donationLink).toHaveAttribute('href', 'https://github.com/khodedawsh/marzneshin#donation');
+ const donationLinkButton = screen.getByRole('link', { name: /support-us.donate/i });
+ expect(donationLinkButton).toHaveAttribute('href', donationLink);
});
});