diff --git a/packages/app-vite-react/package.json b/packages/app-vite-react/package.json index 681dc439..3ec8f0ae 100644 --- a/packages/app-vite-react/package.json +++ b/packages/app-vite-react/package.json @@ -11,7 +11,7 @@ "build": "tsc && vite build --emptyOutDir", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "start": "serve -s dist", - "loadTranslationTypes": "i18next-resources-for-ts interface -i ./src/public/locales/en -o ./src/@types/resources.d.ts" + "loadTranslationTypes": "i18next-resources-for-ts interface -i ./public/locales/en -o ./src/lib/i18next/@types/resources.d.ts" }, "dependencies": { "@emotion/react": "^11.13.3", diff --git a/packages/app-vite-react/src/public/locales/en/translation.json b/packages/app-vite-react/public/locales/en/translation.json similarity index 100% rename from packages/app-vite-react/src/public/locales/en/translation.json rename to packages/app-vite-react/public/locales/en/translation.json diff --git a/packages/app-vite-react/src/public/locales/hr/translation.json b/packages/app-vite-react/public/locales/hr/translation.json similarity index 100% rename from packages/app-vite-react/src/public/locales/hr/translation.json rename to packages/app-vite-react/public/locales/hr/translation.json diff --git a/packages/app-vite-react/src/public/png/logo.png b/packages/app-vite-react/public/png/logo.png similarity index 100% rename from packages/app-vite-react/src/public/png/logo.png rename to packages/app-vite-react/public/png/logo.png diff --git a/packages/app-vite-react/src/ReactApp.tsx b/packages/app-vite-react/src/ReactApp.tsx index 4a0560d5..38de2831 100644 --- a/packages/app-vite-react/src/ReactApp.tsx +++ b/packages/app-vite-react/src/ReactApp.tsx @@ -4,18 +4,17 @@ import type { RouteObject } from "react-router-dom"; import { Outlet, RouterProvider, createBrowserRouter } from "react-router-dom"; import { CssBaseline } from "@mui/material"; -import type { NavigationRoutes } from "@org/app-vite-react/config/NavigationRoute.config"; +import { type NavigationRoutes } from "@org/app-vite-react/routeTypes"; import { Layout } from "@org/app-vite-react/components/layout/Layout"; -import type { Provider } from "@org/app-vite-react/components/providers/Providers"; -import { Providers } from "@org/app-vite-react/components/providers/Providers"; -import { StylesProvider } from "@org/app-vite-react/components/providers/impl/StylesProvider"; -import { ThemeProvider } from "@org/app-vite-react/components/providers/impl/MuiThemeProvider"; -import { QueryClientProvider } from "@org/app-vite-react/components/providers/impl/QueryClientProvider"; +import { Providers, type Provider } from "@org/app-vite-react/components/providers/Providers"; +import { StylesProvider, ThemeProvider } from "@org/app-vite-react/lib/@mui"; +import { QueryClientProvider } from "@org/app-vite-react/lib/@tanstack"; import { Status404Page } from "@org/app-vite-react/app/pages/Status404"; import { RootErrorPage } from "@org/app-vite-react/app/pages/RootError"; -import { KeycloakAuthProvider } from "./components/providers"; +import { KeycloakProvider } from "@org/app-vite-react/lib/keycloak-js"; +import { initI18n } from "@org/app-vite-react/lib/i18next/i18n"; type ReactAppConfig = { providers?: Provider[]; @@ -40,7 +39,7 @@ function convertToRoutes(data: NavigationRoutes): RouteObject[] { export class ReactApp { static readonly #DEFAULT_ROOT_ERROR_PAGE = (); static readonly #COMMON_PROVIDERS = [ - KeycloakAuthProvider, + KeycloakProvider, QueryClientProvider, StylesProvider, ThemeProvider, @@ -58,7 +57,7 @@ export class ReactApp { router!: ReturnType; constructor() { - // NOOP + initI18n(); } run(config: ReactAppConfig) { diff --git a/packages/app-vite-react/src/app/pages/Home/HomePage.tsx b/packages/app-vite-react/src/app/pages/Home/HomePage.tsx index 92535f9e..15c03eff 100644 --- a/packages/app-vite-react/src/app/pages/Home/HomePage.tsx +++ b/packages/app-vite-react/src/app/pages/Home/HomePage.tsx @@ -9,7 +9,7 @@ import { ServerDatatable } from "@org/app-vite-react/components/semantics/Datata import { DEFAULT_PAGINATION_OPTIONS } from "@org/app-vite-react/components/semantics/Datatable/types"; import { DatatableContainer } from "@org/app-vite-react/components/semantics/Datatable/components/DatatableContainer"; import { FixedBadge } from "@org/app-vite-react/app/pages/Home/FixedBadge"; -import { apiClient } from "@org/app-vite-react/setup/apiClient.setup"; +import { apiClient } from "@org/app-vite-react/lib/@ts-rest"; function buildPaginationQueryParams(paginationOptions: PaginationOptions): { paginationOptions: string; diff --git a/packages/app-vite-react/src/app/pages/Home/UserCreateFormButton.tsx b/packages/app-vite-react/src/app/pages/Home/UserCreateFormButton.tsx index 1ed57c5d..1d280d80 100644 --- a/packages/app-vite-react/src/app/pages/Home/UserCreateFormButton.tsx +++ b/packages/app-vite-react/src/app/pages/Home/UserCreateFormButton.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import type { TODO, User } from "@org/lib-commons"; import { UserForm } from "@org/app-vite-react/app/pages/Home/UserForm"; import { Add } from "@mui/icons-material"; -import { apiClient } from "@org/app-vite-react/setup/apiClient.setup"; +import { apiClient } from "@org/app-vite-react/lib/@ts-rest"; export type UserCreateFormButtonProps = { afterUpdate?: () => void; diff --git a/packages/app-vite-react/src/app/routes.tsx b/packages/app-vite-react/src/app/routes.tsx index a012bee3..d0f84d93 100644 --- a/packages/app-vite-react/src/app/routes.tsx +++ b/packages/app-vite-react/src/app/routes.tsx @@ -1,6 +1,6 @@ import * as icons from "@mui/icons-material"; -import type { NavigationRoutes } from "@org/app-vite-react/config/NavigationRoute.config"; +import type { NavigationRoutes } from "@org/app-vite-react/routeTypes"; import { HomePage } from "@org/app-vite-react/app/pages/Home"; export const routes: NavigationRoutes = [ diff --git a/packages/app-vite-react/src/components/inputs/InputLocaleSelect/InputLocaleSelect.tsx b/packages/app-vite-react/src/components/inputs/InputLocaleSelect/InputLocaleSelect.tsx index aeea7d62..58e8ccfc 100644 --- a/packages/app-vite-react/src/components/inputs/InputLocaleSelect/InputLocaleSelect.tsx +++ b/packages/app-vite-react/src/components/inputs/InputLocaleSelect/InputLocaleSelect.tsx @@ -3,7 +3,7 @@ import { Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; import { InputIconButtonSelect } from "@org/app-vite-react/components/inputs/InputIconButtonSelect/InputIconButtonSelect"; import { sigLocale } from "@org/app-vite-react/signals/sigLocale"; -import type { Locale } from "@org/app-vite-react/config/i18n.config"; +import type { Locale } from "@org/app-vite-react/lib/i18next"; function getLocaleNativeName(locale: Locale) { const name: string = new Intl.DisplayNames([locale], { diff --git a/packages/app-vite-react/src/components/layout/variants/HorizontalNavVariant.tsx b/packages/app-vite-react/src/components/layout/variants/HorizontalNavVariant.tsx index 3f82ea65..52b9c078 100644 --- a/packages/app-vite-react/src/components/layout/variants/HorizontalNavVariant.tsx +++ b/packages/app-vite-react/src/components/layout/variants/HorizontalNavVariant.tsx @@ -1,29 +1,17 @@ import { ChevronRight, ExpandMore } from "@mui/icons-material"; import type { Breakpoint } from "@mui/material"; -import { - Box, - Container, - List, - ListItemButton, - ListItemIcon, - ListItemText, - Stack, -} from "@mui/material"; +import * as mui from "@mui/material"; import type { TODO } from "@org/lib-commons"; import { Fragment } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import type { OriginPosition } from "@org/app-vite-react/components/navigation/ButtonHoverMenu"; import { ButtonHoverMenu } from "@org/app-vite-react/components/navigation/ButtonHoverMenu"; -import type { - NavigationRoute, - NavigationRouteSingle, -} from "@org/app-vite-react/config/NavigationRoute.config"; -import { isAnyRouteActive } from "@org/app-vite-react/config/NavigationRoute.config"; -import { reactServer } from "@org/app-vite-react/setup/reactServer.setup"; +import * as RouteTypes from "@org/app-vite-react/routeTypes"; +import { reactServer } from "@org/app-vite-react/server"; export type HorizontalNavItemProps = { - item: NavigationRoute; + item: RouteTypes.NavigationRoute; dropdownPosition?: OriginPosition; }; @@ -39,17 +27,17 @@ function HorizontalNavItem({ const { t } = useTranslation(); const navigate = useNavigate(); const hasChildren = "children" in item && item.children; - const children: NavigationRoute[] = (hasChildren ? item.children : []) as TODO; + const children: RouteTypes.NavigationRoute[] = (hasChildren ? item.children : []) as TODO; const isMainNavButton = dropdownPosition.anchorY === "bottom"; const borderRadius = isMainNavButton ? 1 : undefined; if (hasChildren) { - const isAnyRouteActiveInGroup = isAnyRouteActive(children); + const isAnyRouteActiveInGroup = RouteTypes.isAnyRouteActive(children); return ( ( - - {item.icon && {item.icon}} - - + {item.icon && {item.icon}} + + {isMainNavButton ? : } - - + + )} > {children.map((child, index) => ( @@ -90,7 +78,7 @@ function HorizontalNavItem({ ); } - const itemSingle = item as NavigationRouteSingle; + const itemSingle = item as RouteTypes.NavigationRouteSingle; if (itemSingle.hidden === true) { return <>; @@ -99,7 +87,7 @@ function HorizontalNavItem({ const isSelected = location.pathname === itemSingle.path; return ( - navigate(itemSingle.path)} > - {item.icon && {item.icon}} - - + {item.icon && {item.icon}} + + ); } @@ -127,22 +115,22 @@ export function HorizontalNavVariant({ hidden = false, }: HorizontalNavVariantProps) { return ( - + + + ); } diff --git a/packages/app-vite-react/src/components/layout/variants/SidebarNavVariant.tsx b/packages/app-vite-react/src/components/layout/variants/SidebarNavVariant.tsx index a10e4490..3bda83f9 100644 --- a/packages/app-vite-react/src/components/layout/variants/SidebarNavVariant.tsx +++ b/packages/app-vite-react/src/components/layout/variants/SidebarNavVariant.tsx @@ -5,15 +5,11 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate } from "react-router-dom"; import { Fragment } from "react/jsx-runtime"; -import type { - NavigationRoute, - NavigationRouteSingle, -} from "@org/app-vite-react/config/NavigationRoute.config"; -import { isAnyRouteActive } from "@org/app-vite-react/config/NavigationRoute.config"; -import { reactServer } from "@org/app-vite-react/setup/reactServer.setup"; +import { reactServer } from "@org/app-vite-react/server"; +import * as RouteTypes from "@org/app-vite-react/routeTypes"; export type SidebarNavItemProps = { - item: NavigationRoute; + item: RouteTypes.NavigationRoute; indent?: number; }; @@ -22,7 +18,7 @@ function SidebarNavItem({ item, indent = 0 }: SidebarNavItemProps) { const variant = item?.variant ?? "single"; const renderChildrenPersistent = hasChildren && variant === "group"; const renderChildrenMenu = hasChildren && variant === "menu"; - const children: NavigationRoute[] = (hasChildren ? item.children : []) as TODO; + const children: RouteTypes.NavigationRoute[] = (hasChildren ? item.children : []) as TODO; const [open, setOpen] = useState(false); const navigate = useNavigate(); const location = useLocation(); @@ -60,7 +56,7 @@ function SidebarNavItem({ item, indent = 0 }: SidebarNavItemProps) { paddingLeft: `calc(1.5rem + ${indent}rem)`, borderTopRightRadius: "2rem", borderBottomRightRadius: "2rem", - backgroundColor: isAnyRouteActive(children) + backgroundColor: RouteTypes.isAnyRouteActive(children) ? "var(--mui-palette-action-hover)" : undefined, }} @@ -79,7 +75,7 @@ function SidebarNavItem({ item, indent = 0 }: SidebarNavItemProps) { ); } - const itemSingle = item as NavigationRouteSingle; + const itemSingle = item as RouteTypes.NavigationRouteSingle; if (itemSingle.hidden === true) { return <>; diff --git a/packages/app-vite-react/src/components/providers/Providers.tsx b/packages/app-vite-react/src/components/providers/Providers.tsx index 4c853d8b..be77c0c6 100644 --- a/packages/app-vite-react/src/components/providers/Providers.tsx +++ b/packages/app-vite-react/src/components/providers/Providers.tsx @@ -7,7 +7,7 @@ const nest = (children: React.ReactNode, Provider: React.FC<{ children: React.Re {children} ); -// Type for the props of the Providers component +/** @hidden */ export type ProvidersProps = React.PropsWithChildren<{ list: Array>; }>; diff --git a/packages/app-vite-react/src/components/providers/index.ts b/packages/app-vite-react/src/components/providers/index.ts deleted file mode 100644 index a4b76817..00000000 --- a/packages/app-vite-react/src/components/providers/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./impl/MuiThemeProvider"; -export * from "./impl/QueryClientProvider"; -export * from "./impl/StylesProvider"; -export * from "./impl/KeycloakAuthProvider"; -export * from "./Providers"; diff --git a/packages/app-vite-react/src/components/semantics/Header/UserMenuButton.tsx b/packages/app-vite-react/src/components/semantics/Header/UserMenuButton.tsx index 6edec032..0116311f 100644 --- a/packages/app-vite-react/src/components/semantics/Header/UserMenuButton.tsx +++ b/packages/app-vite-react/src/components/semantics/Header/UserMenuButton.tsx @@ -12,7 +12,7 @@ import Settings from "@mui/icons-material/Settings"; import Logout from "@mui/icons-material/Logout"; import { type TODO } from "@org/lib-commons"; import { sigUser } from "@org/app-vite-react/signals/sigUser"; -import { sigKeycloak } from "@org/app-vite-react/signals/sigKeycloak"; +import { keycloakLogout } from "@org/app-vite-react/lib/keycloak-js"; export function UserMenuButton() { const [anchorEl, setAnchorEl] = React.useState(null); @@ -24,8 +24,7 @@ export function UserMenuButton() { setAnchorEl(null); }; const handleLogout = () => { - sigKeycloak.value!.logout(); - //handleClose(); + keycloakLogout(); }; return ( diff --git a/packages/app-vite-react/src/config/UiPreferences.config.ts b/packages/app-vite-react/src/config/UiPreferences.config.ts deleted file mode 100644 index d7bb3eba..00000000 --- a/packages/app-vite-react/src/config/UiPreferences.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Breakpoint } from "@mui/material"; - -export type UiPreferences = { - containerWidth: Breakpoint | false; -}; diff --git a/packages/app-vite-react/src/config/i18n.config.ts b/packages/app-vite-react/src/config/i18n.config.ts deleted file mode 100644 index fa904d61..00000000 --- a/packages/app-vite-react/src/config/i18n.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { TFunction } from "i18next"; - -export type I18nTranslateFn = TFunction<"translation", undefined>; - -export type Locale = "hr" | "en"; diff --git a/packages/app-vite-react/src/lib/@mui/index.ts b/packages/app-vite-react/src/lib/@mui/index.ts new file mode 100644 index 00000000..6b8bad0c --- /dev/null +++ b/packages/app-vite-react/src/lib/@mui/index.ts @@ -0,0 +1,2 @@ +export * from "./providers/MuiStylesProvider"; +export * from "./providers/MuiThemeProvider"; diff --git a/packages/app-vite-react/src/components/providers/impl/StylesProvider.tsx b/packages/app-vite-react/src/lib/@mui/providers/MuiStylesProvider.tsx similarity index 100% rename from packages/app-vite-react/src/components/providers/impl/StylesProvider.tsx rename to packages/app-vite-react/src/lib/@mui/providers/MuiStylesProvider.tsx diff --git a/packages/app-vite-react/src/components/providers/impl/MuiThemeProvider.tsx b/packages/app-vite-react/src/lib/@mui/providers/MuiThemeProvider.tsx similarity index 100% rename from packages/app-vite-react/src/components/providers/impl/MuiThemeProvider.tsx rename to packages/app-vite-react/src/lib/@mui/providers/MuiThemeProvider.tsx diff --git a/packages/app-vite-react/src/components/providers/impl/QueryClientProvider.tsx b/packages/app-vite-react/src/lib/@tanstack/QueryClientProvider.tsx similarity index 100% rename from packages/app-vite-react/src/components/providers/impl/QueryClientProvider.tsx rename to packages/app-vite-react/src/lib/@tanstack/QueryClientProvider.tsx diff --git a/packages/app-vite-react/src/lib/@tanstack/index.ts b/packages/app-vite-react/src/lib/@tanstack/index.ts new file mode 100644 index 00000000..e8807988 --- /dev/null +++ b/packages/app-vite-react/src/lib/@tanstack/index.ts @@ -0,0 +1 @@ +export * from "./QueryClientProvider"; diff --git a/packages/app-vite-react/src/setup/apiClient.setup.ts b/packages/app-vite-react/src/lib/@ts-rest/apiClient.ts similarity index 91% rename from packages/app-vite-react/src/setup/apiClient.setup.ts rename to packages/app-vite-react/src/lib/@ts-rest/apiClient.ts index 939ce7fc..a782be77 100644 --- a/packages/app-vite-react/src/setup/apiClient.setup.ts +++ b/packages/app-vite-react/src/lib/@ts-rest/apiClient.ts @@ -1,8 +1,6 @@ import { initClient } from "@ts-rest/core"; import { contracts } from "@org/lib-api-client"; -console.log(import.meta.env); - const API_CLIENT_URL = import.meta.env.VITE_API_CLIENT_URL; if (!API_CLIENT_URL) { diff --git a/packages/app-vite-react/src/lib/@ts-rest/index.ts b/packages/app-vite-react/src/lib/@ts-rest/index.ts new file mode 100644 index 00000000..1f8c144e --- /dev/null +++ b/packages/app-vite-react/src/lib/@ts-rest/index.ts @@ -0,0 +1 @@ +export * from "./apiClient"; diff --git a/packages/app-vite-react/src/@types/i18n.d.ts b/packages/app-vite-react/src/lib/i18next/@types/i18n.d.ts similarity index 100% rename from packages/app-vite-react/src/@types/i18n.d.ts rename to packages/app-vite-react/src/lib/i18next/@types/i18n.d.ts diff --git a/packages/app-vite-react/src/@types/resources.d.ts b/packages/app-vite-react/src/lib/i18next/@types/resources.d.ts similarity index 100% rename from packages/app-vite-react/src/@types/resources.d.ts rename to packages/app-vite-react/src/lib/i18next/@types/resources.d.ts diff --git a/packages/app-vite-react/src/lib/i18next/i18n.ts b/packages/app-vite-react/src/lib/i18next/i18n.ts new file mode 100644 index 00000000..57d31330 --- /dev/null +++ b/packages/app-vite-react/src/lib/i18next/i18n.ts @@ -0,0 +1,21 @@ +import i18n, { type TFunction } from "i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import Backend from "i18next-http-backend"; +import { initReactI18next } from "react-i18next"; + +export type I18nTranslateFn = TFunction<"translation", undefined>; + +export type Locale = "hr" | "en"; + +export function initI18n() { + i18n + .use(Backend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + fallbackLng: "en", + interpolation: { + escapeValue: false, + }, + }); +} diff --git a/packages/app-vite-react/src/lib/i18next/index.ts b/packages/app-vite-react/src/lib/i18next/index.ts new file mode 100644 index 00000000..0c4205e0 --- /dev/null +++ b/packages/app-vite-react/src/lib/i18next/index.ts @@ -0,0 +1 @@ +export * from "./i18n"; diff --git a/packages/app-vite-react/src/setup/keycloakClient.setup.ts b/packages/app-vite-react/src/lib/keycloak-js/KeycloakClient.ts similarity index 70% rename from packages/app-vite-react/src/setup/keycloakClient.setup.ts rename to packages/app-vite-react/src/lib/keycloak-js/KeycloakClient.ts index f454762c..74869d3e 100644 --- a/packages/app-vite-react/src/setup/keycloakClient.setup.ts +++ b/packages/app-vite-react/src/lib/keycloak-js/KeycloakClient.ts @@ -5,3 +5,7 @@ export const keycloakClient = new Keycloak({ url: import.meta.env.VITE_API_KEYCLOAK_URL, clientId: "app-vite-react", }); + +export async function keycloakLogout() { + await keycloakClient.logout(); +} diff --git a/packages/app-vite-react/src/components/providers/impl/KeycloakAuthProvider.tsx b/packages/app-vite-react/src/lib/keycloak-js/KeycloakProvider.tsx similarity index 61% rename from packages/app-vite-react/src/components/providers/impl/KeycloakAuthProvider.tsx rename to packages/app-vite-react/src/lib/keycloak-js/KeycloakProvider.tsx index e6234e64..6c12b50f 100644 --- a/packages/app-vite-react/src/components/providers/impl/KeycloakAuthProvider.tsx +++ b/packages/app-vite-react/src/lib/keycloak-js/KeycloakProvider.tsx @@ -1,11 +1,11 @@ import { StrictMode, type PropsWithChildren } from "react"; import { ReactKeycloakProvider, useKeycloak } from "@react-keycloak/web"; -import { keycloakClient } from "@org/app-vite-react/setup/keycloakClient.setup"; -import { decodeUserData, sigUser } from "@org/app-vite-react/signals/sigUser"; +import { sigUser } from "@org/app-vite-react/signals/sigUser"; import { sigToken } from "@org/app-vite-react/signals/sigToken"; -import { sigKeycloak } from "@org/app-vite-react/signals/sigKeycloak"; +import { decodeKeycloakToken } from "@org/app-vite-react/lib/keycloak-js/KeycloakUser"; +import { keycloakClient } from "@org/app-vite-react/lib/keycloak-js/KeycloakClient"; -const KeycloakAuthContent = ({ children }: PropsWithChildren) => { +const KeycloakImpl = ({ children }: PropsWithChildren) => { const { keycloak, initialized } = useKeycloak(); if (!initialized) { @@ -16,11 +16,10 @@ const KeycloakAuthContent = ({ children }: PropsWithChildren) => { return
Not authenticated
; } - sigKeycloak.value = keycloak; return <>{children}; }; -export function KeycloakAuthProvider({ children }: PropsWithChildren) { +export function KeycloakProvider({ children }: PropsWithChildren) { return ( - + {children} - + ); } diff --git a/packages/app-vite-react/src/lib/keycloak-js/KeycloakUser.ts b/packages/app-vite-react/src/lib/keycloak-js/KeycloakUser.ts new file mode 100644 index 00000000..887f2c4b --- /dev/null +++ b/packages/app-vite-react/src/lib/keycloak-js/KeycloakUser.ts @@ -0,0 +1,28 @@ +import { type KeycloakTokenParsed } from "keycloak-js"; +import { jwtDecode } from "jwt-decode"; + +export interface KeycloakUser { + firstName?: string; + lastName?: string; + userName?: string; + token?: string; + locale?: string; + companyId?: string; + roles: string[]; + email?: string; +} + +export function decodeKeycloakToken(keycloakToken: string): KeycloakUser { + const decoded = jwtDecode(keycloakToken); + const fromToken: KeycloakUser = { + userName: decoded["preferred_username"], + email: decoded["email"], + firstName: decoded["given_name"], + lastName: decoded["family_name"], + companyId: decoded["companyId"], + locale: decoded["locale"], + token: keycloakToken, + roles: decoded["realm_access"]?.["roles"] ?? [], + }; + return fromToken; +} diff --git a/packages/app-vite-react/src/lib/keycloak-js/index.ts b/packages/app-vite-react/src/lib/keycloak-js/index.ts new file mode 100644 index 00000000..e20656d3 --- /dev/null +++ b/packages/app-vite-react/src/lib/keycloak-js/index.ts @@ -0,0 +1,3 @@ +export * from "@org/app-vite-react/lib/keycloak-js/KeycloakClient"; +export * from "@org/app-vite-react/lib/keycloak-js/KeycloakProvider"; +export * from "@org/app-vite-react/lib/keycloak-js/KeycloakUser"; diff --git a/packages/app-vite-react/src/main.tsx b/packages/app-vite-react/src/main.tsx index 8e63d324..88e8c58c 100644 --- a/packages/app-vite-react/src/main.tsx +++ b/packages/app-vite-react/src/main.tsx @@ -1,7 +1,6 @@ import "@org/app-vite-react/setup/i18n.setup"; import "@org/app-vite-react/main.css"; -import { reactServer } from "@org/app-vite-react/setup/reactServer.setup"; - +import { reactServer } from "@org/app-vite-react/server"; import { routes } from "@org/app-vite-react/app/routes"; reactServer.run({ diff --git a/packages/app-vite-react/src/config/NavigationRoute.config.ts b/packages/app-vite-react/src/routeTypes.ts similarity index 93% rename from packages/app-vite-react/src/config/NavigationRoute.config.ts rename to packages/app-vite-react/src/routeTypes.ts index 9e6c3ce9..414fc6c7 100644 --- a/packages/app-vite-react/src/config/NavigationRoute.config.ts +++ b/packages/app-vite-react/src/routeTypes.ts @@ -1,7 +1,7 @@ import type { Role, User } from "@org/lib-commons"; import type { ReactNode } from "react"; import type { RouteObject } from "react-router-dom"; -import { type I18nTranslateFn } from "@org/app-vite-react/config/i18n.config"; +import { type I18nTranslateFn } from "@org/app-vite-react/lib/i18next"; export type NavigationRouteAnchorSecure = Role[] | ((user: User) => boolean | Role[]); diff --git a/packages/app-vite-react/src/setup/reactServer.setup.ts b/packages/app-vite-react/src/server.ts similarity index 100% rename from packages/app-vite-react/src/setup/reactServer.setup.ts rename to packages/app-vite-react/src/server.ts diff --git a/packages/app-vite-react/src/setup/i18n.setup.ts b/packages/app-vite-react/src/setup/i18n.setup.ts deleted file mode 100644 index 744078c4..00000000 --- a/packages/app-vite-react/src/setup/i18n.setup.ts +++ /dev/null @@ -1,17 +0,0 @@ -import i18nCore from "i18next"; -import LanguageDetector from "i18next-browser-languagedetector"; -import Backend from "i18next-http-backend"; -import { initReactI18next } from "react-i18next"; - -i18nCore - .use(Backend) - .use(LanguageDetector) - .use(initReactI18next) - .init({ - fallbackLng: "en", - interpolation: { - escapeValue: false, - }, - }); - -export const i18n = i18nCore; diff --git a/packages/app-vite-react/src/signals/sigKeycloak.ts b/packages/app-vite-react/src/signals/sigKeycloak.ts deleted file mode 100644 index c554311e..00000000 --- a/packages/app-vite-react/src/signals/sigKeycloak.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { signal } from "@preact/signals-react"; -import type Keycloak from "keycloak-js"; - -export const sigKeycloak = signal(null); diff --git a/packages/app-vite-react/src/signals/sigLocale.ts b/packages/app-vite-react/src/signals/sigLocale.ts index b218aec9..9f4930be 100644 --- a/packages/app-vite-react/src/signals/sigLocale.ts +++ b/packages/app-vite-react/src/signals/sigLocale.ts @@ -1,6 +1,6 @@ import { effect, signal } from "@preact/signals-react"; -import { i18n } from "@org/app-vite-react/setup/i18n.setup"; -import { type Locale } from "@org/app-vite-react/config/i18n.config"; +import { type Locale } from "@org/app-vite-react/lib/i18next"; +import i18n from "i18next"; export const sigLocale = signal( (["en", "hr"].find(value => value === localStorage.getItem("locale")) ?? "en") as Locale, diff --git a/packages/app-vite-react/src/signals/sigPreferences.ts b/packages/app-vite-react/src/signals/sigPreferences.ts index 2bad50f9..7a5bea00 100644 --- a/packages/app-vite-react/src/signals/sigPreferences.ts +++ b/packages/app-vite-react/src/signals/sigPreferences.ts @@ -1,7 +1,11 @@ import { effect, signal } from "@preact/signals-react"; -import { type UiPreferences } from "@org/app-vite-react/config/UiPreferences.config"; +import type { Breakpoint } from "@mui/material"; -const DEFAULT_PREFERENCES: UiPreferences = { +export type GuiPreferences = { + containerWidth: Breakpoint | false; +}; + +const DEFAULT_PREFERENCES: GuiPreferences = { containerWidth: "xl", }; @@ -22,7 +26,7 @@ function getJsonItem>>( return { ...defaults, ...json } as T; } -export const sigPreferences = signal( +export const sigPreferences = signal( getJsonItem(PREFERENCES_KEY, DEFAULT_PREFERENCES), ); diff --git a/packages/app-vite-react/src/signals/sigTheme.ts b/packages/app-vite-react/src/signals/sigTheme.ts index 7cc3d165..40725a58 100644 --- a/packages/app-vite-react/src/signals/sigTheme.ts +++ b/packages/app-vite-react/src/signals/sigTheme.ts @@ -1,18 +1,51 @@ import { createTheme } from "@mui/material"; import { computed, signal } from "@preact/signals-react"; -const userPrefersDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches; - type ThemeOptsInternal = Parameters[0]; type ThemeOptsBase = Omit; +/** @hidden */ export type ThemeOpts = ThemeOptsBase & { dark: boolean; }; -export const sigThemeOpts = signal({ dark: userPrefersDarkMode }); +/** + * A signal for MUI's theme options. + * + * @remarks Components or hooks subscribing to this signal will automatically re-render or react when its value changes. + * + * @see {@link https://mui.com/material-ui/customization/default-theme/ | MUI's default theme options} + * + * @default + * ```ts + * { dark: window.matchMedia("(prefers-color-scheme: dark)").matches } + * ``` + * + * @example + * ```ts + * import { sigThemeOpts } from './path-to-sigThemeOpts'; + * const currentValue = sigThemeOpts.value // Read + * sigThemeOpts.value = { dark: false } // Write + * ``` + */ +export const sigThemeOpts = signal({ + dark: window.matchMedia("(prefers-color-scheme: dark)").matches, +}); +/** + * A readonly signal for MUI's theme (computed from {@link sigThemeOpts}). + * + * @remarks Components or hooks subscribing to this signal will automatically re-render or react when its value changes. + * + * @see {@link https://mui.com/material-ui/customization/theming/#createtheme-options-args-theme | MUI's createTheme API Documentation} + * + * @example + * ```ts + * import { sigTheme } from './path-to-sigTheme'; + * const currentValue = sigTheme.value // Read + * ``` + */ export const sigTheme = computed(() => { const { dark, ...rest } = sigThemeOpts.value; diff --git a/packages/app-vite-react/src/signals/sigUser.ts b/packages/app-vite-react/src/signals/sigUser.ts index c5ea71dc..ad002392 100644 --- a/packages/app-vite-react/src/signals/sigUser.ts +++ b/packages/app-vite-react/src/signals/sigUser.ts @@ -1,35 +1,4 @@ import { signal } from "@preact/signals-react"; -import { type KeycloakTokenParsed, type KeycloakRoles } from "keycloak-js"; -import { jwtDecode } from "jwt-decode"; +import { type KeycloakUser } from "@org/app-vite-react/lib/keycloak-js"; -export interface KeycloakResourceAccess { - [key: string]: KeycloakRoles; -} - -export function decodeUserData(token: string): UserData { - const decoded = jwtDecode(token); - const fromToken: UserData = { - userName: decoded["preferred_username"], - email: decoded["email"], - firstName: decoded["given_name"], - lastName: decoded["family_name"], - companyId: decoded["companyId"], - locale: decoded["locale"], - token: token, - roles: decoded["realm_access"]?.["roles"] ?? [], - }; - return fromToken; -} - -export interface UserData { - firstName?: string; - lastName?: string; - userName?: string; - token?: string; - locale?: string; - companyId?: string; - roles: string[]; - email?: string; -} - -export const sigUser = signal(null); +export const sigUser = signal(null);