+
+
+
+
-
-
-
Vite + React
-
-
-
This is a template application for React and Vite.
-
- There are some test components on the site. Please clear the content and put down your code.
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
{t(`home.section1`, { ns: ['main'] })}
+
{t(`home.section2`, { ns: ['main'] })}
+
+ } />
+ } />
+ } />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+
+
-
-
-
+
+
+
)
}
diff --git a/src/components/app/loading-screen.tsx b/src/components/app/loading-screen.tsx
new file mode 100644
index 0000000..2d28a28
--- /dev/null
+++ b/src/components/app/loading-screen.tsx
@@ -0,0 +1,14 @@
+import { Spinner } from '@/components/custom/spinner'
+import React from 'react'
+
+export function LoadingScreen() {
+ return (
+
+ )
+}
+
+export const LoadingScreenMemo = React.memo(LoadingScreen)
diff --git a/src/components/app/menu-item.ts b/src/components/app/menu-item.ts
new file mode 100644
index 0000000..351b281
--- /dev/null
+++ b/src/components/app/menu-item.ts
@@ -0,0 +1,5 @@
+export type MenuItem = {
+ readonly key: string
+ readonly route: string
+ readonly restricted: boolean
+}
diff --git a/src/components/app/nav-item.tsx b/src/components/app/nav-item.tsx
new file mode 100644
index 0000000..5c61992
--- /dev/null
+++ b/src/components/app/nav-item.tsx
@@ -0,0 +1,21 @@
+import { useNavigate } from 'react-router-dom'
+import { useLocation } from 'react-router'
+import { NavigationMenuItem, NavigationMenuLink, navigationMenuTriggerStyle } from '@/components/ui/navigation-menu'
+import { MenuItem } from '@/components/app/menu-item'
+
+export function NavMenuItem({ item }: { item: MenuItem }) {
+ const navigate = useNavigate()
+ const location = useLocation()
+
+ return (
+
+ navigate(item.route)}
+ >
+ {item.key}
+
+
+ )
+}
diff --git a/src/components/app/side-menu.tsx b/src/components/app/side-menu.tsx
new file mode 100644
index 0000000..86c70df
--- /dev/null
+++ b/src/components/app/side-menu.tsx
@@ -0,0 +1,56 @@
+import { MenuItem } from '@/components/app/menu-item'
+import { Sheet, SheetContent, SheetTrigger, SheetTitle, SheetDescription, SheetClose } from '@/components/ui/sheet'
+import { Button } from '@/components/ui/button'
+import { Label } from '@/components/ui/label'
+import { Menu } from 'lucide-react'
+import { useNavigate } from 'react-router-dom'
+import { useLocation } from 'react-router'
+
+function SideMenuItem({ item }: { item: MenuItem }) {
+ const navigate = useNavigate()
+ const location = useLocation()
+ return (
+
+
+
+ )
+}
+
+export interface SideMenuItemProps {
+ items: MenuItem[]
+ authenticated: boolean
+}
+
+export function SideMenu({ items, authenticated }: SideMenuItemProps) {
+ return (
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/app/user-dropdown-menu.tsx b/src/components/app/user-dropdown-menu.tsx
new file mode 100644
index 0000000..9b772c9
--- /dev/null
+++ b/src/components/app/user-dropdown-menu.tsx
@@ -0,0 +1,112 @@
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuPortal,
+ DropdownMenuSeparator,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu'
+import { Button } from '@/components/ui/button'
+import { Languages, LogOut, Settings, User, UserCircle, UserPlus } from 'lucide-react'
+import { useKeycloak } from '@react-keycloak/web'
+import { useTranslation } from 'react-i18next'
+
+export interface UserDropdownMenuProps {
+ handleDropdownSelectFn: (action: string) => void
+}
+
+export function UserDropdownMenu({ handleDropdownSelectFn }: UserDropdownMenuProps) {
+ const { keycloak } = useKeycloak()
+ const { t } = useTranslation(['main'])
+
+ return (
+
+
+
+
+ {keycloak.authenticated ? (
+ <>
+
+ {t('user_dropdown.my_account', { ns: ['main'] })}
+
+ handleDropdownSelectFn('profile')}>
+
+ {t('user_dropdown.profile', { ns: ['main'] })}
+
+ handleDropdownSelectFn('settings')}>
+
+ {t('user_dropdown.settings', { ns: ['main'] })}
+
+
+
+
+
+ {t('user_dropdown.select_language', { ns: ['main'] })}
+
+
+
+ handleDropdownSelectFn('lang/english')}>
+ {t('user_dropdown.en', { ns: ['main'] })}
+
+ handleDropdownSelectFn('lang/polish')}>
+ {t('user_dropdown.pl', { ns: ['main'] })}
+
+
+
+
+
+ {
+ keycloak.logout()
+ }}
+ >
+
+ {t('user_dropdown.logout', { ns: ['main'] })}
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+ {t('user_dropdown.select_language', { ns: ['main'] })}
+
+
+
+ handleDropdownSelectFn('lang/english')}>
+ {t('user_dropdown.en', { ns: ['main'] })}
+
+ handleDropdownSelectFn('lang/polish')}>
+ {t('user_dropdown.pl', { ns: ['main'] })}
+
+
+
+
+
+ {
+ keycloak.login()
+ }}
+ >
+
+ {t('user_dropdown.login', { ns: ['main'] })}
+
+ keycloak.register()}>
+
+ {t('user_dropdown.register', { ns: ['main'] })}
+
+
+ >
+ )}
+
+ )
+}
diff --git a/src/components/custom/coockie-consent.tsx b/src/components/custom/coockie-consent.tsx
new file mode 100644
index 0000000..9c06cf3
--- /dev/null
+++ b/src/components/custom/coockie-consent.tsx
@@ -0,0 +1,99 @@
+import { CookieIcon } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+import { cn } from '@/lib/utils'
+import { useState, useEffect } from 'react'
+import { useTranslation } from 'react-i18next'
+
+export interface CookieConsentProps {
+ demo?: boolean
+ onAcceptCallback?: () => void
+ onDeclineCallback?: () => void
+}
+
+export default function CookieConsent({ demo, onAcceptCallback, onDeclineCallback }: CookieConsentProps) {
+ const [isOpen, setIsOpen] = useState(false)
+ const [hide, setHide] = useState(false)
+ const { t } = useTranslation(['main'])
+
+ const accept = () => {
+ setIsOpen(false)
+ document.cookie = 'cookieConsent=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'
+ setTimeout(() => {
+ setHide(true)
+ }, 700)
+ if (onAcceptCallback) {
+ onAcceptCallback()
+ }
+ }
+
+ const decline = () => {
+ setIsOpen(false)
+ setTimeout(() => {
+ setHide(true)
+ }, 700)
+ if (onDeclineCallback) {
+ onDeclineCallback()
+ }
+ }
+
+ useEffect(() => {
+ try {
+ setIsOpen(true)
+ if (document.cookie.includes('cookieConsent=true')) {
+ if (!demo) {
+ setIsOpen(false)
+ setTimeout(() => {
+ setHide(true)
+ }, 700)
+ }
+ }
+ } catch (e) {
+ // console.log("Error: ", e);
+ }
+ }, [])
+
+ return (
+
+
+
+
+
{t('cookie_consent.title', { ns: ['main'] })}
+
+
+
+
+ {t('cookie_consent.content', { ns: ['main'] })}
+
+
+
+ {t('cookie_consent.accept_pt1', { ns: ['main'] }) + ' '} "
+ {t('cookie_consent.accept', { ns: ['main'] })}"{' '}
+ {', ' + t('cookie_consent.accept_pt2', { ns: ['main'] })}
+
+
+
+ {t('cookie_consent.learn_more', { ns: ['main'] })}
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/custom/data-table.tsx b/src/components/custom/data-table.tsx
new file mode 100644
index 0000000..de19ff5
--- /dev/null
+++ b/src/components/custom/data-table.tsx
@@ -0,0 +1,183 @@
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
+import { Button } from '@/components/ui/button'
+import { ChevronLeftIcon, ChevronRightIcon, DoubleArrowLeftIcon, DoubleArrowRightIcon } from '@radix-ui/react-icons'
+import {
+ ColumnDef,
+ flexRender,
+ getCoreRowModel,
+ PaginationState,
+ SortingState,
+ useReactTable,
+} from '@tanstack/react-table'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-expect-error
+import { OnChangeFn } from '@tanstack/table-core/src/types'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-expect-error
+import { Table as TTable } from '@tanstack/table-core/build/lib/types'
+
+export interface DataTablePaginationProps
{
+ table: TTable
+ withSelected?: boolean
+}
+
+export function DataTablePagination({ table, withSelected }: DataTablePaginationProps) {
+ return (
+
+
+ {withSelected ? (
+ <>
+ {table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s)
+ selected.
+ >
+ ) : (
+ <>>
+ )}
+
+
+
+
Rows per page
+
+
+
+ Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export interface DataTableProps {
+ columns: ColumnDef[]
+ data: TData[]
+ total: number
+ pagination: PaginationState
+ paginationChangeFn: OnChangeFn
+ sorting?: SortingState
+ sortingChangeFn?: OnChangeFn
+}
+
+export function DataTable({
+ columns,
+ data,
+ total,
+ pagination,
+ paginationChangeFn,
+ sorting,
+ sortingChangeFn,
+}: DataTableProps) {
+ const table = useReactTable({
+ data: data,
+ columns: columns,
+ getCoreRowModel: getCoreRowModel(),
+ onPaginationChange: paginationChangeFn,
+ rowCount: total,
+ state: {
+ pagination,
+ sorting,
+ },
+ manualPagination: true,
+ debugTable: false,
+ onSortingChange: sortingChangeFn,
+ manualSorting: true,
+ })
+
+ return (
+
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
+
+ )
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ ))}
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/custom/multiple-selector.tsx b/src/components/custom/multiple-selector.tsx
new file mode 100644
index 0000000..3703354
--- /dev/null
+++ b/src/components/custom/multiple-selector.tsx
@@ -0,0 +1,509 @@
+import { Command as CommandPrimitive, useCommandState } from 'cmdk'
+import { X } from 'lucide-react'
+import * as React from 'react'
+import { forwardRef, useEffect } from 'react'
+
+import { Badge } from '@/components/ui/badge'
+import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
+import { cn } from '@/lib/utils'
+
+export interface Option {
+ value: string
+ label: string
+ disable?: boolean
+ /** fixed option that can't be removed. */
+ fixed?: boolean
+ /** Group the options by providing key. */
+ [key: string]: string | boolean | undefined
+}
+interface GroupOption {
+ [key: string]: Option[]
+}
+
+interface MultipleSelectorProps {
+ value?: Option[]
+ defaultOptions?: Option[]
+ /** manually controlled options */
+ options?: Option[]
+ placeholder?: string
+ /** Loading component. */
+ loadingIndicator?: React.ReactNode
+ /** Empty component. */
+ emptyIndicator?: React.ReactNode
+ /** Debounce time for async search. Only work with `onSearch`. */
+ delay?: number
+ /**
+ * Only work with `onSearch` prop. Trigger search when `onFocus`.
+ * For example, when user click on the input, it will trigger the search to get initial options.
+ **/
+ triggerSearchOnFocus?: boolean
+ /** async search */
+ onSearch?: (value: string) => Promise