renove mails

This commit is contained in:
Admin 2025-05-09 08:08:38 +07:00
parent 960f889e92
commit 05f961da62
11 changed files with 150 additions and 102 deletions

View File

@ -1 +1 @@
VITE_API_KEY = ''
VITE_API_KEY = '@yQveRWre@b(!_9HmL'

View File

@ -1,30 +1,3 @@
# React + TypeScript + Vite
DELETE https://zulip.ipsupply.com.au/apac-custom/api/truncate-notes?key={key}
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
}
```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
GET https://zulip.ipsupply.com.au/apac-custom/api/getAllNotes?key={key}

View File

@ -119,7 +119,9 @@ function createWindow() {
}
function createTray() {
tray = new Tray(path.join(process.env.VITE_PUBLIC, "assets", "icon16x16.png"));
tray = new Tray(
path.join(process.env.VITE_PUBLIC, "assets", "icon16x16.png")
);
const contextMenu = Menu.buildFromTemplate([
{
@ -228,5 +230,13 @@ ipcMain.handle("show-notification", async (_, { title, body }) => {
body,
silent: false,
});
notification.on("click", () => {
if (win && !isQuiting) {
win.show();
win.focus();
}
});
notification.show();
});

BIN
public/assets/icon-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

View File

@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Route, HashRouter as Router, Routes } from "react-router-dom";
import { MailsPage, MainPage } from "./pages";
import { MainPage } from "./pages";
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/mails" element={<MailsPage />} />
{/* <Route path="/mails" element={<MailsPage />} /> */}
</Routes>
</Router>
);

View File

@ -1,67 +1,100 @@
import { ActionIcon, Box, Paper, Textarea, Tooltip } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconEye, IconEyeOff, IconTrash } from '@tabler/icons-react';
import { useEffect, useMemo, useState } from 'react';
import { ActionIcon, Box, Paper, Textarea, Tooltip } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { IconEye, IconEyeOff, IconTrash } from "@tabler/icons-react";
import { useEffect, useMemo, useState } from "react";
import dayjs from 'dayjs'
import dayjs from "dayjs";
export interface IMessageProps {
data: IMessage;
onDelete: () => void
data: IMessage;
onDelete: () => void;
}
export default function Message({ data, onDelete }: IMessageProps) {
const [show, { toggle }] = useDisclosure(true);
const [show, { toggle }] = useDisclosure(true);
const [animation, setAnimation] = useState(false);
const [animation, setAnimation] = useState(false);
const messageContent = useMemo(() => {
return show ? data.message : data.message.replace(/./g, '•');
}, [data.message, show]);
const messageContent = useMemo(() => {
return show ? data.message : data.message.replace(/./g, "•");
}, [data.message, show]);
// Tắt hiệu ứng nhấp nháy sau 1 giây
useEffect(() => {
if (!animation) return;
// Tắt hiệu ứng nhấp nháy sau 1 giây
useEffect(() => {
if (!animation) return;
const timer = setTimeout(() => {
setAnimation(false);
}, 5000);
return () => clearTimeout(timer);
}, [animation]);
const timer = setTimeout(() => {
setAnimation(false);
}, 5000);
return () => clearTimeout(timer);
}, [animation]);
useEffect(() => {
const showAnimation = new Date().getTime() - new Date(data.time).getTime() <= 110000;
useEffect(() => {
const showAnimation =
new Date().getTime() - new Date(data.time).getTime() <= 110000;
setAnimation(showAnimation);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
setAnimation(showAnimation);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<Paper
key={data.message}
shadow="xs"
radius="md"
p="xs"
withBorder
className={`flex items-center relative gap-3 w-full transition-all duration-300 ${animation ? 'animate-pulse !bg-blue-100' : ''}`}
>
<div className="flex items-center gap-2 w-full">
<Textarea defaultValue={messageContent} value={messageContent} className="flex-1" variant="unstyled" readOnly />
<Box className="flex gap-1">
<Tooltip label="View">
<ActionIcon onClick={toggle} variant="light" size="sm">
{show ? <IconEyeOff size={16} /> : <IconEye size={16} />}
</ActionIcon>
</Tooltip>
<Tooltip label="Delete">
<ActionIcon onClick={onDelete} variant="light" size="sm" color="red">
<IconTrash size={16} />
</ActionIcon>
</Tooltip>
</Box>
return (
<Tooltip
position="top-start"
style={{
fontSize: "11px",
}}
label={`Sender: ${data.sender}`}
>
<Paper
key={data.message}
shadow="xs"
radius="md"
p="xs"
withBorder
className={`flex items-center relative gap-3 w-full transition-all duration-300 ${
animation ? "animate-pulse !bg-blue-100" : ""
}`}
>
<div className="flex items-center w-full flex-col">
<Box className="flex items-center justify-between w-full">
<div className="text-xs">
{data.recipient.length > 0 && <span>{data.recipient}</span>}
</div>
<div>
<span className="text-xs">
{dayjs(data.time).format("HH:mm D/M/YYYY")}
</span>
</div>
</Box>
<span className='absolute top-1 right-2 text-xs'>{dayjs(data.time).format('HH:ss:mm DD/MM/YYYY')}</span>
</Paper>
);
<Box className="w-full flex items-center">
<Textarea
defaultValue={messageContent}
value={messageContent}
className="flex-1"
variant="unstyled"
readOnly
/>
<Box className="flex gap-2">
<Tooltip label="View">
<ActionIcon onClick={toggle} variant="light" size="sm">
{show ? <IconEyeOff size={16} /> : <IconEye size={16} />}
</ActionIcon>
</Tooltip>
<Tooltip label="Delete">
<ActionIcon
onClick={onDelete}
variant="light"
size="sm"
color="red"
>
<IconTrash size={16} />
</ActionIcon>
</Tooltip>
</Box>
</Box>
</div>
</Paper>
</Tooltip>
);
}

View File

@ -1,11 +1,11 @@
import { Menu } from "@mantine/core";
import { IconCode, IconRefresh } from "@tabler/icons-react";
import { Menu, useMantineColorScheme } from "@mantine/core";
import { IconCode, IconMoon, IconRefresh, IconSun } from "@tabler/icons-react";
import { ReactNode } from "react";
export interface ISettingsProps {
children: ReactNode;
funtions?: {
refresh?: () => void
}
refresh?: () => void;
};
}
export default function Settings({ children, funtions }: ISettingsProps) {
@ -13,18 +13,34 @@ export default function Settings({ children, funtions }: ISettingsProps) {
window.ipcRenderer?.openDevTools();
};
const {toggleColorScheme, colorScheme}= useMantineColorScheme()
return (
<Menu shadow="md" width={200}>
<Menu.Target>{children}</Menu.Target>
<Menu.Dropdown className="text-xs">
<Menu.Label className="text-xs">Application</Menu.Label>
<Menu.Item onClick={openDevtools} leftSection={<IconCode size={12} />} className="text-xs">
<Menu.Item
onClick={openDevtools}
leftSection={<IconCode size={12} />}
className="text-xs"
>
Devtool
</Menu.Item>
<Menu.Item onClick={funtions?.refresh} leftSection={<IconRefresh size={12} />} className="text-xs">
<Menu.Item
onClick={funtions?.refresh}
leftSection={<IconRefresh size={12} />}
className="text-xs"
>
Refresh
</Menu.Item>
<Menu.Item
onClick={toggleColorScheme}
leftSection={colorScheme === 'dark'?<IconSun size={12} />: <IconMoon size={12}/>}
className="text-xs flex items-center capitalize"
>
{colorScheme === 'dark' ? 'Light' : 'Dark'}
</Menu.Item>
</Menu.Dropdown>
</Menu>
);

View File

@ -5,7 +5,7 @@ import '@mantine/core/styles.css';
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<MantineProvider >
<MantineProvider defaultColorScheme="dark">
<App />
</MantineProvider>
);

View File

@ -1,13 +1,15 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
ActionIcon,
Avatar,
Box,
LoadingOverlay,
ScrollAreaAutosize,
Tooltip,
useMantineColorScheme
} from "@mantine/core";
import { useDisclosure, useViewportSize } from "@mantine/hooks"; // 🔥 thêm cái này
import { IconDotsVertical, IconMail } from "@tabler/icons-react";
import { IconDotsVertical } from "@tabler/icons-react";
import { useEffect, useRef, useState } from "react";
import { ConfirmModal, Message, Settings } from "../components";
import { showNotification } from "../ultils/fn";
@ -19,6 +21,9 @@ function MainPage() {
const [opened, { open, close }] = useDisclosure(false);
const { height } = useViewportSize(); // lấy kích thước cửa sổ
const [clickMessage, setClickMessage] = useState<IMessage | null>(null);
const {colorScheme} = useMantineColorScheme();
const scrollToBottom = () => {
const viewport = viewportRef.current;
if (viewport) {
@ -50,7 +55,7 @@ function MainPage() {
close();
showNotification("Thông báo", "Đã xóa note: " + clickMessage?.id);
setClickMessage(null)
setClickMessage(null);
} catch (error) {
console.log("%csrc/pages/mails.tsx:88 error", "color: #007acc;", error);
} finally {
@ -81,9 +86,9 @@ function MainPage() {
});
}, []);
const openMailsWindow = () => {
window.ipcRenderer.invoke("open-new-window");
};
// const openMailsWindow = () => {
// window.ipcRenderer.invoke("open-new-window");
// };
// 🧠 Tính chiều cao dynamic (ví dụ trừ header 80px + padding 30px)
const scrollAreaHeight = height - 70; // Bạn chỉnh số này nếu muốn
@ -91,19 +96,24 @@ function MainPage() {
return (
<Box className="flex flex-col !overflow-hidden h-full">
{/* Header */}
<header className="p-4 flex items-center justify-between sticky top-0 border-b border-gray-100 pb-2 bg-white z-10">
<Tooltip label="Mail">
<header className={`px-4 py-2 flex items-center justify-between sticky top-0 border-b z-10 ${colorScheme === 'dark' ? '': 'border-gray-100 pb-2 bg-white'}`}>
{/* <Tooltip label="Mail">
<ActionIcon
onClick={openMailsWindow}
onClick={() => {
showNotification('test', 'test', scrollToBottom)
}}
variant="light"
radius="md"
size="lg"
>
<IconMail size={18} />
</ActionIcon>
</Tooltip>
</Tooltip> */}
<div className="text-xl font-semibold">Zulip Notes</div>
<Avatar radius="sm" src={"./assets/icon-1.png"} />
{/*
<div className="text-xl font-semibold">Zulip Notes</div> */}
<Settings
funtions={{

View File

@ -1,6 +1,11 @@
export async function showNotification(title: string, body: string) {
export async function showNotification(
title: string,
body: string,
callback?: () => void
) {
await window.ipcRenderer.invoke("show-notification", {
title,
body,
});
callback?.();
}

1
src/vite-env.d.ts vendored
View File

@ -15,6 +15,7 @@ interface IMessage {
sender: string;
time: number;
message: string;
recipient:string
}
interface IEmail {