Merge pull request 'staging' (#6) from staging into production
Reviewed-on: #6
This commit is contained in:
commit
2a9fb6b506
|
|
@ -0,0 +1,2 @@
|
||||||
|
VITE_BASE_URL = 'http://localhost:4000/api/v1/admin/'
|
||||||
|
VITE_SOCKET_URL = 'http://localhost:4000'
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { Badge, Box, Button, Modal, ModalProps, PasswordInput, Text, TextInput, Tooltip } from '@mantine/core';
|
import { Badge, Box, Button, LoadingOverlay, Modal, ModalProps, PasswordInput, Text, TextInput, Tooltip } from '@mantine/core';
|
||||||
import { useForm, zodResolver } from '@mantine/form';
|
import { useForm, zodResolver } from '@mantine/form';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { IconPlus } from '@tabler/icons-react';
|
import { IconPlus } from '@tabler/icons-react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { createAdmin, updateAdmin } from '../../apis/admin';
|
import { createAdmin, updateAdmin } from '../../apis/admin';
|
||||||
import { useConfirmStore } from '../../lib/zustand/use-confirm';
|
import { useConfirmStore } from '../../lib/zustand/use-confirm';
|
||||||
|
|
@ -40,6 +40,8 @@ export default function AdminModal({ data, onUpdated, ...props }: IAdminModelPro
|
||||||
validate: zodResolver(data ? updateSchema : createSchema),
|
validate: zodResolver(data ? updateSchema : createSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
|
||||||
const { deletePermission, setPermissions, permissions, basePermission } = usePermissionStore();
|
const { deletePermission, setPermissions, permissions, basePermission } = usePermissionStore();
|
||||||
|
|
@ -54,7 +56,9 @@ export default function AdminModal({ data, onUpdated, ...props }: IAdminModelPro
|
||||||
title: 'Update ?',
|
title: 'Update ?',
|
||||||
message: `This account will be update`,
|
message: `This account will be update`,
|
||||||
handleOk: async () => {
|
handleOk: async () => {
|
||||||
|
setLoading(true);
|
||||||
const result = await updateAdmin(values);
|
const result = await updateAdmin(values);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
|
|
@ -72,7 +76,10 @@ export default function AdminModal({ data, onUpdated, ...props }: IAdminModelPro
|
||||||
} else {
|
} else {
|
||||||
const { confirmPassword, ...newValues } = values;
|
const { confirmPassword, ...newValues } = values;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
const result = await createAdmin(newValues as Omit<IAdmin, 'id' | 'created_at' | 'updated_at' | 'is_system_account'>);
|
const result = await createAdmin(newValues as Omit<IAdmin, 'id' | 'created_at' | 'updated_at' | 'is_system_account'>);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
|
|
@ -110,6 +117,7 @@ export default function AdminModal({ data, onUpdated, ...props }: IAdminModelPro
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
className="relative"
|
||||||
classNames={{
|
classNames={{
|
||||||
header: '!flex !item-center !justify-center w-full',
|
header: '!flex !item-center !justify-center w-full',
|
||||||
}}
|
}}
|
||||||
|
|
@ -160,6 +168,8 @@ export default function AdminModal({ data, onUpdated, ...props }: IAdminModelPro
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<PermissionDrawer opened={opened} onClose={close} />
|
<PermissionDrawer opened={opened} onClose={close} />
|
||||||
|
|
||||||
|
<LoadingOverlay visible={loading} zIndex={1000} overlayProps={{ blur: 2 }} />
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { Button, Modal, ModalProps, PasswordInput } from '@mantine/core';
|
import { Button, LoadingOverlay, Modal, ModalProps, PasswordInput } from '@mantine/core';
|
||||||
import { useForm, zodResolver } from '@mantine/form';
|
import { useForm, zodResolver } from '@mantine/form';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { grantNewPasswordAdmin } from '../../apis/admin';
|
import { grantNewPasswordAdmin } from '../../apis/admin';
|
||||||
import { useConfirmStore } from '../../lib/zustand/use-confirm';
|
import { useConfirmStore } from '../../lib/zustand/use-confirm';
|
||||||
|
|
@ -26,6 +26,8 @@ export default function GrantNewPasswordModal({ data, onUpdated, ...props }: IAd
|
||||||
validate: zodResolver(schema),
|
validate: zodResolver(schema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const { setConfirm } = useConfirmStore();
|
const { setConfirm } = useConfirmStore();
|
||||||
|
|
||||||
const handleSubmit = async (values: typeof form.values) => {
|
const handleSubmit = async (values: typeof form.values) => {
|
||||||
|
|
@ -34,10 +36,12 @@ export default function GrantNewPasswordModal({ data, onUpdated, ...props }: IAd
|
||||||
title: 'Update ?',
|
title: 'Update ?',
|
||||||
message: `This account will be update`,
|
message: `This account will be update`,
|
||||||
handleOk: async () => {
|
handleOk: async () => {
|
||||||
|
setLoading(true);
|
||||||
const result = await grantNewPasswordAdmin({
|
const result = await grantNewPasswordAdmin({
|
||||||
id: data.id,
|
id: data.id,
|
||||||
password: values.password,
|
password: values.password,
|
||||||
});
|
});
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
|
|
@ -73,6 +77,7 @@ export default function GrantNewPasswordModal({ data, onUpdated, ...props }: IAd
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
className="relative"
|
||||||
classNames={{
|
classNames={{
|
||||||
header: '!flex !item-center !justify-center w-full',
|
header: '!flex !item-center !justify-center w-full',
|
||||||
}}
|
}}
|
||||||
|
|
@ -89,6 +94,8 @@ export default function GrantNewPasswordModal({ data, onUpdated, ...props }: IAd
|
||||||
{'Grant'}
|
{'Grant'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<LoadingOverlay visible={loading} zIndex={1000} overlayProps={{ blur: 2 }} />
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { Button, Modal, ModalProps, NumberInput, TextInput } from '@mantine/core';
|
import { Button, LoadingOverlay, Modal, ModalProps, NumberInput, TextInput } from '@mantine/core';
|
||||||
import { useForm, zodResolver } from '@mantine/form';
|
import { useForm, zodResolver } from '@mantine/form';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { createBid, updateBid } from '../../apis/bid';
|
import { createBid, updateBid } from '../../apis/bid';
|
||||||
import { useConfirmStore } from '../../lib/zustand/use-confirm';
|
import { useConfirmStore } from '../../lib/zustand/use-confirm';
|
||||||
|
|
@ -28,13 +28,17 @@ export default function BidModal({ data, onUpdated, ...props }: IBidModelProps)
|
||||||
|
|
||||||
const { setConfirm } = useConfirmStore();
|
const { setConfirm } = useConfirmStore();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = async (values: typeof form.values) => {
|
const handleSubmit = async (values: typeof form.values) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
setConfirm({
|
setConfirm({
|
||||||
title: 'Update ?',
|
title: 'Update ?',
|
||||||
message: `This product will be update`,
|
message: `This product will be update`,
|
||||||
handleOk: async () => {
|
handleOk: async () => {
|
||||||
|
setLoading(true);
|
||||||
const result = await updateBid(values);
|
const result = await updateBid(values);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
|
|
@ -52,8 +56,11 @@ export default function BidModal({ data, onUpdated, ...props }: IBidModelProps)
|
||||||
} else {
|
} else {
|
||||||
const { url, max_price, plus_price } = values;
|
const { url, max_price, plus_price } = values;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
const result = await createBid({ url, max_price, plus_price } as IBid);
|
const result = await createBid({ url, max_price, plus_price } as IBid);
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
props.onClose();
|
props.onClose();
|
||||||
|
|
@ -83,6 +90,7 @@ export default function BidModal({ data, onUpdated, ...props }: IBidModelProps)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
className="relative"
|
||||||
classNames={{
|
classNames={{
|
||||||
header: '!flex !item-center !justify-center w-full',
|
header: '!flex !item-center !justify-center w-full',
|
||||||
}}
|
}}
|
||||||
|
|
@ -102,6 +110,8 @@ export default function BidModal({ data, onUpdated, ...props }: IBidModelProps)
|
||||||
{data ? 'Update' : 'Create'}
|
{data ? 'Update' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<LoadingOverlay visible={loading} zIndex={1000} overlayProps={{ blur: 2 }} />
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { Modal, ModalProps, Table } from '@mantine/core';
|
import { LoadingOverlay, Modal, ModalProps, Table } from '@mantine/core';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { getDetailBidHistories } from '../../apis/bid-histories';
|
import { getDetailBidHistories } from '../../apis/bid-histories';
|
||||||
import { extractNumber } from '../../lib/table/ultils';
|
import { extractNumber } from '../../lib/table/ultils';
|
||||||
|
|
@ -14,6 +14,8 @@ export interface IShowHistoriesBidGraysApiModalProps extends ModalProps {
|
||||||
export default function ShowHistoriesBidGraysApiModal({ data, onUpdated, ...props }: IShowHistoriesBidGraysApiModalProps) {
|
export default function ShowHistoriesBidGraysApiModal({ data, onUpdated, ...props }: IShowHistoriesBidGraysApiModalProps) {
|
||||||
const [histories, setHistories] = useState<Record<string, string>[]>([]);
|
const [histories, setHistories] = useState<Record<string, string>[]>([]);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const rows = useMemo(() => {
|
const rows = useMemo(() => {
|
||||||
return histories.map((element) => (
|
return histories.map((element) => (
|
||||||
<Table.Tr key={element.LotId}>
|
<Table.Tr key={element.LotId}>
|
||||||
|
|
@ -33,7 +35,9 @@ export default function ShowHistoriesBidGraysApiModal({ data, onUpdated, ...prop
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
const response = await getDetailBidHistories(data?.lot_id);
|
const response = await getDetailBidHistories(data?.lot_id);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (response.data && response.data) {
|
if (response.data && response.data) {
|
||||||
setHistories(response.data);
|
setHistories(response.data);
|
||||||
|
|
@ -45,7 +49,7 @@ export default function ShowHistoriesBidGraysApiModal({ data, onUpdated, ...prop
|
||||||
}, [handleCallApi]);
|
}, [handleCallApi]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal {...props} size="xl" title={<span className="text-xl font-bold">BIDDING HISTORY</span>} centered>
|
<Modal className="relative" {...props} size="xl" title={<span className="text-xl font-bold">BIDDING HISTORY</span>} centered>
|
||||||
<Table striped highlightOnHover withTableBorder withColumnBorders>
|
<Table striped highlightOnHover withTableBorder withColumnBorders>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
|
|
@ -68,6 +72,8 @@ export default function ShowHistoriesBidGraysApiModal({ data, onUpdated, ...prop
|
||||||
)}
|
)}
|
||||||
</Table.Tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
|
<LoadingOverlay visible={loading} zIndex={1000} overlayProps={{ blur: 2 }} />
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Avatar, Button, Menu, Modal, PasswordInput } from '@mantine/core';
|
import { Avatar, Button, LoadingOverlay, Menu, Modal, PasswordInput } from '@mantine/core';
|
||||||
import { useForm, zodResolver } from '@mantine/form';
|
import { useForm, zodResolver } from '@mantine/form';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { IconLogout, IconSettings, IconUser } from '@tabler/icons-react';
|
import { IconLogout, IconSettings, IconUser } from '@tabler/icons-react';
|
||||||
|
|
@ -7,6 +7,7 @@ import { z } from 'zod';
|
||||||
import { changePassword, logout } from '../apis/auth';
|
import { changePassword, logout } from '../apis/auth';
|
||||||
import { useConfirmStore } from '../lib/zustand/use-confirm';
|
import { useConfirmStore } from '../lib/zustand/use-confirm';
|
||||||
import Links from '../system/links';
|
import Links from '../system/links';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
const schema = z
|
const schema = z
|
||||||
.object({
|
.object({
|
||||||
|
|
@ -24,6 +25,8 @@ export default function UserMenu() {
|
||||||
|
|
||||||
const { setConfirm } = useConfirmStore();
|
const { setConfirm } = useConfirmStore();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
|
@ -59,11 +62,14 @@ export default function UserMenu() {
|
||||||
message: 'This account will change password !',
|
message: 'This account will change password !',
|
||||||
okButton: { value: 'Sure' },
|
okButton: { value: 'Sure' },
|
||||||
handleOk: async () => {
|
handleOk: async () => {
|
||||||
|
setLoading(true);
|
||||||
const data = await changePassword({
|
const data = await changePassword({
|
||||||
newPassword: values.newPassword,
|
newPassword: values.newPassword,
|
||||||
password: values.currentPassword,
|
password: values.currentPassword,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (data && data.data) {
|
if (data && data.data) {
|
||||||
navigate(Links.LOGIN);
|
navigate(Links.LOGIN);
|
||||||
close();
|
close();
|
||||||
|
|
@ -95,7 +101,7 @@ export default function UserMenu() {
|
||||||
</Menu.Dropdown>
|
</Menu.Dropdown>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
<Modal opened={opened} onClose={close} title="Change password" centered>
|
<Modal className="relative" opened={opened} onClose={close} title="Change password" centered>
|
||||||
<form onSubmit={form.onSubmit(handleSubmit)} className="flex flex-col gap-2.5">
|
<form onSubmit={form.onSubmit(handleSubmit)} className="flex flex-col gap-2.5">
|
||||||
<PasswordInput size="sm" label="Current password" {...form.getInputProps('currentPassword')} />
|
<PasswordInput size="sm" label="Current password" {...form.getInputProps('currentPassword')} />
|
||||||
<PasswordInput size="sm" label="New password" {...form.getInputProps('newPassword')} />
|
<PasswordInput size="sm" label="New password" {...form.getInputProps('newPassword')} />
|
||||||
|
|
@ -104,6 +110,8 @@ export default function UserMenu() {
|
||||||
Change
|
Change
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<LoadingOverlay visible={loading} zIndex={1000} overlayProps={{ blur: 2 }} />
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { Button, Modal, ModalProps, PasswordInput, TextInput } from '@mantine/core';
|
import { Button, LoadingOverlay, Modal, ModalProps, PasswordInput, TextInput } from '@mantine/core';
|
||||||
import { useForm, zodResolver } from '@mantine/form';
|
import { useForm, zodResolver } from '@mantine/form';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { updateWebBid } from '../../apis/web-bid';
|
import { updateWebBid } from '../../apis/web-bid';
|
||||||
import { useConfirmStore } from '../../lib/zustand/use-confirm';
|
import { useConfirmStore } from '../../lib/zustand/use-confirm';
|
||||||
|
|
@ -22,6 +22,8 @@ export default function WebAccountModal({ data, onUpdated, ...props }: IWebBidMo
|
||||||
validate: zodResolver(schema),
|
validate: zodResolver(schema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const prevData = useRef<IWebBid | null>(data);
|
const prevData = useRef<IWebBid | null>(data);
|
||||||
|
|
||||||
const { setConfirm } = useConfirmStore();
|
const { setConfirm } = useConfirmStore();
|
||||||
|
|
@ -32,7 +34,9 @@ export default function WebAccountModal({ data, onUpdated, ...props }: IWebBidMo
|
||||||
title: 'Update ?',
|
title: 'Update ?',
|
||||||
message: `This account will be update`,
|
message: `This account will be update`,
|
||||||
handleOk: async () => {
|
handleOk: async () => {
|
||||||
|
setLoading(true);
|
||||||
const result = await updateWebBid(values);
|
const result = await updateWebBid(values);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
|
|
@ -48,7 +52,9 @@ export default function WebAccountModal({ data, onUpdated, ...props }: IWebBidMo
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
setLoading(true);
|
||||||
const result = await updateWebBid(values);
|
const result = await updateWebBid(values);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
|
|
@ -79,6 +85,7 @@ export default function WebAccountModal({ data, onUpdated, ...props }: IWebBidMo
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
className="relative"
|
||||||
classNames={{
|
classNames={{
|
||||||
header: '!flex !item-center !justify-center w-full',
|
header: '!flex !item-center !justify-center w-full',
|
||||||
}}
|
}}
|
||||||
|
|
@ -95,6 +102,8 @@ export default function WebAccountModal({ data, onUpdated, ...props }: IWebBidMo
|
||||||
{data ? 'Update' : 'Create'}
|
{data ? 'Update' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<LoadingOverlay visible={loading} zIndex={1000} overlayProps={{ blur: 2 }} />
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { Button, Modal, ModalProps, TextInput } from '@mantine/core';
|
import { Button, LoadingOverlay, Modal, ModalProps, TextInput } from '@mantine/core';
|
||||||
import { useForm, zodResolver } from '@mantine/form';
|
import { useForm, zodResolver } from '@mantine/form';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { createWebBid, updateWebBid } from '../../apis/web-bid';
|
import { createWebBid, updateWebBid } from '../../apis/web-bid';
|
||||||
import { useConfirmStore } from '../../lib/zustand/use-confirm';
|
import { useConfirmStore } from '../../lib/zustand/use-confirm';
|
||||||
|
|
@ -22,6 +22,8 @@ export default function WebBidModal({ data, onUpdated, ...props }: IWebBidModelP
|
||||||
validate: zodResolver(z.object(schema)),
|
validate: zodResolver(z.object(schema)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const prevData = useRef<IWebBid | null>(data);
|
const prevData = useRef<IWebBid | null>(data);
|
||||||
|
|
||||||
const { setConfirm } = useConfirmStore();
|
const { setConfirm } = useConfirmStore();
|
||||||
|
|
@ -32,7 +34,9 @@ export default function WebBidModal({ data, onUpdated, ...props }: IWebBidModelP
|
||||||
title: 'Update ?',
|
title: 'Update ?',
|
||||||
message: `This web will be update`,
|
message: `This web will be update`,
|
||||||
handleOk: async () => {
|
handleOk: async () => {
|
||||||
|
setLoading(true);
|
||||||
const result = await updateWebBid(values);
|
const result = await updateWebBid(values);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
|
|
@ -50,7 +54,9 @@ export default function WebBidModal({ data, onUpdated, ...props }: IWebBidModelP
|
||||||
} else {
|
} else {
|
||||||
const { url, origin_url } = values;
|
const { url, origin_url } = values;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
const result = await createWebBid({ url, origin_url } as IWebBid);
|
const result = await createWebBid({ url, origin_url } as IWebBid);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
|
|
@ -88,6 +94,7 @@ export default function WebBidModal({ data, onUpdated, ...props }: IWebBidModelP
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
className="relative"
|
||||||
classNames={{
|
classNames={{
|
||||||
header: '!flex !item-center !justify-center w-full',
|
header: '!flex !item-center !justify-center w-full',
|
||||||
}}
|
}}
|
||||||
|
|
@ -104,6 +111,8 @@ export default function WebBidModal({ data, onUpdated, ...props }: IWebBidModelP
|
||||||
{data ? 'Update' : 'Create'}
|
{data ? 'Update' : 'Create'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<LoadingOverlay visible={loading} zIndex={1000} overlayProps={{ blur: 2 }} />
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export default function Login() {
|
||||||
<Box className="w-screen h-screen flex items-center justify-center">
|
<Box className="w-screen h-screen flex items-center justify-center">
|
||||||
<Paper w={400} radius="md" p="xl" withBorder>
|
<Paper w={400} radius="md" p="xl" withBorder>
|
||||||
<Text size="xl" className="text-center" fw={500}>
|
<Text size="xl" className="text-center" fw={500}>
|
||||||
Login to KTQ Admin
|
Login to Bid System
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Divider label="" labelPosition="center" my="lg" />
|
<Divider label="" labelPosition="center" my="lg" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
# DB_HOST=127.0.0.1
|
||||||
|
DB_USERNAME=root
|
||||||
|
DB_PASSWORD=123
|
||||||
|
|
||||||
|
ENVIRONMENT='dev'
|
||||||
|
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_NAME=auto-bids
|
||||||
|
|
||||||
|
PORT = 4000
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
APP_PATH = 'http://localhost:4000'
|
||||||
|
|
||||||
|
CORS = "http://localhost:5173, http://localhost:3000"
|
||||||
|
|
||||||
|
|
||||||
|
SECRET_KEY = "kgmwljwekqiq25232mdmsgnekwhlwekmglkwjqjwqw"
|
||||||
|
|
||||||
|
|
||||||
|
# DEV GROUP
|
||||||
|
TELEGRAM_BOT_TOKEN = "7963294152:AAE8b9AbsyLYzpeJbUMIcelVWlCBN5mLJ2o"
|
||||||
|
CHAT_ID = "-1002593407119"
|
||||||
|
|
||||||
|
# Bid histories GROUP
|
||||||
|
# TELEGRAM_BOT_TOKEN = "7533631751:AAEfE7Ei015U1sSsYPSAYwbYXWFl5D7y_18"
|
||||||
|
# CHAT_ID = "-1002535794248"
|
||||||
|
|
||||||
|
|
@ -2,6 +2,11 @@ import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
export class CreateAdminTable1742778498009 implements MigrationInterface {
|
export class CreateAdminTable1742778498009 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// {
|
||||||
|
// 'username': 'admin',
|
||||||
|
// 'password': 'Admin@123'
|
||||||
|
// }
|
||||||
|
|
||||||
await queryRunner.query(`
|
await queryRunner.query(`
|
||||||
INSERT INTO admins (email, username, password, is_system_account) VALUES
|
INSERT INTO admins (email, username, password, is_system_account) VALUES
|
||||||
('admin@gmail.com', 'admin', '$2b$10$eF7K4Msw32e5ZC2cU78KgOqxMJygQcPDt5xXZP29inBBIV9KEsoyO', 1);
|
('admin@gmail.com', 'admin', '$2b$10$eF7K4Msw32e5ZC2cU78KgOqxMJygQcPDt5xXZP29inBBIV9KEsoyO', 1);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ export default class Admin extends Timestamp {
|
||||||
@Column({ type: 'boolean', default: false })
|
@Column({ type: 'boolean', default: false })
|
||||||
is_system_account: boolean;
|
is_system_account: boolean;
|
||||||
|
|
||||||
@ManyToMany(() => Permission, (permission) => permission.admins)
|
@ManyToMany(() => Permission, (permission) => permission.admins, {
|
||||||
|
cascade: true,
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
permissions: Permission[];
|
permissions: Permission[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
ENVIRONMENT = 'prod'
|
||||||
|
SOCKET_URL = 'http://localhost:4000'
|
||||||
|
BASE_URL = 'http://localhost:4000/api/v1/'
|
||||||
|
|
@ -40,9 +40,6 @@ lerna-debug.log*
|
||||||
|
|
||||||
# dotenv environment variable files
|
# dotenv environment variable files
|
||||||
.env
|
.env
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
.env.local
|
||||||
|
|
||||||
# temp directory
|
# temp directory
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,11 @@ import { createApiBid, createBidProduct, deleteProfile, shouldUpdateProductTab }
|
||||||
import browser from './system/browser.js';
|
import browser from './system/browser.js';
|
||||||
import configs from './system/config.js';
|
import configs from './system/config.js';
|
||||||
import { delay, isTimeReached, safeClosePage } from './system/utils.js';
|
import { delay, isTimeReached, safeClosePage } from './system/utils.js';
|
||||||
|
import pLimit from 'p-limit';
|
||||||
|
|
||||||
let MANAGER_BIDS = [];
|
let MANAGER_BIDS = [];
|
||||||
|
|
||||||
let _INTERVAL_TRACKING_ID = null;
|
const activeTasks = new Set();
|
||||||
let _CLEAR_LAZY_TAB_ID = null;
|
|
||||||
let _WORK_TRACKING_ID = null;
|
|
||||||
|
|
||||||
global.IS_CLEANING = false;
|
|
||||||
|
|
||||||
const handleUpdateProductTabs = (data) => {
|
const handleUpdateProductTabs = (data) => {
|
||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
|
|
@ -48,142 +45,91 @@ const handleUpdateProductTabs = (data) => {
|
||||||
MANAGER_BIDS = newDataManager;
|
MANAGER_BIDS = newDataManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
// const tracking = async () => {
|
|
||||||
// if (_INTERVAL_TRACKING_ID) {
|
|
||||||
// clearInterval(_INTERVAL_TRACKING_ID);
|
|
||||||
// _INTERVAL_TRACKING_ID = null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _INTERVAL_TRACKING_ID = setInterval(async () => {
|
|
||||||
// const productTabs = _.flatMap(MANAGER_BIDS, 'children');
|
|
||||||
|
|
||||||
// for (const productTab of productTabs) {
|
|
||||||
// // Tìm parent context nếu chưa có
|
|
||||||
// if (!productTab.parent_browser_context) {
|
|
||||||
// const parent = _.find(MANAGER_BIDS, { id: productTab.web_bid.id });
|
|
||||||
// productTab.parent_browser_context = parent?.browser_context;
|
|
||||||
|
|
||||||
// if (!productTab.parent_browser_context) {
|
|
||||||
// console.log(`🔄 Waiting for parent process to start... (Product ID: ${productTab.id})`);
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Kết nối Puppeteer nếu chưa có page_context
|
|
||||||
// if (!productTab.page_context) {
|
|
||||||
// console.log(`🔌 Connecting to page for Product ID: ${productTab.id}`);
|
|
||||||
// await productTab.puppeteer_connect();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Nếu URL thay đổi, điều hướng đến URL mới
|
|
||||||
// if (productTab.page_context.url() !== productTab.url) {
|
|
||||||
// await productTab.gotoLink();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Kiểm tra nếu cần cập nhật trước khi gọi update()
|
|
||||||
// if (shouldUpdateProductTab(productTab)) {
|
|
||||||
// console.log(`🔄 Updating Product ID: ${productTab.id}...`);
|
|
||||||
// await productTab.update();
|
|
||||||
// } else {
|
|
||||||
// console.log(`⏳ Product ID: ${productTab.id} was updated recently. Skipping update.`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Nếu chưa có first_bid (trạng thái chưa đặt giá)
|
|
||||||
// if (!productTab.first_bid) {
|
|
||||||
// console.log(`🎯 Tracking out-bid event for Product ID: ${productTab.id}`);
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Nếu chưa đến giờ bid
|
|
||||||
// if (productTab.start_bid_time && !isTimeReached(productTab.start_bid_time)) {
|
|
||||||
// console.log(`⏳ Not yet time to bid. Skipping Product ID: ${productTab.id}`);
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// console.log(`🚀 Executing action for Product ID: ${productTab.id}`);
|
|
||||||
// await productTab.action();
|
|
||||||
// }
|
|
||||||
// }, configs.AUTO_TRACKING_DELAY);
|
|
||||||
// };
|
|
||||||
|
|
||||||
const tracking = async () => {
|
const tracking = async () => {
|
||||||
console.log('🚀 Tracking process started...');
|
console.log('🚀 Tracking process started...');
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
console.log('🔍 Scanning active bids...');
|
try {
|
||||||
const productTabs = _.flatMap(MANAGER_BIDS, 'children');
|
console.log('🔍 Scanning active bids...');
|
||||||
|
const productTabs = _.flatMap(MANAGER_BIDS, 'children');
|
||||||
|
|
||||||
for (const apiBid of MANAGER_BIDS) {
|
// Lắng nghe sự kiện của API bids (chạy song song)
|
||||||
if (apiBid.page_context) continue;
|
await Promise.allSettled(
|
||||||
|
MANAGER_BIDS.filter((bid) => !bid.page_context).map((apiBid) => {
|
||||||
|
console.log(`🎧 Listening to events for API Bid ID: ${apiBid.id}`);
|
||||||
|
return apiBid.listen_events();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
console.log(`🎧 Listening to events for API Bid ID: ${apiBid.id}`);
|
Promise.allSettled(
|
||||||
await apiBid.listen_events();
|
productTabs.map(async (productTab) => {
|
||||||
|
console.log(`📌 Processing Product ID: ${productTab.id}`);
|
||||||
|
|
||||||
|
// Xác định parent context
|
||||||
|
if (!productTab.parent_browser_context) {
|
||||||
|
const parent = _.find(MANAGER_BIDS, { id: productTab.web_bid.id });
|
||||||
|
productTab.parent_browser_context = parent?.browser_context;
|
||||||
|
if (!productTab.parent_browser_context) {
|
||||||
|
console.log(`⏳ Waiting for parent process... (Product ID: ${productTab.id})`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kết nối Puppeteer nếu chưa có page_context
|
||||||
|
if (!productTab.page_context) {
|
||||||
|
console.log(`🔌 Connecting to page for Product ID: ${productTab.id}`);
|
||||||
|
await productTab.puppeteer_connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểm tra URL và điều hướng nếu cần
|
||||||
|
if ((await productTab.page_context.url()) !== productTab.url) {
|
||||||
|
console.log(`🔄 Redirecting to new URL for Product ID: ${productTab.id}`);
|
||||||
|
await productTab.gotoLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cập nhật nếu cần thiết
|
||||||
|
if (shouldUpdateProductTab(productTab)) {
|
||||||
|
console.log(`🔄 Updating Product ID: ${productTab.id}...`);
|
||||||
|
await productTab.update();
|
||||||
|
} else {
|
||||||
|
console.log(`⏳ Product ID: ${productTab.id} was updated recently. Skipping update.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chờ first bid
|
||||||
|
if (!productTab.first_bid) {
|
||||||
|
console.log(`🎯 Waiting for first bid for Product ID: ${productTab.id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kiểm tra thời gian bid
|
||||||
|
if (productTab.start_bid_time && !isTimeReached(productTab.start_bid_time)) {
|
||||||
|
console.log(`⏳ Not yet time to bid. Skipping Product ID: ${productTab.id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thực thi hành động
|
||||||
|
console.log(`🚀 Executing action for Product ID: ${productTab.id}`);
|
||||||
|
await productTab.action();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dọn dẹp tab không dùng
|
||||||
|
console.log('🧹 Cleaning up unused tabs...');
|
||||||
|
clearLazyTab();
|
||||||
|
|
||||||
|
// Cập nhật trạng thái tracking
|
||||||
|
console.log('📊 Tracking work status...');
|
||||||
|
workTracking();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error in tracking loop:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const productTab of productTabs) {
|
|
||||||
console.log(`📌 Processing Product ID: ${productTab.id}`);
|
|
||||||
|
|
||||||
// Tìm parent context nếu chưa có
|
|
||||||
if (!productTab.parent_browser_context) {
|
|
||||||
const parent = _.find(MANAGER_BIDS, { id: productTab.web_bid.id });
|
|
||||||
productTab.parent_browser_context = parent?.browser_context;
|
|
||||||
|
|
||||||
if (!productTab.parent_browser_context) {
|
|
||||||
console.log(`⏳ Waiting for parent process to start... (Product ID: ${productTab.id})`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kết nối Puppeteer nếu chưa có page_context
|
|
||||||
if (!productTab.page_context) {
|
|
||||||
console.log(`🔌 Connecting to page for Product ID: ${productTab.id}`);
|
|
||||||
await productTab.puppeteer_connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nếu URL thay đổi, điều hướng đến URL mới
|
|
||||||
if (productTab.page_context.url() !== productTab.url) {
|
|
||||||
console.log(`🔄 Redirecting to new URL for Product ID: ${productTab.id}`);
|
|
||||||
await productTab.gotoLink();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kiểm tra nếu cần cập nhật trước khi gọi update()
|
|
||||||
if (shouldUpdateProductTab(productTab)) {
|
|
||||||
console.log(`🔄 Updating Product ID: ${productTab.id}...`);
|
|
||||||
await productTab.update();
|
|
||||||
} else {
|
|
||||||
console.log(`⏳ Product ID: ${productTab.id} was updated recently. Skipping update.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nếu chưa có first_bid (trạng thái chưa đặt giá)
|
|
||||||
if (!productTab.first_bid) {
|
|
||||||
console.log(`🎯 Waiting for first bid for Product ID: ${productTab.id}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nếu chưa đến giờ bid
|
|
||||||
if (productTab.start_bid_time && !isTimeReached(productTab.start_bid_time)) {
|
|
||||||
console.log(`⏳ Not yet time to bid. Skipping Product ID: ${productTab.id}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`🚀 Executing action for Product ID: ${productTab.id}`);
|
|
||||||
await productTab.action();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🧹 Cleaning up unused tabs...');
|
|
||||||
await clearLazyTab();
|
|
||||||
|
|
||||||
console.log('📊 Tracking work status...');
|
|
||||||
workTracking();
|
|
||||||
|
|
||||||
console.log(`⏳ Waiting ${configs.AUTO_TRACKING_DELAY / 1000} seconds before the next iteration...`);
|
console.log(`⏳ Waiting ${configs.AUTO_TRACKING_DELAY / 1000} seconds before the next iteration...`);
|
||||||
await delay(configs.AUTO_TRACKING_DELAY);
|
await delay(configs.AUTO_TRACKING_DELAY);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearLazyTab = async () => {
|
const clearLazyTab = async () => {
|
||||||
if (!global.IS_CLEANING) return;
|
|
||||||
|
|
||||||
if (!browser) {
|
if (!browser) {
|
||||||
console.warn('⚠️ Browser is not available or disconnected.');
|
console.warn('⚠️ Browser is not available or disconnected.');
|
||||||
return;
|
return;
|
||||||
|
|
@ -212,7 +158,7 @@ const clearLazyTab = async () => {
|
||||||
await page.close();
|
await page.close();
|
||||||
console.log(`🛑 Closing unused tab: ${pageUrl}`);
|
console.log(`🛑 Closing unused tab: ${pageUrl}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`⚠️ Error closing tab ${pageUrl}:`, err.message);
|
console.warn(`⚠️ Error closing tab ${pageUrl}:, err.message`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -225,14 +171,27 @@ const clearLazyTab = async () => {
|
||||||
const workTracking = async () => {
|
const workTracking = async () => {
|
||||||
try {
|
try {
|
||||||
const activeData = _.flatMap(MANAGER_BIDS, (item) => [item, ...item.children]);
|
const activeData = _.flatMap(MANAGER_BIDS, (item) => [item, ...item.children]);
|
||||||
|
const limit = pLimit(5);
|
||||||
|
|
||||||
for (const item of activeData) {
|
await Promise.allSettled(
|
||||||
if (item.page_context && !item.page_context.isClosed()) {
|
activeData
|
||||||
item.handleTakeWorkSnapshot();
|
.filter((item) => item.page_context && !item.page_context.isClosed())
|
||||||
}
|
.filter((item) => !activeTasks.has(item.id))
|
||||||
}
|
.map((item) =>
|
||||||
|
limit(async () => {
|
||||||
|
activeTasks.add(item.id);
|
||||||
|
try {
|
||||||
|
await item.handleTakeWorkSnapshot();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[❌ ERROR] Snapshot failed for Product ID: ${item.id}`, error);
|
||||||
|
} finally {
|
||||||
|
activeTasks.delete(item.id);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Lỗi rồi:', error);
|
console.error(`[❌ ERROR] Work tracking failed: ${error.message}\n`, error.stack);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -253,8 +212,6 @@ const workTracking = async () => {
|
||||||
console.log('📢 Bids Data:', data);
|
console.log('📢 Bids Data:', data);
|
||||||
|
|
||||||
handleUpdateProductTabs(data);
|
handleUpdateProductTabs(data);
|
||||||
|
|
||||||
// await Promise.all(MANAGER_BIDS.map((apiBid) => apiBid.listen_events()));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('webUpdated', async (data) => {
|
socket.on('webUpdated', async (data) => {
|
||||||
|
|
@ -270,8 +227,6 @@ const workTracking = async () => {
|
||||||
if (tabs.length <= 0) return;
|
if (tabs.length <= 0) return;
|
||||||
|
|
||||||
await Promise.all(tabs.map((tab) => safeClosePage(tab)));
|
await Promise.all(tabs.map((tab) => safeClosePage(tab)));
|
||||||
|
|
||||||
await Promise.all(MANAGER_BIDS.map((apiBid) => apiBid.listen_events()));
|
|
||||||
} else {
|
} else {
|
||||||
console.log('⚠️ No profile found to delete.');
|
console.log('⚠️ No profile found to delete.');
|
||||||
}
|
}
|
||||||
|
|
@ -279,8 +234,4 @@ const workTracking = async () => {
|
||||||
|
|
||||||
// AUTO TRACKING
|
// AUTO TRACKING
|
||||||
tracking();
|
tracking();
|
||||||
|
|
||||||
// clearLazyTab();
|
|
||||||
|
|
||||||
// workTracking();
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,11 @@ export class Bid {
|
||||||
if (!this.page_context) return;
|
if (!this.page_context) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`✅ Page loaded. Taking snapshot for Product ID: ${this.id}`);
|
// await this.page_context.waitForSelector('#pageContainer', { timeout: 10000 });
|
||||||
|
console.log(`✅ Page fully loaded. Taking snapshot for Product ID: ${this.id}`);
|
||||||
takeSnapshot(this.page_context, this, 'working', CONSTANTS.TYPE_IMAGE.WORK);
|
takeSnapshot(this.page_context, this, 'working', CONSTANTS.TYPE_IMAGE.WORK);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`❌ Error taking snapshot for Product ID: ${this.id}:`, error.message);
|
console.error(`❌ Error taking snapshot for Product ID: ${this.id}:`, error.message);
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,14 +134,12 @@ export class GrayApiBid extends ApiBid {
|
||||||
// 🔍 Check if already logged in (login input should not be visible)
|
// 🔍 Check if already logged in (login input should not be visible)
|
||||||
if (!(await page.$('input[name="username"]')) || fs.existsSync(filePath)) {
|
if (!(await page.$('input[name="username"]')) || fs.existsSync(filePath)) {
|
||||||
console.log('✅ Already logged in, skipping login.');
|
console.log('✅ Already logged in, skipping login.');
|
||||||
global.IS_CLEANING = true;
|
|
||||||
|
|
||||||
this.retry_login = 0; // Reset retry count
|
this.retry_login = 0; // Reset retry count
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🔑 Starting login process...');
|
console.log('🔑 Starting login process...');
|
||||||
global.IS_CLEANING = false;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await page.type('input[name="username"]', this.username, { delay: 100 });
|
await page.type('input[name="username"]', this.username, { delay: 100 });
|
||||||
|
|
@ -156,7 +154,6 @@ export class GrayApiBid extends ApiBid {
|
||||||
if (!(await page.$('input[name="username"]'))) {
|
if (!(await page.$('input[name="username"]'))) {
|
||||||
console.log('✅ Login successful!');
|
console.log('✅ Login successful!');
|
||||||
this.retry_login = 0; // Reset retry count after success
|
this.retry_login = 0; // Reset retry count after success
|
||||||
global.IS_CLEANING = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,7 +208,7 @@ export class GrayApiBid extends ApiBid {
|
||||||
});
|
});
|
||||||
|
|
||||||
page.on('load', async () => {
|
page.on('load', async () => {
|
||||||
console.log('🔄 Trang đã reload, khởi động lại polling...');
|
console.log('🔄 The page has reloaded, restarting polling...');
|
||||||
|
|
||||||
// await takeSnapshot(this.page_context, this, 'working', CONSTANTS.TYPE_IMAGE.WORK);
|
// await takeSnapshot(this.page_context, this, 'working', CONSTANTS.TYPE_IMAGE.WORK);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,6 @@ export class GraysProductBid extends ProductBid {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.status) {
|
if (!response.status) {
|
||||||
// await this.handleReturnProductPage(page);
|
|
||||||
// await safeClosePage(this);
|
|
||||||
return { result: false, bid_price: 0 };
|
return { result: false, bid_price: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +93,6 @@ export class GraysProductBid extends ProductBid {
|
||||||
|
|
||||||
if (!close_time || new Date(close_time).getTime() <= new Date().getTime()) {
|
if (!close_time || new Date(close_time).getTime() <= new Date().getTime()) {
|
||||||
console.log(`Product is close ${close_time} ❌`);
|
console.log(`Product is close ${close_time} ❌`);
|
||||||
// await safeClosePage(this);
|
|
||||||
return { result: true, close_time };
|
return { result: true, close_time };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,10 +123,8 @@ export class GraysProductBid extends ProductBid {
|
||||||
await takeSnapshot(page, this, 'bid-success', CONSTANTS.TYPE_IMAGE.SUCCESS);
|
await takeSnapshot(page, this, 'bid-success', CONSTANTS.TYPE_IMAGE.SUCCESS);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log({ error: error.message });
|
|
||||||
console.log('❌ Timeout to loading');
|
console.log('❌ Timeout to loading');
|
||||||
await takeSnapshot(page, this, 'timeout to loading');
|
await takeSnapshot(page, this, 'timeout to loading');
|
||||||
// await safeClosePage(this);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -140,8 +135,6 @@ export class GraysProductBid extends ProductBid {
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleUpdateBid({ lot_id, close_time, name, current_price, reserve_price }) {
|
async handleUpdateBid({ lot_id, close_time, name, current_price, reserve_price }) {
|
||||||
// if (close_time && this.close_time == close_time) return;
|
|
||||||
|
|
||||||
const response = await updateBid(this.id, { lot_id, close_time, name, current_price, reserve_price: Number(reserve_price) || 0 });
|
const response = await updateBid(this.id, { lot_id, close_time, name, current_price, reserve_price: Number(reserve_price) || 0 });
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
|
|
@ -236,7 +229,6 @@ export class GraysProductBid extends ProductBid {
|
||||||
if (!resultPlaceBid) {
|
if (!resultPlaceBid) {
|
||||||
console.log('❌ Error occurred while placing the bid.');
|
console.log('❌ Error occurred while placing the bid.');
|
||||||
await takeSnapshot(page, this, 'place-bid-action');
|
await takeSnapshot(page, this, 'place-bid-action');
|
||||||
// await safeClosePage(this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,7 +237,6 @@ export class GraysProductBid extends ProductBid {
|
||||||
await this.handleReturnProductPage(page);
|
await this.handleReturnProductPage(page);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`🚨 Error navigating the page: ${error.message}`);
|
console.error(`🚨 Error navigating the page: ${error.message}`);
|
||||||
// safeClosePage(this);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"p-limit": "^6.2.0",
|
||||||
"puppeteer": "^24.4.0",
|
"puppeteer": "^24.4.0",
|
||||||
"puppeteer-extra": "^3.3.6",
|
"puppeteer-extra": "^3.3.6",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
|
|
@ -1294,6 +1295,21 @@
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-limit": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"yocto-queue": "^1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pac-proxy-agent": {
|
"node_modules/pac-proxy-agent": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
|
||||||
|
|
@ -1992,6 +2008,18 @@
|
||||||
"fd-slicer": "~1.1.0"
|
"fd-slicer": "~1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yocto-queue": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.24.2",
|
"version": "3.24.2",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"p-limit": "^6.2.0",
|
||||||
"puppeteer": "^24.4.0",
|
"puppeteer": "^24.4.0",
|
||||||
"puppeteer-extra": "^3.3.6",
|
"puppeteer-extra": "^3.3.6",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import configs from '../system/config.js';
|
||||||
import CONSTANTS from '../system/constants.js';
|
import CONSTANTS from '../system/constants.js';
|
||||||
import { sanitizeFileName } from '../system/utils.js';
|
import { sanitizeFileName } from '../system/utils.js';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
const ONE_MINUTE = 60 * 1000;
|
const ONE_MINUTE = 60 * 1000;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,35 @@ import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
||||||
|
|
||||||
puppeteer.use(StealthPlugin());
|
puppeteer.use(StealthPlugin());
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
headless: process.env.ENVIRONMENT === 'prod' ? true : false,
|
headless: process.env.ENVIRONMENT === 'prod' ? 'new' : false,
|
||||||
// userDataDir: CONSTANTS.PROFILE_PATH, // Thư mục lưu profile
|
// userDataDir: CONSTANTS.PROFILE_PATH, // Thư mục lưu profile
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
args: [
|
args: [
|
||||||
'--no-sandbox',
|
'--no-sandbox',
|
||||||
'--disable-setuid-sandbox',
|
'--disable-setuid-sandbox',
|
||||||
'--disable-backgrounding-occluded-windows',
|
'--disable-dev-shm-usage',
|
||||||
'--disable-renderer-backgrounding',
|
'--disable-gpu',
|
||||||
'--disable-ipc-flooding-protection',
|
'--disable-software-rasterizer',
|
||||||
'--disable-features=CalculateNativeWinOcclusion,AudioServiceOutOfProcess',
|
'--disable-background-networking',
|
||||||
'--disable-background-timer-throttling',
|
'--disable-sync',
|
||||||
|
'--mute-audio',
|
||||||
|
'--no-first-run',
|
||||||
|
'--no-default-browser-check',
|
||||||
'--ignore-certificate-errors',
|
'--ignore-certificate-errors',
|
||||||
'--start-maximized',
|
'--start-maximized',
|
||||||
|
'--disable-site-isolation-trials', // Tắt sandbox riêng cho từng site
|
||||||
|
'--memory-pressure-off', // Tắt cơ chế bảo vệ bộ nhớ
|
||||||
|
'--disk-cache-size=0', // Không dùng cache để giảm bộ nhớ
|
||||||
|
'--enable-low-end-device-mode', // Kích hoạt chế độ tiết kiệm RAM
|
||||||
|
'--disable-best-effort-tasks', // Tắt tác vụ không quan trọng
|
||||||
|
'--disable-accelerated-2d-canvas', // Không dùng GPU để vẽ canvas
|
||||||
|
'--disable-threaded-animation', // Giảm animation chạy trên nhiều thread
|
||||||
|
'--disable-threaded-scrolling', // Tắt cuộn trang đa luồng
|
||||||
|
'--disable-logging', // Tắt log debug
|
||||||
|
'--blink-settings=imagesEnabled=false', // Không tải hình ảnh,
|
||||||
|
'--disable-background-timer-throttling', // Tránh việc throttling các timer khi chạy nền.
|
||||||
|
'--disable-webrtc',
|
||||||
|
'--disable-ipc-flooding-protection', // Nếu có extension cần IPC, cái này giúp tối ưu.
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export const takeSnapshot = async (page, item, imageName, type = CONSTANTS.TYPE_
|
||||||
});
|
});
|
||||||
|
|
||||||
// Chụp ảnh màn hình và lưu vào filePath
|
// Chụp ảnh màn hình và lưu vào filePath
|
||||||
await page.screenshot({ path: filePath, fullPage: true });
|
await page.screenshot({ path: filePath });
|
||||||
|
|
||||||
console.log(`📸 Image saved at: ${filePath}`);
|
console.log(`📸 Image saved at: ${filePath}`);
|
||||||
|
|
||||||
|
|
@ -49,8 +49,6 @@ export const takeSnapshot = async (page, item, imageName, type = CONSTANTS.TYPE_
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Error when snapshot: ' + error.message);
|
console.log('Error when snapshot: ' + error.message);
|
||||||
} finally {
|
|
||||||
global.IS_CLEANING = true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue