Skip to content

Commit cb598bc

Browse files
committed
feat: add new UI enhancement packages implementing loadings and error modals
1 parent acd7d04 commit cb598bc

9 files changed

+128
-15
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "MemGUI",
33
"description": "GUI for managing Memcached",
44
"private": false,
5-
"version": "0.1.0",
5+
"version": "0.1.2",
66
"author": {
77
"name": "Lucas Santos"
88
},

src/ui/App.jsx

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import LoadingModal from './components/LoadingModal';
2+
import ErrorModal from './components/ErrorModal';
13
import Main from './components/Main';
24
import TitleBar from './components/TitleBar';
35
import { ConnectionsProvider } from './context/ConnectionsContext';
@@ -11,6 +13,8 @@ const App = () => (
1113
<ConnectionsProvider>
1214
<div className="h-screen flex flex-col">
1315
<TitleBar />
16+
<LoadingModal/>
17+
<ErrorModal/>
1418
<Main />
1519
</div>
1620
</ConnectionsProvider>

src/ui/components/ConnectionForm.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ const ConnectionForm = ({ initialConnection, error, onSubmit }) => {
4242
<input
4343
type="text"
4444
name="name"
45-
placeholder="Ex: Produção"
45+
placeholder="Produção"
4646
value={formData.name}
4747
onChange={handleChange}
48-
className={`w-full p-3 rounded-lg focus:ring-2 transition-all ${darkMode
48+
className={`w-full mt-1 p-3 rounded-lg focus:ring-2 transition-all ${darkMode
4949
? 'bg-gray-700 border-gray-600 text-gray-100 focus:ring-blue-400'
5050
: 'bg-gray-100 border-gray-300 text-gray-900 focus:ring-blue-500'
5151
}`}
@@ -61,7 +61,7 @@ const ConnectionForm = ({ initialConnection, error, onSubmit }) => {
6161
<input
6262
type="text"
6363
name="host"
64-
placeholder="ex: 60.68.77.198"
64+
placeholder="60.68.77.198"
6565
value={formData.host}
6666
onChange={handleChange}
6767
className={`flex-1 p-3 rounded-lg focus:ring-2 transition-all ${darkMode

src/ui/components/ErrorModal.jsx

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { ExclamationCircleIcon } from "@heroicons/react/24/outline";
2+
import { useModal } from "../hooks/useModal";
3+
4+
const ErrorModal = () => {
5+
6+
const {dismissError, errorModalIsOpen, errorModalMessage} = useModal()
7+
8+
if (!errorModalIsOpen) return null;
9+
10+
return (
11+
<div className="fixed inset-0 flex items-center justify-center bg-black/30 backdrop-blur-lg z-100">
12+
<div className="bg-[#1E293B]/90 text-white p-6 rounded-lg shadow-xl w-96 border border-gray-700 animate-fadeIn">
13+
<div className="flex items-center gap-3">
14+
<ExclamationCircleIcon className="w-8 h-8 text-red-500" />
15+
<h2 className="text-lg font-semibold">Erro</h2>
16+
</div>
17+
<p className="mt-2 text-sm text-gray-300">{errorModalMessage}</p>
18+
<div className="mt-4 flex justify-end">
19+
<button
20+
onClick={dismissError}
21+
className="bg-red-500 hover:bg-red-600 transition duration-200 text-white px-4 py-2 rounded-md shadow-md"
22+
>
23+
Fechar
24+
</button>
25+
</div>
26+
</div>
27+
</div>
28+
);
29+
};
30+
31+
export default ErrorModal;

src/ui/components/LoadingModal.jsx

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ArrowPathIcon } from "@heroicons/react/24/outline";
2+
import { useModal } from "../hooks/useModal";
3+
4+
const LoadingModal = () => {
5+
6+
const {loadingModalIsOpen} = useModal()
7+
8+
if (!loadingModalIsOpen) return null;
9+
10+
return (
11+
<div className="fixed inset-0 flex items-center justify-center bg-black/50 backdrop-blur-md z-99">
12+
<div className="bg-[#1E293B]/80 text-white p-8 rounded-lg shadow-xl w-40 border border-gray-700 animate-fadeIn flex flex-col items-center">
13+
<div className="relative flex justify-center">
14+
<ArrowPathIcon className="w-14 h-14 text-blue-400 animate-spin" />
15+
<div className="absolute w-14 h-14 bg-blue-400 opacity-20 rounded-full blur-xl"></div>
16+
</div>
17+
<p className="mt-5 text-sm text-gray-300 font-medium">Carregando...</p>
18+
</div>
19+
</div>
20+
);
21+
};
22+
23+
export default LoadingModal;

src/ui/components/Main.jsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,23 @@ const Main = () => {
1515
return (
1616
<div className={`flex-1 flex flex-col overflow-hidden transition-all ${darkMode ? 'bg-gray-900 text-gray-100' : 'bg-gray-50 text-gray-900'}`}>
1717

18-
{isConnected ? (
18+
{isConnected && (
1919
<ConnectedHeader connection={currentConnection} onDisconnect={handleDisconnect} onToggleMenu={() => setMenuOpen(true)} />
20-
) : (
20+
)}
21+
22+
{!isConnected && (
2123
<UnconnectedHeader onToggleMenu={() => setMenuOpen(true)} />
2224
)}
2325

2426

2527
<main className="flex-1 overflow-auto">
26-
{isConnected ? (
28+
{isConnected && (
2729
<div className="w-full max-w-7xl mx-auto">
2830
<KeyList />
2931
</div>
30-
) : (
32+
)}
33+
34+
{!isConnected && (
3135
<ConnectionForm initialConnection={currentConnection} error={error} onSubmit={handleConnect} />
3236
)}
3337
</main>

src/ui/context/ConnectionsContext.tsx

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
22
import api, { clearConnectionId, setConnectionId } from '../api';
33
import React from 'react';
4+
import { useModal } from '../hooks/useModal';
45

56
interface Connection {
67
name: string;
@@ -51,8 +52,12 @@ export const ConnectionsProvider = ({ children }: { children: ReactNode }) => {
5152
const [keys, setKeys] = useState<KeyData[]>([]);
5253
const [error, setError] = useState('');
5354

55+
const { showError, showLoading, dismissLoading } = useModal()
56+
57+
5458
const handleConnect = async ({ host, port, name }: Omit<Connection, 'id'>) => {
5559
try {
60+
showLoading();
5661
const response = await api.post('/connections', { host, port });
5762
const { connectionId } = response.data;
5863
setConnectionId(connectionId);
@@ -68,13 +73,16 @@ export const ConnectionsProvider = ({ children }: { children: ReactNode }) => {
6873

6974
setCurrentConnection(newConnection);
7075
await handleLoadKeys();
76+
dismissLoading()
7177
} catch (err) {
72-
setError('Falha na conexão. Verifique os dados e tente novamente.');
78+
dismissLoading()
79+
showError('Falha na conexão. Verifique os dados e tente novamente.');
7380
}
7481
};
7582

7683
const choseConnection = async ({ name, host, port }: Omit<Connection, 'id'>) => {
7784
try {
85+
showLoading();
7886
const connection = savedConnections.find((c) => c.host === host && c.port === port);
7987

8088
if (!connection) {
@@ -88,12 +96,14 @@ export const ConnectionsProvider = ({ children }: { children: ReactNode }) => {
8896
setIsConnected(true);
8997
setCurrentConnection(connection);
9098
await handleLoadKeys();
99+
dismissLoading()
91100
} catch (err) {
101+
dismissLoading()
92102
if (err.status === 404) {
93103
await handleConnect({ name, host, port });
94104
return;
95105
}
96-
setError('Erro ao escolher conexão.');
106+
showError('Erro ao escolher conexão.');
97107
}
98108
};
99109

@@ -106,11 +116,14 @@ export const ConnectionsProvider = ({ children }: { children: ReactNode }) => {
106116

107117
const handleLoadKeys = async () => {
108118
try {
119+
showLoading()
109120
const response = await api.get('/keys');
110121
const sortedKeys = [...response.data].sort((a, b) => a.key.localeCompare(b.key));
111122
setKeys(sortedKeys);
123+
dismissLoading()
112124
} catch (err) {
113-
setError('Erro ao carregar chaves.');
125+
dismissLoading()
126+
showError('Erro ao carregar chaves.');
114127
}
115128
};
116129

@@ -120,18 +133,19 @@ export const ConnectionsProvider = ({ children }: { children: ReactNode }) => {
120133
setKeys(newList);
121134
await api.post('/keys', { key: newKey.key, value: newKey.value, expires: newKey.timeUntilExpiration });
122135
} catch (error) {
123-
setError('Erro ao criar chave.');
136+
showError('Erro ao criar chave.');
124137
}
125138
};
126139

127140
const handleEditKey = async (updatedKey: KeyData) => {
128141
try {
142+
showLoading();
129143
setKeys((prevKeys) =>
130144
prevKeys.map((k) => (k.key === updatedKey.key ? { ...updatedKey, size: new Blob([updatedKey.value]).size, timeUntilExpiration: updatedKey.timeUntilExpiration ?? 0 } : k))
131145
);
132146
await api.post('/keys', { key: updatedKey.key, value: updatedKey.value, expires: updatedKey.timeUntilExpiration });
133147
} catch (error) {
134-
setError('Erro ao editar chave.');
148+
showError('Erro ao editar chave.');
135149
}
136150
};
137151

@@ -140,7 +154,7 @@ export const ConnectionsProvider = ({ children }: { children: ReactNode }) => {
140154
await api.delete(`/keys/${key}`);
141155
setKeys((prevKeys) => prevKeys.filter((k) => k.key !== key));
142156
} catch (error) {
143-
setError('Erro ao excluir chave.');
157+
showError('Erro ao excluir chave.');
144158
}
145159
};
146160

src/ui/context/ModalContext.tsx

+29-1
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,25 @@ interface ModalContextType {
77
closeEditModal: () => void;
88
openCreateModal: () => void;
99
closeCreateModal: () => void;
10+
showLoading: () => void;
11+
dismissLoading: () => void;
12+
showError: (error: string) => void;
13+
dismissError: () => void;
1014
createModalIsOpen: boolean;
1115
editModalIsOpen: boolean;
16+
errorModalIsOpen: boolean;
17+
errorModalMessage: string;
18+
loadingModalIsOpen: boolean;
1219
}
1320

1421
export const ModalContext = createContext<ModalContextType | undefined>(undefined);
1522

1623
export const ModalProvider = ({ children }: { children: ReactNode }) => {
1724
const [createModalIsOpen, setCreateModalIsOpen] = useState(false);
1825
const [editModalIsOpen, setEditModalIsOpen] = useState(false);
26+
const [errorModalIsOpen, setErrorModalIsOpen] = useState(false);
27+
const [errorModalMessage, setErrorModalMessage] = useState('');
28+
const [loadingModalIsOpen, setLoadingModalIsOpen] = useState(false);
1929

2030
const openEditModal = () => {
2131
setEditModalIsOpen(true)
@@ -33,8 +43,26 @@ export const ModalProvider = ({ children }: { children: ReactNode }) => {
3343
setCreateModalIsOpen(false);
3444
};
3545

46+
const showError = (error: string) => {
47+
setErrorModalIsOpen(true)
48+
setErrorModalMessage(error);
49+
}
50+
51+
const dismissError = () => {
52+
setErrorModalIsOpen(false)
53+
};
54+
55+
const showLoading = () => {
56+
setLoadingModalIsOpen(true)
57+
58+
}
59+
60+
const dismissLoading = () => {
61+
setLoadingModalIsOpen(false)
62+
};
63+
3664
return (
37-
<ModalContext.Provider value={{ openCreateModal, closeCreateModal, closeEditModal, openEditModal, createModalIsOpen, editModalIsOpen }}>
65+
<ModalContext.Provider value={{ openCreateModal, closeCreateModal, closeEditModal, openEditModal, createModalIsOpen, editModalIsOpen, showError, dismissError, errorModalIsOpen, errorModalMessage, dismissLoading, loadingModalIsOpen, showLoading }}>
3866
{children}
3967
</ModalContext.Provider>
4068
);

tailwind.config.js

+9
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@ module.exports = {
66
colors: {
77
// Adicione cores personalizadas se necessário
88
},
9+
animation: {
10+
fadeIn: 'fadeIn 0.3s ease-in-out',
11+
},
912
transitionProperty: {
1013
colors: 'background-color, border-color, color, fill, stroke',
1114
},
15+
keyframes: {
16+
fadeIn: {
17+
'0%': { opacity: 0, transform: 'scale(0.95)' },
18+
'100%': { opacity: 1, transform: 'scale(1)' },
19+
},
20+
},
1221
},
1322
},
1423
plugins: [],

0 commit comments

Comments
 (0)