diff --git a/ServicesApi/Builds/Auth/events/auth/events.py b/ServicesApi/Builds/Auth/events/auth/events.py index ff8c302..4e2404c 100644 --- a/ServicesApi/Builds/Auth/events/auth/events.py +++ b/ServicesApi/Builds/Auth/events/auth/events.py @@ -346,7 +346,7 @@ class LoginHandler: ).join(EndpointRestriction, EndpointRestriction.id == Events.endpoint_id).filter().all() # Get reachable applications - reachable_app_codes = Application2Employee.get_application_codes(employee_id=int(result_with_keys_dict['Employees.id']), db=db_session) + # reachable_app_codes = Application2Employee.get_application_codes(employee_id=int(result_with_keys_dict['Employees.id']), db=db_session) # Get reachable events reachable_event_codes = {endpoint_name: function_code for endpoint_name, function_code in filter_endpoints_and_events} @@ -361,6 +361,11 @@ class LoginHandler: redis_handler = RedisHandlers() user_uu_id = Users.query.filter(Users.person_id == result_with_keys_dict['People.id']).first().uu_id redis_result = redis_handler.update_token_at_redis(token=access_token, add_payload=add_company, user_uuid=user_uu_id) + reachable_app_codes = [] + if result := RedisHandlers.get_object_from_redis(access_token=access_token): + if result.is_employee and result.selected_company: + if reachable_app_codes_dict := result.reachable_app_codes: + reachable_app_codes = list(reachable_app_codes_dict.get(data.uuid, {}).keys()) return {"selected_uu_id": data.uuid, "reachable_app_codes": reachable_app_codes} @classmethod @@ -396,10 +401,7 @@ class LoginHandler: reachable_event_codes = {endpoint_name: function_code for endpoint_name, function_code in filter_endpoints_and_events} # Get reachable applications - print('selected_build_living_space_first', selected_build_living_space_first.id) - reachable_app_codes = Application2Occupant.get_application_codes(build_living_space_id=selected_build_living_space_first.id, db=db) - print('reachable_app_codes', reachable_app_codes) - + # reachable_app_codes = Application2Occupant.get_application_codes(build_living_space_id=selected_build_living_space_first.id, db=db) build_part = BuildParts.query.filter(BuildParts.id == result_with_keys_dict['BuildParts.id']).first() if not build_part: raise ValueError("EYS_0013") @@ -414,10 +416,16 @@ class LoginHandler: "code": result_with_keys_dict['OccupantTypes.occupant_code'] } redis_handler = RedisHandlers() - user_uu_id = Users.query.filter(Users.person_id == result_with_keys_dict['People.id']).first().uu_id redis_handler.update_token_at_redis(token=access_token, add_payload=add_build_living_space, user_uuid=user_uu_id) - return {"selected_uu_id": data.uuid, "reachable_app_codes": reachable_app_codes} + reachable_app_codes = [] + + if result := RedisHandlers.get_object_from_redis(access_token=access_token): + if result.is_occupant and result.selected_occupant: + if reachable_app_codes_dict := result.reachable_app_codes: + reachable_app_codes = list(reachable_app_codes_dict.get(data.uuid, {}).keys()) + print('reachable_app_codes', reachable_app_codes) + return {"selected_uu_id": data.uuid, "reachable_app_codes": reachable_app_codes or []} @classmethod def authentication_select_company_or_occupant_type(cls, request: Any, data: Any): diff --git a/ServicesWeb/customer/package-lock.json b/ServicesWeb/customer/package-lock.json index b165a3d..91c20f4 100644 --- a/ServicesWeb/customer/package-lock.json +++ b/ServicesWeb/customer/package-lock.json @@ -34,6 +34,8 @@ "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-tooltip": "^1.2.7", + "@tanstack/react-query": "^5.80.7", + "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -2657,6 +2659,65 @@ "tailwindcss": "4.1.8" } }, + "node_modules/@tanstack/query-core": { + "version": "5.80.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.80.7.tgz", + "integrity": "sha512-s09l5zeUKC8q7DCCCIkVSns8zZrK4ZDT6ryEjxNBFi68G4z2EBobBS7rdOY3r6W1WbUDpc1fe5oY+YO/+2UVUg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.80.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.80.7.tgz", + "integrity": "sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.80.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", diff --git a/ServicesWeb/customer/package.json b/ServicesWeb/customer/package.json index a1cf14b..6d7a680 100644 --- a/ServicesWeb/customer/package.json +++ b/ServicesWeb/customer/package.json @@ -35,6 +35,8 @@ "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-tooltip": "^1.2.7", + "@tanstack/react-query": "^5.80.7", + "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/ServicesWeb/customer/src/app/api/login/email/route.ts b/ServicesWeb/customer/src/app/api/login/email/route.ts index 687679d..ae899be 100644 --- a/ServicesWeb/customer/src/app/api/login/email/route.ts +++ b/ServicesWeb/customer/src/app/api/login/email/route.ts @@ -21,14 +21,13 @@ export async function POST(req: Request): Promise { message: validatedLoginBody.error.message, }); } - const userLogin = await loginViaAccessKeys(dataValidated); console.log("userLogin", userLogin); - await initFirstSelection( - userLogin.data?.firstSelection, - userLogin.data?.userType - ); if (userLogin.status === 200 || userLogin.status === 202) { + await initFirstSelection( + userLogin.data?.firstSelection, + userLogin.data?.userType + ); return NextResponse.json({ status: 200, message: "Login successfully completed", diff --git a/ServicesWeb/customer/src/app/layout.tsx b/ServicesWeb/customer/src/app/layout.tsx index f7fa87e..c39c99f 100644 --- a/ServicesWeb/customer/src/app/layout.tsx +++ b/ServicesWeb/customer/src/app/layout.tsx @@ -2,6 +2,11 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +// Import EventEmitter configuration to increase max listeners +import "../utils/eventEmitterConfig"; +// Import Redis-specific configuration to fix Commander EventEmitter warning +import "../utils/configureRedis"; + const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], diff --git a/ServicesWeb/customer/src/components/custom/content/DPage.tsx b/ServicesWeb/customer/src/components/custom/content/DPage.tsx new file mode 100644 index 0000000..6165563 --- /dev/null +++ b/ServicesWeb/customer/src/components/custom/content/DPage.tsx @@ -0,0 +1,491 @@ +'use client'; + +import { FC, useState } from 'react'; +import { + useQuery, + QueryClient, + QueryClientProvider, +} from '@tanstack/react-query'; +import { + flexRender, + getCoreRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, + SortingState, + createColumnHelper, + ColumnDef, + Row, + Cell, + Header, + Table, +} from '@tanstack/react-table'; + + +interface DashboardPageProps { + searchParams: Record; + activePageUrl?: string; + + userData?: any; + userLoading?: boolean; + userError?: any; + refreshUser?: () => void; + updateUser?: (data: any) => void; + + onlineData?: any; + onlineLoading?: boolean; + onlineError?: any; + refreshOnline?: () => void; + updateOnline?: (data: any) => void; +} + +// Wrap the component with QueryClientProvider +const queryClient = new QueryClient(); + +const DPage: FC = ({ + searchParams, + userData, + userLoading, + onlineData, + activePageUrl +}) => { + // Sample data for the dashboard + const statCardsData = [ + { + title: 'Users', + count: '36.5k', + icon: 'bx bx-user', + iconColor: 'text-blue-500', + percentage: '4.65%', + isPositive: true + }, + { + title: 'Companies', + count: '4.5k', + icon: 'bx bx-building', + iconColor: 'text-yellow-500', + percentage: '1.25%', + isPositive: false + }, + { + title: 'Blogs', + count: '12.5k', + icon: 'bx bxl-blogger', + iconColor: 'text-green-500', + percentage: '2.15%', + isPositive: true + }, + { + title: 'Revenue', + count: '$35.5k', + icon: 'bx bx-dollar', + iconColor: 'text-pink-500', + percentage: '3.75%', + isPositive: true + } + ]; + + const userRolesColumns = [ + { header: 'Name', accessor: 'name' }, + { header: 'Email', accessor: 'email' }, + { + header: 'Role', accessor: 'role', + cell: (value: string) => ( + + {value} + + ) + } + ]; + + const userRolesData = [ + { name: 'John Doe', email: 'john@example.com', role: 'Admin' }, + { name: 'Jane Smith', email: 'jane@example.com', role: 'Editor' }, + { name: 'Robert Johnson', email: 'robert@example.com', role: 'Customer' }, + { name: 'Emily Davis', email: 'emily@example.com', role: 'Customer' }, + { name: 'Michael Brown', email: 'michael@example.com', role: 'Editor' } + ]; + + const activitiesColumns = [ + { header: 'Name', accessor: 'name' }, + { header: 'Date', accessor: 'date' }, + { header: 'Time', accessor: 'time' } + ]; + + const activitiesData = [ + { name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' }, + { name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' }, + { name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' }, + { name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' }, + { name: 'Lorem Ipsum', date: '02-02-2024', time: '17.45' } + ]; + + const earningsData = [ + { service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' }, + { service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: false, status: 'Withdrawn' }, + { service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' }, + { service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: false, status: 'Withdrawn' }, + { service: 'Create landing page', imageUrl: 'https://placehold.co/32x32', amount: '$235', isPositive: true, status: 'Pending' } + ]; + + return ( +
+ + + +
+ ); +}; + + +// TanStack Table Example component + +type Person = { + id: number + firstName: string + lastName: string + age: number + visits: number + status: string + progress: number +} + +const TanStackTableExample: FC = () => { + // Column definitions using columnHelper + const columnHelper = createColumnHelper(); + + const columns = [ + columnHelper.accessor('firstName', { + cell: (info) => info.getValue(), + header: () => 'First Name', + footer: (info) => info.column.id, + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + cell: (info) => info.getValue(), + header: () => 'Last Name', + footer: (info) => info.column.id, + }), + columnHelper.accessor('age', { + header: () => 'Age', + cell: (info) => info.renderValue(), + footer: (info) => info.column.id, + }), + columnHelper.accessor('visits', { + header: () => 'Visits', + footer: (info) => info.column.id, + }), + columnHelper.accessor('status', { + header: 'Status', + cell: (info) => ( + + {info.getValue()} + + ), + footer: (info) => info.column.id, + }), + columnHelper.accessor('progress', { + header: 'Profile Progress', + cell: (info) => ( +
+
+
+ ), + }), + ] as ColumnDef[]; + + // Fetch data using React Query + const fetchPeople = async (): Promise => { + // In a real app, this would be an API call + // For this example, we'll return mock data + return [ + { + id: 1, + firstName: 'John', + lastName: 'Doe', + age: 28, + visits: 10, + status: 'Active', + progress: 80, + }, + { + id: 2, + firstName: 'Jane', + lastName: 'Smith', + age: 32, + visits: 5, + status: 'Pending', + progress: 45, + }, + { + id: 3, + firstName: 'Robert', + lastName: 'Johnson', + age: 45, + visits: 20, + status: 'Inactive', + progress: 30, + }, + { + id: 4, + firstName: 'Emily', + lastName: 'Davis', + age: 27, + visits: 15, + status: 'Active', + progress: 95, + }, + { + id: 5, + firstName: 'Michael', + lastName: 'Brown', + age: 39, + visits: 8, + status: 'Pending', + progress: 60, + }, + { + id: 6, + firstName: 'Sarah', + lastName: 'Wilson', + age: 34, + visits: 12, + status: 'Active', + progress: 75, + }, + { + id: 7, + firstName: 'David', + lastName: 'Miller', + age: 41, + visits: 7, + status: 'Inactive', + progress: 25, + }, + { + id: 8, + firstName: 'Jennifer', + lastName: 'Taylor', + age: 29, + visits: 18, + status: 'Active', + progress: 88, + }, + { + id: 9, + firstName: 'James', + lastName: 'Anderson', + age: 36, + visits: 9, + status: 'Pending', + progress: 52, + }, + { + id: 10, + firstName: 'Lisa', + lastName: 'Thomas', + age: 31, + visits: 14, + status: 'Active', + progress: 70, + }, + { + id: 11, + firstName: 'Richard', + lastName: 'Jackson', + age: 47, + visits: 6, + status: 'Inactive', + progress: 15, + }, + { + id: 12, + firstName: 'Mary', + lastName: 'White', + age: 25, + visits: 22, + status: 'Active', + progress: 92, + }, + { + id: 13, + firstName: 'Thomas', + lastName: 'Harris', + age: 38, + visits: 11, + status: 'Pending', + progress: 48, + }, + { + id: 14, + firstName: 'Patricia', + lastName: 'Martin', + age: 33, + visits: 16, + status: 'Active', + progress: 83, + }, + { + id: 15, + firstName: 'Charles', + lastName: 'Thompson', + age: 42, + visits: 4, + status: 'Inactive', + progress: 22, + }, + ]; + }; + + const { data = [], isLoading, error } = useQuery({ + queryKey: ['people'], + queryFn: fetchPeople, + }); + + const [sorting, setSorting] = useState([]); + + const table = useReactTable({ + data, + columns, + state: { + sorting, + }, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }); + + if (isLoading) return
Loading data...
; + if (error) return
Error loading data
; + + return ( +
+
+

People Data

+

TanStack React Query Table Example

+
+ +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + +
+
+ {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} + +
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ +
+
+ + +
+
+
+

+ Showing{' '} + {table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}{' '} + to{' '} + + {Math.min( + (table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize, + table.getRowCount() + )} + {' '} + of {table.getRowCount()} results +

+
+
+ +
+
+
+
+ ); +}; + +export { DPage } diff --git a/ServicesWeb/customer/src/components/custom/content/DashboardPage.tsx b/ServicesWeb/customer/src/components/custom/content/DashboardPage.tsx index 6b37bf5..b06ff1d 100644 --- a/ServicesWeb/customer/src/components/custom/content/DashboardPage.tsx +++ b/ServicesWeb/customer/src/components/custom/content/DashboardPage.tsx @@ -9,20 +9,22 @@ import EarningsTable from '@/components/custom/EarningsTable'; interface DashboardPageProps { searchParams: Record; + activePageUrl?: string; + userData?: any; userLoading?: boolean; userError?: any; refreshUser?: () => void; updateUser?: (data: any) => void; + onlineData?: any; onlineLoading?: boolean; onlineError?: any; refreshOnline?: () => void; updateOnline?: (data: any) => void; - activePageUrl?: string; } -export const DashboardPage: FC = ({ +const DashboardPage: FC = ({ searchParams, userData, userLoading, @@ -165,3 +167,8 @@ export const DashboardPage: FC = ({ ); }; + + +export { + DashboardPage +} \ No newline at end of file diff --git a/ServicesWeb/customer/src/components/custom/menu/menuItemsSection.tsx b/ServicesWeb/customer/src/components/custom/menu/menuItemsSection.tsx index 2d52f3d..079daab 100644 --- a/ServicesWeb/customer/src/components/custom/menu/menuItemsSection.tsx +++ b/ServicesWeb/customer/src/components/custom/menu/menuItemsSection.tsx @@ -11,7 +11,6 @@ import { MenuStructure, MenuItemsSectionProps, } from "./types"; -import MenuEmptyState from "./menuEmptyState"; const menuStaticTranslation = { tr: { menu: "Menü" }, diff --git a/ServicesWeb/customer/src/fetchers/custom/context/complete/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/complete/fetch.tsx index d522dff..bf26859 100644 --- a/ServicesWeb/customer/src/fetchers/custom/context/complete/fetch.tsx +++ b/ServicesWeb/customer/src/fetchers/custom/context/complete/fetch.tsx @@ -1,8 +1,8 @@ "use server"; -import { redis } from "@/lib/redis"; import { functionRetrieveUserSelection } from "@/fetchers/fecther"; import { ClientRedisToken, defaultClientRedisToken } from "@/fetchers/types/context"; import { REDIS_TIMEOUT } from "@/fetchers/base"; +import { safeRedisGet, safeRedisSet, safeJsonParse } from "@/utils/redisOperations"; /** * Gets the complete data from Redis with improved error handling and timeouts @@ -16,14 +16,12 @@ const getCompleteFromRedis = async (): Promise => { if (!redisKey) { return defaultClientRedisToken } if (redisKey === "default") { return defaultClientRedisToken } - try { - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT); - }); - const result = await Promise.race([redis.get(`${redisKey}`), timeoutPromise]); - if (!result) { return defaultClientRedisToken } - try { const parsedResult = JSON.parse(result); return parsedResult } catch (parseError) { return defaultClientRedisToken } - } catch (redisError) { return defaultClientRedisToken } + // Use safe Redis get operation with proper connection handling + const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT); + if (!result) { return defaultClientRedisToken } + + // Use safe JSON parsing + return safeJsonParse(result, defaultClientRedisToken); } catch (error) { return defaultClientRedisToken } } @@ -40,11 +38,8 @@ const setCompleteToRedis = async (completeObject: ClientRedisToken): Promise { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); - await Promise.race([redis.set(redisKey, JSON.stringify(completeObject)), timeoutPromise]); - return true; - } catch (redisError) { return false } + // Use safe Redis set operation with proper connection handling + return await safeRedisSet(redisKey, JSON.stringify(completeObject), REDIS_TIMEOUT); } catch (error) { return false } } @@ -58,11 +53,8 @@ const setNewCompleteToRedis = async (completeObject: ClientRedisToken, redisKey: try { if (!redisKey) { return false } if (!completeObject) { return false } - try { - const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); - await Promise.race([redis.set(redisKey, JSON.stringify(completeObject)), timeoutPromise]) - return true; - } catch (redisError) { return false } + // Use safe Redis set operation with proper connection handling + return await safeRedisSet(redisKey, JSON.stringify(completeObject), REDIS_TIMEOUT); } catch (error) { return false } } diff --git a/ServicesWeb/customer/src/fetchers/custom/context/dash/selection/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/dash/selection/fetch.tsx index 42eeada..bf024e0 100644 --- a/ServicesWeb/customer/src/fetchers/custom/context/dash/selection/fetch.tsx +++ b/ServicesWeb/customer/src/fetchers/custom/context/dash/selection/fetch.tsx @@ -1,21 +1,27 @@ "use server"; -import { redis } from "@/lib/redis"; import { functionRetrieveUserSelection } from "@/fetchers/fecther"; import { ClientSelection, AuthError } from "@/fetchers/types/context"; +import { defaultValuesSelection } from "@/fetchers/types/context/selection/validations"; import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; import { loginSelectEmployee, loginSelectOccupant } from "@/fetchers/custom/login/login"; +import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations"; +import { REDIS_TIMEOUT } from "@/fetchers/base"; const getSelectionFromRedis = async (): Promise => { try { const decrpytUserSelection = await functionRetrieveUserSelection(); const redisKey = decrpytUserSelection?.redisKey; - if (redisKey === "default") { return { selectionList: [], activeSelection: {} } } - const result = await redis.get(`${redisKey}`); - if (!result) { return { selectionList: [], activeSelection: {} } } - const parsedResult = JSON.parse(result); - if (!parsedResult.selection) { return { selectionList: [], activeSelection: {} } } + if (redisKey === "default") { return defaultValuesSelection } + + // Use safe Redis get operation with proper connection handling + const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT); + if (!result) { return defaultValuesSelection } + + // Use safe JSON parsing with proper default object type + const parsedResult = safeJsonParse(result, { selection: defaultValuesSelection }); + if (!parsedResult.selection) { return defaultValuesSelection } return parsedResult.selection; - } catch (error) { return { selectionList: [], activeSelection: {} } } + } catch (error) { return defaultValuesSelection } } const setActiveSelectionToRedis = async (selectionObject: any) => { @@ -37,11 +43,11 @@ const setActiveSelectionToRedis = async (selectionObject: any) => { activeSelection: selectionObject } }) - console.log("oldData", oldData) if (oldData.online.userType.toUpperCase() === "EMPLOYEE") { console.log("selectionObject", selectionObject) await loginSelectEmployee({ uuid: selectionObject.uu_id }); } else if (oldData.online.userType.toUpperCase() === "OCCUPANT") { + console.log("selectionObject", selectionObject.build_living_space_uu_id) await loginSelectOccupant({ uuid: selectionObject.build_living_space_uu_id }); } return true; diff --git a/ServicesWeb/customer/src/fetchers/custom/context/dash/settings/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/dash/settings/fetch.tsx index 0d0f0fc..21fa8b1 100644 --- a/ServicesWeb/customer/src/fetchers/custom/context/dash/settings/fetch.tsx +++ b/ServicesWeb/customer/src/fetchers/custom/context/dash/settings/fetch.tsx @@ -1,17 +1,22 @@ "use server"; -import { redis } from "@/lib/redis"; import { functionRetrieveUserSelection } from "@/fetchers/fecther"; import { ClientSettings, AuthError } from "@/fetchers/types/context"; +import { defaultValuesSettings } from "@/fetchers/types/context/settings/validations"; import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; +import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations"; +import { REDIS_TIMEOUT } from "@/fetchers/base"; const getSettingsFromRedis = async (): Promise => { try { const decrpytUserSelection = await functionRetrieveUserSelection() const redisKey = decrpytUserSelection?.redisKey; if (!redisKey) throw new AuthError("No redis key found"); - const result = await redis.get(`${redisKey}`); + // Use safe Redis get operation with proper connection handling + const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT); if (!result) throw new AuthError("No data found in redis"); - const parsedResult = JSON.parse(result); + + // Use safe JSON parsing with proper default object type + const parsedResult = safeJsonParse(result, { settings: defaultValuesSettings }); if (!parsedResult.settings) throw new AuthError("No settings found in redis"); return parsedResult.settings; } catch (error) { diff --git a/ServicesWeb/customer/src/fetchers/custom/context/dash/user/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/dash/user/fetch.tsx index 4399ab8..7975bc6 100644 --- a/ServicesWeb/customer/src/fetchers/custom/context/dash/user/fetch.tsx +++ b/ServicesWeb/customer/src/fetchers/custom/context/dash/user/fetch.tsx @@ -1,17 +1,22 @@ "use server"; -import { redis } from "@/lib/redis"; import { functionRetrieveUserSelection } from "@/fetchers/fecther"; import { ClientUser, AuthError } from "@/fetchers/types/context"; +import { defaultValuesUser } from "@/fetchers/types/context/user/validations"; import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; +import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations"; +import { REDIS_TIMEOUT } from "@/fetchers/base"; const getUserFromRedis = async (): Promise => { try { const decrpytUserSelection = await functionRetrieveUserSelection() const redisKey = decrpytUserSelection?.redisKey; if (!redisKey) throw new AuthError("No redis key found"); - const result = await redis.get(`${redisKey}`); + // Use safe Redis get operation with proper connection handling + const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT); if (!result) throw new AuthError("No data found in redis"); - const parsedResult = JSON.parse(result); + + // Use safe JSON parsing with proper default object type + const parsedResult = safeJsonParse(result, { user: defaultValuesUser }); if (!parsedResult.user) throw new AuthError("No user found in redis"); return parsedResult.user; } catch (error) { diff --git a/ServicesWeb/customer/src/fetchers/custom/context/page/config/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/page/config/fetch.tsx index f84900e..df264ed 100644 --- a/ServicesWeb/customer/src/fetchers/custom/context/page/config/fetch.tsx +++ b/ServicesWeb/customer/src/fetchers/custom/context/page/config/fetch.tsx @@ -1,17 +1,22 @@ "use server"; -import { redis } from "@/lib/redis"; import { functionRetrieveUserSelection } from "@/fetchers/fecther"; import { ClientSettings, AuthError } from "@/fetchers/types/context"; +import { defaultValuesSettings } from "@/fetchers/types/context/settings/validations"; import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; +import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations"; +import { REDIS_TIMEOUT } from "@/fetchers/base"; const getConfigFromRedis = async (): Promise => { try { const decrpytUserSelection = await functionRetrieveUserSelection() const redisKey = decrpytUserSelection?.redisKey; if (!redisKey) throw new AuthError("No redis key found"); - const result = await redis.get(`${redisKey}`); + // Use safe Redis get operation with proper connection handling + const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT); if (!result) throw new AuthError("No data found in redis"); - const parsedResult = JSON.parse(result); + + // Use safe JSON parsing with proper default object type + const parsedResult = safeJsonParse(result, { settings: defaultValuesSettings }); if (!parsedResult.settings) throw new AuthError("No settings found in redis"); return parsedResult.settings; } catch (error) { diff --git a/ServicesWeb/customer/src/fetchers/custom/context/page/menu/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/page/menu/fetch.tsx index af7ef26..98a4185 100644 --- a/ServicesWeb/customer/src/fetchers/custom/context/page/menu/fetch.tsx +++ b/ServicesWeb/customer/src/fetchers/custom/context/page/menu/fetch.tsx @@ -1,17 +1,22 @@ "use server"; -import { redis } from "@/lib/redis"; import { functionRetrieveUserSelection } from "@/fetchers/fecther"; import { ClientMenu, AuthError } from "@/fetchers/types/context"; +import { defaultValuesMenu } from "@/fetchers/types/context/menu/validations"; import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; +import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations"; +import { REDIS_TIMEOUT } from "@/fetchers/base"; const getMenuFromRedis = async (): Promise => { try { const decrpytUserSelection = await functionRetrieveUserSelection() const redisKey = decrpytUserSelection?.redisKey; if (!redisKey) throw new AuthError("No redis key found"); - const result = await redis.get(`${redisKey}`); + // Use safe Redis get operation with proper connection handling + const result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT); if (!result) throw new AuthError("No data found in redis"); - const parsedResult = JSON.parse(result); + + // Use safe JSON parsing with proper default object type + const parsedResult = safeJsonParse(result, { menu: defaultValuesMenu }); if (!parsedResult.menu) throw new AuthError("No menu found in redis"); return parsedResult.menu; } catch (error) { if (error instanceof AuthError) { throw error } else { throw new AuthError(error instanceof Error ? error.message : "Unknown error") } } diff --git a/ServicesWeb/customer/src/fetchers/custom/context/page/online/fetch.tsx b/ServicesWeb/customer/src/fetchers/custom/context/page/online/fetch.tsx index 15889ad..c691cf0 100644 --- a/ServicesWeb/customer/src/fetchers/custom/context/page/online/fetch.tsx +++ b/ServicesWeb/customer/src/fetchers/custom/context/page/online/fetch.tsx @@ -1,8 +1,9 @@ "use server"; -import { redis } from "@/lib/redis"; import { functionRetrieveUserSelection } from "@/fetchers/fecther"; import { ClientOnline, AuthError } from "@/fetchers/types/context"; +import { defaultValuesOnline } from "@/fetchers/types/context/online/validations"; import { getCompleteFromRedis, setCompleteToRedis } from "@/fetchers/custom/context/complete/fetch"; +import { safeRedisGet, safeJsonParse } from "@/utils/redisOperations"; import { REDIS_TIMEOUT } from "@/fetchers/base"; /** @@ -18,14 +19,15 @@ const getOnlineFromRedis = async (): Promise => { if (!redisKey) { throw new AuthError('No redis key found') } if (redisKey === "default") { throw new AuthError('Invalid redis key') } try { - const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); - result = await Promise.race([redis.get(`${redisKey}`), timeoutPromise]) as string | null; + // Use safe Redis get operation with proper connection handling + result = await safeRedisGet(`${redisKey}`, REDIS_TIMEOUT); } catch (redisError) { throw new AuthError('Failed to access Redis data') } if (!result) { throw new AuthError('No data found in redis') } try { - const parsedResult = JSON.parse(result); + // Use safe JSON parsing with proper default object type + const parsedResult = safeJsonParse(result, { online: defaultValuesOnline }); if (!parsedResult.online) { throw new AuthError('No online data found in redis') } return parsedResult.online; } catch (parseError) { throw new AuthError('Invalid data format in redis') } @@ -51,8 +53,8 @@ const setOnlineToRedis = async (onlineObject: ClientOnline): Promise => try { oldData = await getCompleteFromRedis() } catch (error) { throw new AuthError('Failed to retrieve existing data from Redis') } if (!oldData) { throw new AuthError('No old data found in redis') } try { - const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Redis operation timed out')), REDIS_TIMEOUT) }); - await Promise.race([setCompleteToRedis({ ...oldData, online: onlineObject }), timeoutPromise]); + // Use the setCompleteToRedis function which already uses safe Redis operations + await setCompleteToRedis({ ...oldData, online: onlineObject }); return true; } catch (redisError) { throw new AuthError('Failed to update Redis data') } } catch (error) { if (error instanceof AuthError) throw error; throw new AuthError(error instanceof Error ? error.message : 'Unknown error') } diff --git a/ServicesWeb/customer/src/fetchers/custom/login/login.tsx b/ServicesWeb/customer/src/fetchers/custom/login/login.tsx index 4c08f32..c511587 100644 --- a/ServicesWeb/customer/src/fetchers/custom/login/login.tsx +++ b/ServicesWeb/customer/src/fetchers/custom/login/login.tsx @@ -62,6 +62,7 @@ async function initRedis(loginRespone: any, firstSelection: any, accessToken: st async function initFirstSelection(firstSelection: any, userType: string) { if (userType === "EMPLOYEE") { const uuid = firstSelection.uu_id; + console.log("uuid", uuid) await loginSelectEmployee({ uuid }); } else if (userType === "OCCUPANT") { const uuid = firstSelection.build_living_space_uu_id; @@ -99,8 +100,6 @@ async function loginViaAccessKeys(payload: LoginViaAccessKeys) { const redisKeyAccess = await nextCrypto.encrypt(redisKey); const usersSelection = await nextCrypto.encrypt(JSON.stringify({ selected: firstSelection, userType, redisKey })); - await initRedis(loginRespone, firstSelection, accessToken, redisKey); - cookieStore.set({ name: "eys-zzz", value: accessToken, @@ -117,6 +116,8 @@ async function loginViaAccessKeys(payload: LoginViaAccessKeys) { ...cookieObject, }); + await initRedis(loginRespone, firstSelection, accessToken, redisKey); + try { return { completed: true, @@ -156,11 +157,7 @@ async function loginSelectEmployee(payload: LoginSelect) { const employeeUUID = payload.uuid; const selectResponse: any = await fetchDataWithToken(urlLoginSelectEndpoint, { uuid: employeeUUID }, "POST", false); if (selectResponse.status === 200 || selectResponse.status === 202) { - try { - console.log("selectResponse", selectResponse) // Get Menu URL's of Employee - const validUrls = await retrieveValidUrlsOfRestriction() - setMenuToRedis(validUrls) - } catch (error) { } + try { setMenuToRedis(selectResponse.data.reachable_app_codes || []) } catch (error) { } } return selectResponse; } @@ -169,11 +166,7 @@ async function loginSelectOccupant(payload: LoginSelect) { const livingSpaceUUID = payload.uuid; const selectResponse: any = await fetchDataWithToken(urlLoginSelectEndpoint, { uuid: livingSpaceUUID }, "POST", false); if (selectResponse.status === 200 || selectResponse.status === 202) { - try { - console.log("selectResponse", selectResponse) // Get Menu URL's of Occupant - const validUrls = await retrieveValidUrlsOfRestriction() - setMenuToRedis(validUrls) - } catch (error) { } + try { setMenuToRedis(selectResponse.data.reachable_app_codes || []) } catch (error) { } } return selectResponse; } diff --git a/ServicesWeb/customer/src/pages/multi/index.ts b/ServicesWeb/customer/src/pages/multi/index.ts index c29f415..edc7542 100644 --- a/ServicesWeb/customer/src/pages/multi/index.ts +++ b/ServicesWeb/customer/src/pages/multi/index.ts @@ -1,7 +1,8 @@ -import { DashboardPage } from "@/components/custom/content/DashboardPage"; +// import { DashboardPage } from "@/components/custom/content/DashboardPage"; +import { DPage } from "@/components/custom/content/DPage"; const pageIndexMulti: Record>> = { - "/dashboard": { DashboardPage: DashboardPage }, + "/dashboard": { DashboardPage: DPage }, }; export { pageIndexMulti }; diff --git a/ServicesWeb/customer/src/utils/configureRedis.ts b/ServicesWeb/customer/src/utils/configureRedis.ts new file mode 100644 index 0000000..7f0c1fd --- /dev/null +++ b/ServicesWeb/customer/src/utils/configureRedis.ts @@ -0,0 +1,66 @@ +// Configure Redis client to prevent EventEmitter memory leak warnings +import { EventEmitter } from "events"; + +// Increase the default maximum listeners for all EventEmitter instances +EventEmitter.defaultMaxListeners = 20; + +// This file will be imported in layout.tsx to configure Redis early in the app lifecycle +export const configureRedisEmitters = () => { + try { + // Attempt to dynamically import the Redis client + import("@/lib/redis") + .then((redisModule) => { + const redis = redisModule.redis; + + if (!redis) return; + + // Use type assertion to access any potential EventEmitter methods + const redisAny = redis as any; + + // Configure Redis client's EventEmitter interfaces + if (typeof redisAny.setMaxListeners === "function") { + redisAny.setMaxListeners(20); + } + + // Many Redis clients have internal EventEmitter instances with different names + // Check for common patterns like 'command', 'commander', etc. + const possibleEmitterProps = [ + "command", + "commander", + "connector", + "connection", + "client", + "subscriber", + ]; + + possibleEmitterProps.forEach((prop) => { + if ( + redisAny[prop] && + typeof redisAny[prop].setMaxListeners === "function" + ) { + redisAny[prop].setMaxListeners(20); + } + }); + + // Handle any nested emitters that might be causing the warning + if ( + redisAny._events && + Object.keys(redisAny._events).includes("connect") + ) { + console.log( + "Found connect event listeners, likely source of warning" + ); + } + + console.log("Redis EventEmitter configured successfully"); + }) + .catch((err) => { + console.warn("Failed to configure Redis EventEmitter:", err); + }); + } catch (error) { + console.warn("Error configuring Redis EventEmitter:", error); + } +}; + +// Execute configuration immediately +configureRedisEmitters(); diff --git a/ServicesWeb/customer/src/utils/eventEmitterConfig.ts b/ServicesWeb/customer/src/utils/eventEmitterConfig.ts new file mode 100644 index 0000000..e7ca08e --- /dev/null +++ b/ServicesWeb/customer/src/utils/eventEmitterConfig.ts @@ -0,0 +1,12 @@ +// Increase the default maximum listeners for EventEmitter instances +import { EventEmitter } from 'events'; + +// Set a higher limit for all EventEmitter instances +EventEmitter.defaultMaxListeners = 20; + +// Export a function to set max listeners for a specific emitter instance +export const setMaxListeners = (emitter: EventEmitter, max: number = 20): void => { + if (emitter && typeof emitter.setMaxListeners === 'function') { + emitter.setMaxListeners(max); + } +}; diff --git a/ServicesWeb/customer/src/utils/redisHelper.ts b/ServicesWeb/customer/src/utils/redisHelper.ts new file mode 100644 index 0000000..4c7d9b1 --- /dev/null +++ b/ServicesWeb/customer/src/utils/redisHelper.ts @@ -0,0 +1,38 @@ +// Helper functions for Redis client management +import { EventEmitter } from 'events'; + +/** + * Safely increases max listeners for any EventEmitter instance + * This is particularly useful for Redis clients that may create many listeners + */ +export function configureRedisEventEmitter(emitter: any, maxListeners: number = 20): void { + if (emitter && typeof emitter.setMaxListeners === 'function') { + emitter.setMaxListeners(maxListeners); + } + + // If the emitter has a commander property (common in Redis clients) + if (emitter && emitter.commander && typeof emitter.commander.setMaxListeners === 'function') { + emitter.commander.setMaxListeners(maxListeners); + } +} + +/** + * Creates a singleton wrapper for Redis clients to prevent connection duplication + * @param createClientFn Function that creates a Redis client + * @returns A singleton Redis client + */ +export function createRedisSingleton(createClientFn: () => T): () => T { + let instance: T | null = null; + + return () => { + if (!instance) { + instance = createClientFn(); + + // Configure event emitters if applicable + if (instance && typeof (instance as any).setMaxListeners === 'function') { + configureRedisEventEmitter(instance); + } + } + return instance; + }; +} diff --git a/ServicesWeb/customer/src/utils/redisOperations.ts b/ServicesWeb/customer/src/utils/redisOperations.ts new file mode 100644 index 0000000..954eaad --- /dev/null +++ b/ServicesWeb/customer/src/utils/redisOperations.ts @@ -0,0 +1,66 @@ +// Redis operations with proper connection management +import { redis } from "@/lib/redis"; + +/** + * Safely executes a Redis get operation with proper connection handling + * @param key Redis key to retrieve + * @param timeoutMs Timeout in milliseconds + * @returns The value from Redis or null + */ +export async function safeRedisGet(key: string, timeoutMs: number = 5000): Promise { + try { + // Create timeout promise + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Redis operation timed out')), timeoutMs); + }); + + // Execute the Redis get operation with timeout + const result = await Promise.race([redis.get(key), timeoutPromise]); + + return result; + } catch (error) { + console.error('Redis get operation failed:', error); + return null; + } +} + +/** + * Safely executes a Redis set operation with proper connection handling + * @param key Redis key to set + * @param value Value to store + * @param timeoutMs Timeout in milliseconds + * @returns True if successful, false otherwise + */ +export async function safeRedisSet(key: string, value: string, timeoutMs: number = 5000): Promise { + try { + // Create timeout promise + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Redis operation timed out')), timeoutMs); + }); + + // Execute the Redis set operation with timeout + await Promise.race([redis.set(key, value), timeoutPromise]); + + return true; + } catch (error) { + console.error('Redis set operation failed:', error); + return false; + } +} + +/** + * Safely parses JSON from Redis + * @param jsonString JSON string to parse + * @param defaultValue Default value to return if parsing fails + * @returns Parsed object or default value + */ +export function safeJsonParse(jsonString: string | null, defaultValue: T): T { + if (!jsonString) return defaultValue; + + try { + return JSON.parse(jsonString) as T; + } catch (error) { + console.error('Failed to parse JSON from Redis:', error); + return defaultValue; + } +} diff --git a/example_component.html b/example_component.html deleted file mode 100755 index cbcfaf6..0000000 --- a/example_component.html +++ /dev/null @@ -1,2288 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - Admin Panel - - - - - - - - - -
- -
- - - -
- - - -
-
-
-
-
-
-
2
-
-
Users
-
- -
- - View -
-
-
-
-
-
100
-
+30%
-
-
Companies
-
- -
- View -
-
-
-
-
100
-
Blogs
-
- -
- View -
-
-
-
-
-
-
-

Users

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RoleAmount
Administrator1 -
- 70% -
-
-
-
-
-
-
User6 -
- 40% -
-
-
-
-
-
-
User5 -
- 45% -
-
-
-
-
-
-
User4 -
- 60% -
-
-
-
-
-
-
-
-
-
-
-
-
Activities
- -
-
- - - - - - - - - - - - - - - -
- - - 02-02-2024 - - 17.45 - - -
- - - 02-02-2024 - - 17.45 - - -
-
-
-
-
-
-
-
Order Statistics
- -
-
-
-
-
10
- $80 -
- Active -
-
-
-
50
- +$469 -
- Completed -
-
-
-
4
- -$130 -
- Canceled -
-
-
- -
-
-
-
-
Earnings
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ServiceEarningStatus
- - - +$235 - - Pending -
- - - -$235 - - Withdrawn -
- - - +$235 - - Pending -
- - - -$235 - - Withdrawn -
- - - +$235 - - Pending -
- - - -$235 - - Withdrawn -
- - - +$235 - - Pending -
- - - -$235 - - Withdrawn -
- - - +$235 - - Pending -
- - - -$235 - - Withdrawn -
-
-
-
-
- -
- - - - - - - - - - diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..11e071f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,48 @@ +{ + "name": "production-evyos-systems-and-services-3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@tanstack/react-query": "^5.80.7" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.80.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.80.7.tgz", + "integrity": "sha512-s09l5zeUKC8q7DCCCIkVSns8zZrK4ZDT6ryEjxNBFi68G4z2EBobBS7rdOY3r6W1WbUDpc1fe5oY+YO/+2UVUg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.80.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.80.7.tgz", + "integrity": "sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.80.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4d769c5 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@tanstack/react-query": "^5.80.7" + } +}