update config future #1
			
				
			
		
		
		
	| 
						 | 
					@ -5,27 +5,29 @@ import eslintPluginReactHooks from 'eslint-plugin-react-hooks'
 | 
				
			||||||
import eslintPluginReactRefresh from 'eslint-plugin-react-refresh'
 | 
					import eslintPluginReactRefresh from 'eslint-plugin-react-refresh'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default tseslint.config(
 | 
					export default tseslint.config(
 | 
				
			||||||
  { ignores: ['**/node_modules', '**/dist', '**/out'] },
 | 
					    { ignores: ['**/node_modules', '**/dist', '**/out'] },
 | 
				
			||||||
  tseslint.configs.recommended,
 | 
					    tseslint.configs.recommended,
 | 
				
			||||||
  eslintPluginReact.configs.flat.recommended,
 | 
					    eslintPluginReact.configs.flat.recommended,
 | 
				
			||||||
  eslintPluginReact.configs.flat['jsx-runtime'],
 | 
					    eslintPluginReact.configs.flat['jsx-runtime'],
 | 
				
			||||||
  {
 | 
					    {
 | 
				
			||||||
    settings: {
 | 
					        settings: {
 | 
				
			||||||
      react: {
 | 
					            react: {
 | 
				
			||||||
        version: 'detect'
 | 
					                version: 'detect'
 | 
				
			||||||
      }
 | 
					            }
 | 
				
			||||||
    }
 | 
					        }
 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    files: ['**/*.{ts,tsx}'],
 | 
					 | 
				
			||||||
    plugins: {
 | 
					 | 
				
			||||||
      'react-hooks': eslintPluginReactHooks,
 | 
					 | 
				
			||||||
      'react-refresh': eslintPluginReactRefresh
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    rules: {
 | 
					    {
 | 
				
			||||||
      ...eslintPluginReactHooks.configs.recommended.rules,
 | 
					        files: ['**/*.{ts,tsx}'],
 | 
				
			||||||
      ...eslintPluginReactRefresh.configs.vite.rules
 | 
					        plugins: {
 | 
				
			||||||
    }
 | 
					            'react-hooks': eslintPluginReactHooks,
 | 
				
			||||||
  },
 | 
					            'react-refresh': eslintPluginReactRefresh
 | 
				
			||||||
  eslintConfigPrettier
 | 
					        },
 | 
				
			||||||
 | 
					        rules: {
 | 
				
			||||||
 | 
					            ...eslintPluginReactHooks.configs.recommended.rules,
 | 
				
			||||||
 | 
					            ...eslintPluginReactRefresh.configs.vite.rules,
 | 
				
			||||||
 | 
					            'react/prop-types': 'off',
 | 
				
			||||||
 | 
					            '@typescript-eslint/explicit-function-return-type': 'off'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    eslintConfigPrettier
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,9 +12,11 @@
 | 
				
			||||||
                "@electron-toolkit/preload": "^3.0.1",
 | 
					                "@electron-toolkit/preload": "^3.0.1",
 | 
				
			||||||
                "@electron-toolkit/utils": "^4.0.0",
 | 
					                "@electron-toolkit/utils": "^4.0.0",
 | 
				
			||||||
                "@mantine/core": "^8.0.0",
 | 
					                "@mantine/core": "^8.0.0",
 | 
				
			||||||
 | 
					                "@mantine/form": "^8.0.0",
 | 
				
			||||||
                "@mantine/hooks": "^8.0.0",
 | 
					                "@mantine/hooks": "^8.0.0",
 | 
				
			||||||
                "@mantine/notifications": "^8.0.0",
 | 
					                "@mantine/notifications": "^8.0.0",
 | 
				
			||||||
                "@mantine/vanilla-extract": "^8.0.0",
 | 
					                "@mantine/vanilla-extract": "^8.0.0",
 | 
				
			||||||
 | 
					                "@tabler/icons-react": "^3.31.0",
 | 
				
			||||||
                "axios": "^1.9.0",
 | 
					                "axios": "^1.9.0",
 | 
				
			||||||
                "electron-updater": "^6.3.9",
 | 
					                "electron-updater": "^6.3.9",
 | 
				
			||||||
                "moment": "^2.30.1",
 | 
					                "moment": "^2.30.1",
 | 
				
			||||||
| 
						 | 
					@ -1803,6 +1805,19 @@
 | 
				
			||||||
                "url": "https://github.com/sponsors/sindresorhus"
 | 
					                "url": "https://github.com/sponsors/sindresorhus"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/@mantine/form": {
 | 
				
			||||||
 | 
					            "version": "8.0.0",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@mantine/form/-/form-8.0.0.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-ErbbEFMEiRsK2Rn0jmFE5ohNJXHSMSbuJsL2vDUVsbIaXo6svw6ockw1WWGdiU8oEGqxM6Pd618yI9cJWNHF3g==",
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "fast-deep-equal": "^3.1.3",
 | 
				
			||||||
 | 
					                "klona": "^2.0.6"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "peerDependencies": {
 | 
				
			||||||
 | 
					                "react": "^18.x || ^19.x"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "node_modules/@mantine/hooks": {
 | 
					        "node_modules/@mantine/hooks": {
 | 
				
			||||||
            "version": "8.0.0",
 | 
					            "version": "8.0.0",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.0.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.0.0.tgz",
 | 
				
			||||||
| 
						 | 
					@ -2276,6 +2291,32 @@
 | 
				
			||||||
                "node": ">=10"
 | 
					                "node": ">=10"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/@tabler/icons": {
 | 
				
			||||||
 | 
					            "version": "3.31.0",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.31.0.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-dblAdeKY3+GA1U+Q9eziZ0ooVlZMHsE8dqP0RkwvRtEsAULoKOYaCUOcJ4oW1DjWegdxk++UAt2SlQVnmeHv+g==",
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "funding": {
 | 
				
			||||||
 | 
					                "type": "github",
 | 
				
			||||||
 | 
					                "url": "https://github.com/sponsors/codecalm"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/@tabler/icons-react": {
 | 
				
			||||||
 | 
					            "version": "3.31.0",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.31.0.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-2rrCM5y/VnaVKnORpDdAua9SEGuJKVqPtWxeQ/vUVsgaUx30LDgBZph7/lterXxDY1IKR6NO//HDhWiifXTi3w==",
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "dependencies": {
 | 
				
			||||||
 | 
					                "@tabler/icons": "3.31.0"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "funding": {
 | 
				
			||||||
 | 
					                "type": "github",
 | 
				
			||||||
 | 
					                "url": "https://github.com/sponsors/codecalm"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "peerDependencies": {
 | 
				
			||||||
 | 
					                "react": ">= 16"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "node_modules/@tootallnate/once": {
 | 
					        "node_modules/@tootallnate/once": {
 | 
				
			||||||
            "version": "2.0.0",
 | 
					            "version": "2.0.0",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
 | 
				
			||||||
| 
						 | 
					@ -5660,7 +5701,6 @@
 | 
				
			||||||
            "version": "3.1.3",
 | 
					            "version": "3.1.3",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 | 
				
			||||||
            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
 | 
					            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
 | 
				
			||||||
            "dev": true,
 | 
					 | 
				
			||||||
            "license": "MIT"
 | 
					            "license": "MIT"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "node_modules/fast-diff": {
 | 
					        "node_modules/fast-diff": {
 | 
				
			||||||
| 
						 | 
					@ -7370,6 +7410,15 @@
 | 
				
			||||||
                "json-buffer": "3.0.1"
 | 
					                "json-buffer": "3.0.1"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/klona": {
 | 
				
			||||||
 | 
					            "version": "2.0.6",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "engines": {
 | 
				
			||||||
 | 
					                "node": ">= 8"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "node_modules/lazy-val": {
 | 
					        "node_modules/lazy-val": {
 | 
				
			||||||
            "version": "1.0.5",
 | 
					            "version": "1.0.5",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,9 +24,11 @@
 | 
				
			||||||
        "@electron-toolkit/preload": "^3.0.1",
 | 
					        "@electron-toolkit/preload": "^3.0.1",
 | 
				
			||||||
        "@electron-toolkit/utils": "^4.0.0",
 | 
					        "@electron-toolkit/utils": "^4.0.0",
 | 
				
			||||||
        "@mantine/core": "^8.0.0",
 | 
					        "@mantine/core": "^8.0.0",
 | 
				
			||||||
 | 
					        "@mantine/form": "^8.0.0",
 | 
				
			||||||
        "@mantine/hooks": "^8.0.0",
 | 
					        "@mantine/hooks": "^8.0.0",
 | 
				
			||||||
        "@mantine/notifications": "^8.0.0",
 | 
					        "@mantine/notifications": "^8.0.0",
 | 
				
			||||||
        "@mantine/vanilla-extract": "^8.0.0",
 | 
					        "@mantine/vanilla-extract": "^8.0.0",
 | 
				
			||||||
 | 
					        "@tabler/icons-react": "^3.31.0",
 | 
				
			||||||
        "axios": "^1.9.0",
 | 
					        "axios": "^1.9.0",
 | 
				
			||||||
        "electron-updater": "^6.3.9",
 | 
					        "electron-updater": "^6.3.9",
 | 
				
			||||||
        "moment": "^2.30.1",
 | 
					        "moment": "^2.30.1",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,10 @@
 | 
				
			||||||
 | 
					/* eslint-disable prettier/prettier */
 | 
				
			||||||
 | 
					/* eslint-disable @typescript-eslint/no-explicit-any */
 | 
				
			||||||
import { app, shell, BrowserWindow, ipcMain, screen, globalShortcut } from 'electron'
 | 
					import { app, shell, BrowserWindow, ipcMain, screen, globalShortcut } from 'electron'
 | 
				
			||||||
import { join } from 'path'
 | 
					import path, { join } from 'path'
 | 
				
			||||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
 | 
					import { electronApp, optimizer, is } from '@electron-toolkit/utils'
 | 
				
			||||||
import icon from '../../resources/icon.png?asset'
 | 
					import icon from '../../resources/icon.png?asset'
 | 
				
			||||||
 | 
					import fs from 'fs'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createWindow(): void {
 | 
					function createWindow(): void {
 | 
				
			||||||
    // Get Screen width, height
 | 
					    // Get Screen width, height
 | 
				
			||||||
| 
						 | 
					@ -106,3 +109,56 @@ app.on('window-all-closed', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// In this file you can include the rest of your app's specific main process
 | 
					// In this file you can include the rest of your app's specific main process
 | 
				
			||||||
// code. You can also put them in separate files and require them here.
 | 
					// code. You can also put them in separate files and require them here.
 | 
				
			||||||
 | 
					// Custom events
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ipcMain.handle('save-config-file', async (_event, jsonContent: any) => {
 | 
				
			||||||
 | 
					    // const filePath = path.join(__dirname, 'config-data.json')
 | 
				
			||||||
 | 
					    const userDataPath = app.getPath('userData')
 | 
				
			||||||
 | 
					    const filePath = path.join(userDataPath, 'config-data.json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        let fileData: any = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 👀 Kiểm tra nếu file đã tồn tại
 | 
				
			||||||
 | 
					        if (fs.existsSync(filePath)) {
 | 
				
			||||||
 | 
					            const rawData = fs.readFileSync(filePath, 'utf-8')
 | 
				
			||||||
 | 
					            fileData = JSON.parse(rawData)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const now = new Date().getTime()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 📦 Ghi dữ liệu mới
 | 
				
			||||||
 | 
					        const newData = {
 | 
				
			||||||
 | 
					            ...jsonContent, // giữ dữ liệu mới (nếu bạn muốn merge thì sửa dòng này)
 | 
				
			||||||
 | 
					            created_at: fileData.created_at || now, // nếu có created_at thì giữ nguyên, không có thì lấy now
 | 
				
			||||||
 | 
					            updated_at: now // luôn cập nhật updated_at
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fs.writeFileSync(filePath, JSON.stringify(newData, null, 2), 'utf-8')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return { success: true, path: filePath }
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					        console.error('Error saving file:', error)
 | 
				
			||||||
 | 
					        return { success: false, error: error.message }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ipcMain.handle('get-config-file', async () => {
 | 
				
			||||||
 | 
					    const userDataPath = app.getPath('userData')
 | 
				
			||||||
 | 
					    const filePath = path.join(userDataPath, 'config-data.json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        // 📂 Kiểm tra file có tồn tại không
 | 
				
			||||||
 | 
					        if (!fs.existsSync(filePath)) {
 | 
				
			||||||
 | 
					            return { success: false, error: 'Config file does not exist' }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const rawData = fs.readFileSync(filePath, 'utf-8')
 | 
				
			||||||
 | 
					        const fileData = JSON.parse(rawData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return fileData
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					        console.error('Error reading config file:', error)
 | 
				
			||||||
 | 
					        return null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import { contextBridge } from 'electron'
 | 
					/* eslint-disable @typescript-eslint/no-explicit-any */
 | 
				
			||||||
 | 
					import { contextBridge, ipcRenderer } from 'electron'
 | 
				
			||||||
import { electronAPI } from '@electron-toolkit/preload'
 | 
					import { electronAPI } from '@electron-toolkit/preload'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Custom APIs for renderer
 | 
					// Custom APIs for renderer
 | 
				
			||||||
| 
						 | 
					@ -8,15 +9,21 @@ const api = {}
 | 
				
			||||||
// renderer only if context isolation is enabled, otherwise
 | 
					// renderer only if context isolation is enabled, otherwise
 | 
				
			||||||
// just add to the DOM global.
 | 
					// just add to the DOM global.
 | 
				
			||||||
if (process.contextIsolated) {
 | 
					if (process.contextIsolated) {
 | 
				
			||||||
  try {
 | 
					    try {
 | 
				
			||||||
    contextBridge.exposeInMainWorld('electron', electronAPI)
 | 
					        contextBridge.exposeInMainWorld('electron', electronAPI)
 | 
				
			||||||
    contextBridge.exposeInMainWorld('api', api)
 | 
					        contextBridge.exposeInMainWorld('api', api)
 | 
				
			||||||
  } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
    console.error(error)
 | 
					        console.error(error)
 | 
				
			||||||
  }
 | 
					    }
 | 
				
			||||||
} else {
 | 
					} else {
 | 
				
			||||||
  // @ts-ignore (define in dts)
 | 
					    // @ts-ignore (define in dts)
 | 
				
			||||||
  window.electron = electronAPI
 | 
					    window.electron = electronAPI
 | 
				
			||||||
  // @ts-ignore (define in dts)
 | 
					    // @ts-ignore (define in dts)
 | 
				
			||||||
  window.api = api
 | 
					    window.api = api
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					contextBridge.exposeInMainWorld('electron', {
 | 
				
			||||||
 | 
					    ipcRenderer: {
 | 
				
			||||||
 | 
					        invoke: (channel: string, ...args: any[]) => ipcRenderer.invoke(channel, ...args)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,7 @@
 | 
				
			||||||
import '@mantine/core/styles.css'
 | 
					/* eslint-disable @typescript-eslint/explicit-function-return-type */
 | 
				
			||||||
import '@mantine/notifications/styles.css'
 | 
					/* eslint-disable @typescript-eslint/no-explicit-any */
 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Suspense, useEffect, useState } from 'react'
 | 
					 | 
				
			||||||
import { pusher } from './pusher'
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    ActionIcon,
 | 
				
			||||||
    Box,
 | 
					    Box,
 | 
				
			||||||
    Container,
 | 
					    Container,
 | 
				
			||||||
    List,
 | 
					    List,
 | 
				
			||||||
| 
						 | 
					@ -11,27 +9,53 @@ import {
 | 
				
			||||||
    MantineProvider,
 | 
					    MantineProvider,
 | 
				
			||||||
    Skeleton,
 | 
					    Skeleton,
 | 
				
			||||||
    Text,
 | 
					    Text,
 | 
				
			||||||
    Title
 | 
					    Title,
 | 
				
			||||||
 | 
					    Tooltip
 | 
				
			||||||
} from '@mantine/core'
 | 
					} from '@mantine/core'
 | 
				
			||||||
import { theme } from './theme'
 | 
					import '@mantine/core/styles.css'
 | 
				
			||||||
import CardItem from './components/CardItem'
 | 
					import { useDisclosure } from '@mantine/hooks'
 | 
				
			||||||
import { Product } from '@renderer/types/Product'
 | 
					 | 
				
			||||||
import { listHotItem } from './api/products'
 | 
					 | 
				
			||||||
import { Notifications } from '@mantine/notifications'
 | 
					import { Notifications } from '@mantine/notifications'
 | 
				
			||||||
 | 
					import '@mantine/notifications/styles.css'
 | 
				
			||||||
 | 
					import { Product } from '@renderer/types/Product'
 | 
				
			||||||
 | 
					import { IconDotsVertical } from '@tabler/icons-react'
 | 
				
			||||||
 | 
					import moment from 'moment'
 | 
				
			||||||
 | 
					import { Suspense, useCallback, useEffect, useState } from 'react'
 | 
				
			||||||
 | 
					import { listHotItem } from './api/products'
 | 
				
			||||||
 | 
					import CardItem from './components/CardItem'
 | 
				
			||||||
 | 
					import ConfigModal from './components/ConfigModal'
 | 
				
			||||||
 | 
					import { pusher } from './pusher'
 | 
				
			||||||
 | 
					import { theme } from './theme'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    myNotificationStore,
 | 
					    myNotificationStore,
 | 
				
			||||||
    playNotificationSound,
 | 
					    playNotificationSound,
 | 
				
			||||||
    prependNotification
 | 
					    prependNotification
 | 
				
			||||||
} from './utils/Notificaton'
 | 
					} from './utils/Notificaton'
 | 
				
			||||||
import moment from 'moment'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const PUSHER_CHANNEL = import.meta.env.VITE_PUSHER_CHANNEL
 | 
					const PUSHER_CHANNEL = import.meta.env.VITE_PUSHER_CHANNEL
 | 
				
			||||||
const PUSHER_EVENT = 'App\\Events\\MessagePushed'
 | 
					const PUSHER_EVENT = 'App\\Events\\MessagePushed'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function App(): React.JSX.Element {
 | 
					function App(): React.JSX.Element {
 | 
				
			||||||
    const [newItem, setNewItem] = useState<Array<Product>>([])
 | 
					    const [newItems, setnewItems] = useState<Array<Product>>([])
 | 
				
			||||||
    const [hotItem, setHotItem] = useState<any>([])
 | 
					    const [hotItem, setHotItem] = useState<any>([])
 | 
				
			||||||
    const [isLoading, setIsLoading] = useState<boolean>(false)
 | 
					    const [isLoading, setIsLoading] = useState<boolean>(false)
 | 
				
			||||||
 | 
					    const [timeout, setTimeout] = useState(2000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const [opened, { open, close }] = useDisclosure(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const loadTimeOutData = async () => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const data = await window.electron.ipcRenderer.invoke('get-config-file')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!data?.noti_timeout) {
 | 
				
			||||||
 | 
					                await window.electron.ipcRenderer.invoke('save-config-file', { noti_timeout: 2 })
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            setTimeout(data.noti_timeout * 1000)
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.log('%csrc/renderer/src/App.tsx:48 error', 'color: #007acc;', error)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        // Subcribe Channel
 | 
					        // Subcribe Channel
 | 
				
			||||||
| 
						 | 
					@ -40,31 +64,34 @@ function App(): React.JSX.Element {
 | 
				
			||||||
        // Listen Event
 | 
					        // Listen Event
 | 
				
			||||||
        const callback = (data: any) => {
 | 
					        const callback = (data: any) => {
 | 
				
			||||||
            if (data.data && data.data?.id) {
 | 
					            if (data.data && data.data?.id) {
 | 
				
			||||||
                setNewItem((prev: Array<Product>) => [data.data, ...prev])
 | 
					                setnewItems((prev: Array<Product>) => [data.data, ...prev])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Show notification
 | 
					                // Show notification
 | 
				
			||||||
                prependNotification({
 | 
					                prependNotification(
 | 
				
			||||||
                    title: (
 | 
					                    {
 | 
				
			||||||
                        <Title order={4} className="text-overflow-1-line">
 | 
					                        title: (
 | 
				
			||||||
                            {data.data?.title}
 | 
					                            <Title order={4} className="text-overflow-1-line">
 | 
				
			||||||
                        </Title>
 | 
					                                {data.data?.title}
 | 
				
			||||||
                    ),
 | 
					                            </Title>
 | 
				
			||||||
                    message: (
 | 
					                        ),
 | 
				
			||||||
                        <Box>
 | 
					                        message: (
 | 
				
			||||||
                            <Text style={{ fontSize: 14 }} mb={2}>
 | 
					                            <Box>
 | 
				
			||||||
                                Price: <span className="bold-text">${data.data?.price}</span>, Site:{' '}
 | 
					                                <Text style={{ fontSize: 14 }} mb={2}>
 | 
				
			||||||
                                <span className="bold-text">{data.data?.from_site}</span>, Seller:{' '}
 | 
					                                    Price: <span className="bold-text">${data.data?.price}</span>,
 | 
				
			||||||
                                <span className="bold-text">{data.data?.seller}</span>
 | 
					                                    Site: <span className="bold-text">{data.data?.from_site}</span>,
 | 
				
			||||||
                            </Text>
 | 
					                                    Seller: <span className="bold-text">{data.data?.seller}</span>
 | 
				
			||||||
 | 
					                                </Text>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <Text style={{ fontSize: 12 }}>
 | 
					                                <Text style={{ fontSize: 12 }}>
 | 
				
			||||||
                                {moment(new Date()).format('HH:mm A')}
 | 
					                                    {moment(new Date()).format('HH:mm A')}
 | 
				
			||||||
                            </Text>
 | 
					                                </Text>
 | 
				
			||||||
                        </Box>
 | 
					                            </Box>
 | 
				
			||||||
                    ),
 | 
					                        ),
 | 
				
			||||||
                    autoClose: false,
 | 
					                        autoClose: isHotItem(data.data) ? false : timeout,
 | 
				
			||||||
                    color: 'orange'
 | 
					                        color: 'orange'
 | 
				
			||||||
                })
 | 
					                    },
 | 
				
			||||||
 | 
					                    () => isHotItem(data.data)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Play sound effect
 | 
					                // Play sound effect
 | 
				
			||||||
                playNotificationSound()
 | 
					                playNotificationSound()
 | 
				
			||||||
| 
						 | 
					@ -77,16 +104,20 @@ function App(): React.JSX.Element {
 | 
				
			||||||
            channel.unbind(PUSHER_EVENT, callback)
 | 
					            channel.unbind(PUSHER_EVENT, callback)
 | 
				
			||||||
            pusher.unsubscribe(PUSHER_CHANNEL)
 | 
					            pusher.unsubscribe(PUSHER_CHANNEL)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }, [])
 | 
					        // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
 | 
					    }, [timeout, hotItem])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        // load timeout config form local
 | 
				
			||||||
 | 
					        loadTimeOutData()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        getListHotItem()
 | 
					        getListHotItem()
 | 
				
			||||||
    }, [])
 | 
					    }, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const getListHotItem = async () => {
 | 
					    const getListHotItem = async () => {
 | 
				
			||||||
        setIsLoading(true)
 | 
					        setIsLoading(true)
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            let data = await listHotItem()
 | 
					            const data = await listHotItem()
 | 
				
			||||||
            setHotItem(data)
 | 
					            setHotItem(data)
 | 
				
			||||||
        } catch (error) {
 | 
					        } catch (error) {
 | 
				
			||||||
            console.log(error)
 | 
					            console.log(error)
 | 
				
			||||||
| 
						 | 
					@ -95,6 +126,15 @@ function App(): React.JSX.Element {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const isHotItem = useCallback(
 | 
				
			||||||
 | 
					        (item: Product) => {
 | 
				
			||||||
 | 
					            return hotItem.some((obj: any) =>
 | 
				
			||||||
 | 
					                item.title.toLowerCase().includes(obj.name.toLowerCase())
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        [hotItem]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <MantineProvider theme={theme}>
 | 
					        <MantineProvider theme={theme}>
 | 
				
			||||||
            <Notifications limit={10} store={myNotificationStore} position="top-right" />
 | 
					            <Notifications limit={10} store={myNotificationStore} position="top-right" />
 | 
				
			||||||
| 
						 | 
					@ -109,6 +149,14 @@ function App(): React.JSX.Element {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                <Container fluid mt={8}>
 | 
					                <Container fluid mt={8}>
 | 
				
			||||||
 | 
					                    <Box mb={'xs'}>
 | 
				
			||||||
 | 
					                        <Tooltip label="Settings">
 | 
				
			||||||
 | 
					                            <ActionIcon onClick={open} variant="subtle" radius="md" size="lg">
 | 
				
			||||||
 | 
					                                <IconDotsVertical size={18} />
 | 
				
			||||||
 | 
					                            </ActionIcon>
 | 
				
			||||||
 | 
					                        </Tooltip>
 | 
				
			||||||
 | 
					                    </Box>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    {isLoading ? (
 | 
					                    {isLoading ? (
 | 
				
			||||||
                        <Box>
 | 
					                        <Box>
 | 
				
			||||||
                            {Array.from({ length: 5 }).map((_, index) => (
 | 
					                            {Array.from({ length: 5 }).map((_, index) => (
 | 
				
			||||||
| 
						 | 
					@ -119,23 +167,19 @@ function App(): React.JSX.Element {
 | 
				
			||||||
                        </Box>
 | 
					                        </Box>
 | 
				
			||||||
                    ) : (
 | 
					                    ) : (
 | 
				
			||||||
                        <List>
 | 
					                        <List>
 | 
				
			||||||
                            {newItem?.length === 0 ? (
 | 
					                            {newItems?.length === 0 ? (
 | 
				
			||||||
                                <Box ta="center" mt={10}>
 | 
					                                <Box ta="center" mt={10}>
 | 
				
			||||||
                                    <Title c="#495057" order={4}>
 | 
					                                    <Title c="#495057" order={4}>
 | 
				
			||||||
                                        No new item found!!!
 | 
					                                        No new item found!!!
 | 
				
			||||||
                                    </Title>
 | 
					                                    </Title>
 | 
				
			||||||
                                </Box>
 | 
					                                </Box>
 | 
				
			||||||
                            ) : (
 | 
					                            ) : (
 | 
				
			||||||
                                newItem?.map((item: Product) => {
 | 
					                                newItems?.map((item: Product) => {
 | 
				
			||||||
                                    return (
 | 
					                                    return (
 | 
				
			||||||
                                        <CardItem
 | 
					                                        <CardItem
 | 
				
			||||||
                                            key={item?.id}
 | 
					                                            key={item?.id}
 | 
				
			||||||
                                            item={item}
 | 
					                                            item={item}
 | 
				
			||||||
                                            hotItem={hotItem.some((obj: any) =>
 | 
					                                            hotItem={isHotItem(item)}
 | 
				
			||||||
                                                item.title
 | 
					 | 
				
			||||||
                                                    .toLowerCase()
 | 
					 | 
				
			||||||
                                                    .includes(obj.name.toLowerCase())
 | 
					 | 
				
			||||||
                                            )}
 | 
					 | 
				
			||||||
                                        />
 | 
					                                        />
 | 
				
			||||||
                                    )
 | 
					                                    )
 | 
				
			||||||
                                })
 | 
					                                })
 | 
				
			||||||
| 
						 | 
					@ -143,6 +187,8 @@ function App(): React.JSX.Element {
 | 
				
			||||||
                        </List>
 | 
					                        </List>
 | 
				
			||||||
                    )}
 | 
					                    )}
 | 
				
			||||||
                </Container>
 | 
					                </Container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <ConfigModal onSaved={loadTimeOutData} opened={opened} onClose={close} />
 | 
				
			||||||
            </Suspense>
 | 
					            </Suspense>
 | 
				
			||||||
        </MantineProvider>
 | 
					        </MantineProvider>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					/* eslint-disable @typescript-eslint/explicit-function-return-type */
 | 
				
			||||||
import axios from './api-instance'
 | 
					import axios from './api-instance'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const listHotItem = async () => {
 | 
					export const listHotItem = async () => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,85 @@
 | 
				
			||||||
 | 
					import { Modal, ModalProps, NumberInput, Button, Stack } from '@mantine/core'
 | 
				
			||||||
 | 
					import { useForm } from '@mantine/form'
 | 
				
			||||||
 | 
					import { useEffect } from 'react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IConfigModalProps extends ModalProps {
 | 
				
			||||||
 | 
					    onSaved?: () => void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function ConfigModal(props: IConfigModalProps) {
 | 
				
			||||||
 | 
					    const form = useForm({
 | 
				
			||||||
 | 
					        initialValues: {
 | 
				
			||||||
 | 
					            timeout: 30 // default timeout 30s
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        validate: {
 | 
				
			||||||
 | 
					            timeout: (value) => (value <= 0 ? 'Timeout must be greater than 0' : null)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleSave = async () => {
 | 
				
			||||||
 | 
					        if (!form.isValid()) return // Nếu form lỗi thì không save
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const jsonContent = {
 | 
				
			||||||
 | 
					            noti_timeout: form.values.timeout
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const result = await window.electron.ipcRenderer.invoke('save-config-file', jsonContent)
 | 
				
			||||||
 | 
					            if (result.success) {
 | 
				
			||||||
 | 
					                props.onClose?.()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                props.onSaved?.()
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.error('Failed to save file:', result.error)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.error('IPC error:', error)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const loadConfigData = async () => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const data = await window.electron.ipcRenderer.invoke('get-config-file')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            console.log(
 | 
				
			||||||
 | 
					                '%csrc/renderer/src/components/ConfigModal.tsx:44 data',
 | 
				
			||||||
 | 
					                'color: #007acc;',
 | 
				
			||||||
 | 
					                data
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if (!data) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            form.setValues({ timeout: data?.noti_timeout })
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.log(
 | 
				
			||||||
 | 
					                '%csrc/renderer/src/components/ConfigModal.tsx:41 error',
 | 
				
			||||||
 | 
					                'color: #007acc;',
 | 
				
			||||||
 | 
					                error
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (props.opened) {
 | 
				
			||||||
 | 
					            loadConfigData()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
				
			||||||
 | 
					    }, [props.opened])
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Modal title="Configuration" {...props}>
 | 
				
			||||||
 | 
					            <form onSubmit={form.onSubmit(handleSave)}>
 | 
				
			||||||
 | 
					                <Stack>
 | 
				
			||||||
 | 
					                    <NumberInput
 | 
				
			||||||
 | 
					                        label="Timeout for notification (seconds)"
 | 
				
			||||||
 | 
					                        placeholder="Enter timeout in seconds"
 | 
				
			||||||
 | 
					                        min={1}
 | 
				
			||||||
 | 
					                        {...form.getInputProps('timeout')}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <Button type="submit" fullWidth>
 | 
				
			||||||
 | 
					                        Save
 | 
				
			||||||
 | 
					                    </Button>
 | 
				
			||||||
 | 
					                </Stack>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					        </Modal>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ import { createRoot } from 'react-dom/client'
 | 
				
			||||||
import App from './App'
 | 
					import App from './App'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
createRoot(document.getElementById('root')!).render(
 | 
					createRoot(document.getElementById('root')!).render(
 | 
				
			||||||
  <StrictMode>
 | 
					    <StrictMode>
 | 
				
			||||||
    <App />
 | 
					        <App />
 | 
				
			||||||
  </StrictMode>
 | 
					    </StrictMode>
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import Pusher from 'pusher-js'
 | 
					import Pusher from 'pusher-js'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const pusher = new Pusher(import.meta.env.VITE_PUSHER_APP_KEY, {
 | 
					export const pusher = new Pusher(import.meta.env.VITE_PUSHER_APP_KEY, {
 | 
				
			||||||
    cluster: 'ap4',
 | 
					    cluster: 'ap4'
 | 
				
			||||||
    encrypted: true
 | 
					    // encrypted: true
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +1,54 @@
 | 
				
			||||||
 | 
					// /* eslint-disable @typescript-eslint/explicit-function-return-type */
 | 
				
			||||||
 | 
					// import { createNotificationsStore, NotificationData } from '@mantine/notifications'
 | 
				
			||||||
 | 
					// import notifySound from '../assets/notifty.mp3'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// export const myNotificationStore = createNotificationsStore()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// export function prependNotification(notification: NotificationData, callback?: () => boolean) {
 | 
				
			||||||
 | 
					//     const id = notification.id ?? crypto.randomUUID()
 | 
				
			||||||
 | 
					//     myNotificationStore.updateState((current) => {
 | 
				
			||||||
 | 
					//         const existing = current.notifications.filter((n) => n.id !== id)
 | 
				
			||||||
 | 
					//         return {
 | 
				
			||||||
 | 
					//             ...current,
 | 
				
			||||||
 | 
					//             notifications: [
 | 
				
			||||||
 | 
					//                 {
 | 
				
			||||||
 | 
					//                     ...notification,
 | 
				
			||||||
 | 
					//                     id
 | 
				
			||||||
 | 
					//                 },
 | 
				
			||||||
 | 
					//                 ...existing
 | 
				
			||||||
 | 
					//             ]
 | 
				
			||||||
 | 
					//         }
 | 
				
			||||||
 | 
					//     })
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// export const playNotificationSound = () => {
 | 
				
			||||||
 | 
					//     const audio = new Audio(notifySound)
 | 
				
			||||||
 | 
					//     audio.play().catch((e) => console.error('Audio play failed:', e))
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					/* eslint-disable @typescript-eslint/explicit-function-return-type */
 | 
				
			||||||
import { createNotificationsStore, NotificationData } from '@mantine/notifications'
 | 
					import { createNotificationsStore, NotificationData } from '@mantine/notifications'
 | 
				
			||||||
import notifySound from '../assets/notifty.mp3'
 | 
					import notifySound from '../assets/notifty.mp3'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const myNotificationStore = createNotificationsStore()
 | 
					export const myNotificationStore = createNotificationsStore()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function prependNotification(notification: NotificationData) {
 | 
					export function prependNotification(notification: NotificationData, callback?: () => boolean) {
 | 
				
			||||||
    const id = notification.id ?? crypto.randomUUID()
 | 
					    const id = notification.id ?? crypto.randomUUID()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    myNotificationStore.updateState((current) => {
 | 
					    myNotificationStore.updateState((current) => {
 | 
				
			||||||
        const existing = current.notifications.filter((n) => n.id !== id)
 | 
					        const existing = current.notifications.filter((n) => n.id !== id)
 | 
				
			||||||
 | 
					        const shouldPrepend = callback ? callback() : false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            ...current,
 | 
					            ...current,
 | 
				
			||||||
            notifications: [
 | 
					            notifications: shouldPrepend
 | 
				
			||||||
                {
 | 
					                ? [
 | 
				
			||||||
                    ...notification,
 | 
					                      { ...notification, id }, // 👉 thêm lên đầu nếu callback = true
 | 
				
			||||||
                    id
 | 
					                      ...existing
 | 
				
			||||||
                },
 | 
					                  ]
 | 
				
			||||||
                ...existing
 | 
					                : [
 | 
				
			||||||
            ]
 | 
					                      ...existing,
 | 
				
			||||||
 | 
					                      { ...notification, id } // 👉 thêm xuống cuối nếu callback = false
 | 
				
			||||||
 | 
					                  ]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue