diff --git a/dashboard/public/locales/en.json b/dashboard/public/locales/en.json index dcabd5d..7fb1fea 100644 --- a/dashboard/public/locales/en.json +++ b/dashboard/public/locales/en.json @@ -49,6 +49,8 @@ "system": "System", "disabled": "Disabled", "advanced-options": "Advanced Options", + "language": "Language", + "theme": "Theme", "support-us": { "title": "Support Us", "desc": "Marzneshin is free and open-source software (FOSS), join us and show your support by donating.", diff --git a/dashboard/src/components/header/index.tsx b/dashboard/src/components/header/index.tsx index 5bf28ec..edaa44c 100644 --- a/dashboard/src/components/header/index.tsx +++ b/dashboard/src/components/header/index.tsx @@ -1,27 +1,28 @@ -import { LanguageSwitch } from "@marzneshin/features/language-switch"; -import { ThemeToggle } from "@marzneshin/features/theme-switch"; -import { Logout } from "@marzneshin/features/auth"; +import { HeaderMenu } from "./menu"; +import { GithubRepo } from "@marzneshin/features/github-repo"; import { Link } from "@tanstack/react-router"; import { CommandBox } from "@marzneshin/features/search-command"; import type { FC, PropsWithChildren } from "react"; +import { useScreenBreakpoint } from "@marzneshin/hooks/use-screen-breakpoint"; export const Header: FC = ({ children }) => { - return ( -
-
- -
- M -
- - {children} -
- -
- - - -
-
- ); + const isDesktop = useScreenBreakpoint("md"); + + return ( +
+
+ +
+ M +
+ + {children} + +
+
+ + +
+
+ ); }; diff --git a/dashboard/src/components/header/menu.tsx b/dashboard/src/components/header/menu.tsx new file mode 100644 index 0000000..f155069 --- /dev/null +++ b/dashboard/src/components/header/menu.tsx @@ -0,0 +1,44 @@ +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, + DropdownMenuGroup, + Button, +} from "@marzneshin/components"; +import { FC } from 'react'; +import { MenuIcon } from "lucide-react"; +import { LanguageSwitchMenu } from "@marzneshin/features/language-switch"; +import { ThemeToggle } from "@marzneshin/features/theme-switch"; +import { Logout } from "@marzneshin/features/auth"; + +export const HeaderMenu: FC = () => ( + + + + + + Menu + + + + + + + + + + + + + + +); diff --git a/dashboard/src/components/ui/button/button.stories.tsx b/dashboard/src/components/ui/button/button.stories.tsx index 7c98382..b28232e 100644 --- a/dashboard/src/components/ui/button/button.stories.tsx +++ b/dashboard/src/components/ui/button/button.stories.tsx @@ -16,6 +16,8 @@ type Story = StoryObj; export const Primary: Story = { args: { + variant: "default", + size: "default", onMouseDown: action('clicked'), children: 'Primary' } diff --git a/dashboard/src/components/ui/dropdown-menu.tsx b/dashboard/src/components/ui/dropdown-menu.tsx index 2c4c3d5..000f13c 100644 --- a/dashboard/src/components/ui/dropdown-menu.tsx +++ b/dashboard/src/components/ui/dropdown-menu.tsx @@ -1,6 +1,6 @@ import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" -import { Check, ChevronRight, Circle } from "lucide-react" +import { Check, ChevronLeft, ChevronRight, Circle } from "lucide-react" import { cn } from "@marzneshin/utils" @@ -17,182 +17,186 @@ const DropdownMenuSub = DropdownMenuPrimitive.Sub const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuSubTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } ->(({ className, inset, children, ...props }, ref) => ( - - {children} - - + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + arrowDir?: "right" | "left" + } +>(({ className, inset, children, arrowDir = "right", ...props }, ref) => ( + + {children} + {arrowDir === "right" ? + : + } + )) DropdownMenuSubTrigger.displayName = - DropdownMenuPrimitive.SubTrigger.displayName + DropdownMenuPrimitive.SubTrigger.displayName const DropdownMenuSubContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )) DropdownMenuSubContent.displayName = - DropdownMenuPrimitive.SubContent.displayName + DropdownMenuPrimitive.SubContent.displayName const DropdownMenuContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, sideOffset = 4, ...props }, ref) => ( - - - + + + )) DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName const DropdownMenuItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } >(({ className, inset, ...props }, ref) => ( - + )) DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName const DropdownMenuCheckboxItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, checked, ...props }, ref) => ( - - - - - - - {children} - + + + + + + + {children} + )) DropdownMenuCheckboxItem.displayName = - DropdownMenuPrimitive.CheckboxItem.displayName + DropdownMenuPrimitive.CheckboxItem.displayName const DropdownMenuRadioItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( - - - - - - - {children} - + + + + + + + {children} + )) DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName const DropdownMenuLabel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - inset?: boolean - } + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } >(({ className, inset, ...props }, ref) => ( - + )) DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName const DropdownMenuSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )) DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName const DropdownMenuShortcut = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => { - return ( - - ) + return ( + + ) } DropdownMenuShortcut.displayName = "DropdownMenuShortcut" export { - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuCheckboxItem, - DropdownMenuRadioItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuGroup, - DropdownMenuPortal, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuRadioGroup, + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, } diff --git a/dashboard/src/features/auth/logout/index.tsx b/dashboard/src/features/auth/logout/index.tsx index 16be1a5..7eda4a7 100644 --- a/dashboard/src/features/auth/logout/index.tsx +++ b/dashboard/src/features/auth/logout/index.tsx @@ -12,12 +12,13 @@ export const Logout: FC = () => { }, [navigate]); return ( ); }; diff --git a/dashboard/src/features/github-repo/github-repo.stories.tsx b/dashboard/src/features/github-repo/github-repo.stories.tsx new file mode 100644 index 0000000..9b8ecd5 --- /dev/null +++ b/dashboard/src/features/github-repo/github-repo.stories.tsx @@ -0,0 +1,30 @@ +import { GithubRepo } from './index' +import type { Meta, StoryObj } from "@storybook/react"; + +const meta: Meta = { + title: "Features/Github Repo Stats", + component: GithubRepo, + tags: ["autodocs"] +}; + +export default meta; + +type Story = StoryObj; + +/** + * Github repo present repo fullname, description, and repo stars. + * */ +export const GithubRepoStats: Story = { + args: {} +} + + +/** + * Mini variant of Github repo present repo only the github logo and stars. + * */ +export const GithubRepoStatsMini: Story = { + args: { + variant: "mini" + } +} + diff --git a/dashboard/src/features/github-repo/index.tsx b/dashboard/src/features/github-repo/index.tsx new file mode 100644 index 0000000..1942960 --- /dev/null +++ b/dashboard/src/features/github-repo/index.tsx @@ -0,0 +1,53 @@ +import { + Card, + CardTitle, + CardContent, + CardDescription, + Button, +} from "@marzneshin/components" +import { + useGithubRepoStatsQuery +} from "./services"; +import { type FC } from "react"; +import { + GithubIcon, + StarIcon, +} from "lucide-react"; +import { projectInfo } from "@marzneshin/utils"; + +interface GithubRepoProps { + variant?: "full" | "mini" +} + +export const GithubRepo: FC = ({ variant = "full" }) => { + const { data: stats } = useGithubRepoStatsQuery() + return ( + + ) +} diff --git a/dashboard/src/features/github-repo/services/github-repo.query.ts b/dashboard/src/features/github-repo/services/github-repo.query.ts new file mode 100644 index 0000000..5ffa20e --- /dev/null +++ b/dashboard/src/features/github-repo/services/github-repo.query.ts @@ -0,0 +1,40 @@ +import { useQuery } from "@tanstack/react-query"; +import { projectInfo } from "@marzneshin/utils"; +import { ofetch } from "ofetch"; + +/** + * GithubRepoStatsResponse + * + * Github API repo statistics response + * + * @param full_name Full name of the repo (author/repo-name) + * @param description Github repo description + * @param stargazers_count Github repo star count + */ +export interface GithubRepoStatsResponse { + full_name: string; + description: string; + stargazers_count: number; +} + +export const GithubRepoStatsResponseDefault: GithubRepoStatsResponse = { + full_name: projectInfo.repo, + description: "", + stargazers_count: 0, +} + +export async function fetchGithubRepoStats(): Promise { + return await ofetch(`https://api.github.com/repos/${projectInfo.repo}`, { parseResponse: JSON.parse }).then((result) => { + return result; + }); +} + + +export const useGithubRepoStatsQuery = () => { + return useQuery({ + queryKey: ["github", projectInfo.repo, "stats"], + queryFn: fetchGithubRepoStats, + refetchInterval: 1000 * 60 * 1, // 5min refresh + initialData: GithubRepoStatsResponseDefault + }) +} diff --git a/dashboard/src/features/github-repo/services/index.ts b/dashboard/src/features/github-repo/services/index.ts new file mode 100644 index 0000000..f1f558e --- /dev/null +++ b/dashboard/src/features/github-repo/services/index.ts @@ -0,0 +1 @@ +export * from "./github-repo.query" diff --git a/dashboard/src/features/language-switch/index.tsx b/dashboard/src/features/language-switch/index.tsx index 5862352..51d7271 100644 --- a/dashboard/src/features/language-switch/index.tsx +++ b/dashboard/src/features/language-switch/index.tsx @@ -1,53 +1,52 @@ import { - Button, - DropdownMenu, - DropdownMenuContent, + DropdownMenuSub, + DropdownMenuSubContent, DropdownMenuItem, - DropdownMenuTrigger, + DropdownMenuSubTrigger, + DropdownMenuPortal } from "@marzneshin/components"; import { Languages } from "lucide-react"; import { FC } from "react"; import { useTranslation } from "react-i18next"; +import { cn } from "@marzneshin/utils"; -export const LanguageSwitch: FC = () => { +const LanguageItem = ({ language, title }: { language: string, title: string }) => { const { i18n } = useTranslation(); const changeLanguage = (lang: string) => { i18n.changeLanguage(lang); }; + return ( + changeLanguage(language)} + > + {title} + + ); +} + +export const LanguageSwitchMenu: FC = () => { + const { t } = useTranslation(); return ( - - - - - - changeLanguage("en")}> - English - - changeLanguage("kmr")}> - Kurdî Kurmancî - - changeLanguage("kur")}> - Kurdî Soranî - - changeLanguage("ckb")}> - کوردی سورانی - - changeLanguage("fa")}> - فارسی - - changeLanguage("ru")}> - Русский - - - + + +
+ {t("language")} + +
+
+ + + + + + + + + + +
); }; diff --git a/dashboard/src/features/theme-switch/theme-provider.tsx b/dashboard/src/features/theme-switch/theme-provider.tsx index c19e733..a58239a 100644 --- a/dashboard/src/features/theme-switch/theme-provider.tsx +++ b/dashboard/src/features/theme-switch/theme-provider.tsx @@ -1,6 +1,6 @@ import { createContext, useContext, useEffect, useState } from "react" -type Theme = "dark" | "light" | "system" +export type Theme = "dark" | "light" | "system" type ThemeProviderProps = { children: React.ReactNode diff --git a/dashboard/src/features/theme-switch/theme-toggle/index.tsx b/dashboard/src/features/theme-switch/theme-toggle/index.tsx index 00b4133..6c30410 100644 --- a/dashboard/src/features/theme-switch/theme-toggle/index.tsx +++ b/dashboard/src/features/theme-switch/theme-toggle/index.tsx @@ -1,42 +1,47 @@ import { Moon, Sun } from "lucide-react"; - import { - Button, - DropdownMenu, - DropdownMenuContent, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, DropdownMenuItem, - DropdownMenuTrigger, + DropdownMenuPortal, } from "@marzneshin/components"; -import { useTheme } from "../theme-provider"; +import { useTheme, Theme } from "../theme-provider"; import { useTranslation } from "react-i18next"; +import { cn } from "@marzneshin/utils"; + +const ThemeItem = ({ schema }: { schema: Theme }) => { + const { theme, setTheme } = useTheme(); + const { t } = useTranslation(); + return ( + setTheme(schema)}> + {t(schema)} + + ); +} export function ThemeToggle() { - const { setTheme } = useTheme(); const { t } = useTranslation(); + return ( - - - - - - setTheme("light")}> - {t("light")} - - setTheme("dark")}> - {t("dark")} - - setTheme("system")}> - {t("system")} - - - + + +
+ + +
+ {t('theme')} +
+ + + + + + + +
); + } diff --git a/dashboard/src/globals.css b/dashboard/src/globals.css index 806c598..72b8b04 100644 --- a/dashboard/src/globals.css +++ b/dashboard/src/globals.css @@ -88,6 +88,14 @@ font-family: 'Ubuntu'; } + .vstack { + @apply flex flex-col; + } + + .hstack { + @apply flex flex-row; + } + * { @apply border-border font-body; } diff --git a/dashboard/src/utils/index.ts b/dashboard/src/utils/index.ts index ad6bb6e..d8e6241 100644 --- a/dashboard/src/utils/index.ts +++ b/dashboard/src/utils/index.ts @@ -4,3 +4,4 @@ export * from './cn' export * from './query-client' export * from './subscription-link' export * from './is-online' +export * from './project-info' diff --git a/dashboard/src/utils/project-info.ts b/dashboard/src/utils/project-info.ts new file mode 100644 index 0000000..d8ed728 --- /dev/null +++ b/dashboard/src/utils/project-info.ts @@ -0,0 +1,8 @@ + +export const projectInfo = { + repo: "khodedawsh/marzneshin", + github: "https://github.com/khodedawsh/marzneshin", + donationLink: "https://github.com/khodedawsh/marzneshin#donation", + website: "marzneshin.com", + authors: ["Mardin", "Dawsh"] +}