uibee 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/globals.css +52 -0
- package/dist/src/components/index.d.ts +2 -1
- package/dist/src/components/index.js +2 -1
- package/dist/src/components/{loginPage.js → login/loginPage.js} +6 -2
- package/dist/src/components/toast/toaster.d.ts +3 -0
- package/dist/src/components/toast/toaster.js +75 -0
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.js +4 -4
- package/dist/src/scripts/buildCss.js +1 -1
- package/dist/src/scripts/rewriteAlias.d.ts +1 -0
- package/dist/src/scripts/rewriteAlias.js +31 -0
- package/dist/src/utils/discord/discordAlert.d.ts +2 -0
- package/dist/src/utils/discord/discordAlert.js +32 -0
- package/dist/src/utils/index.d.ts +2 -0
- package/dist/src/utils/index.js +2 -0
- package/dist/src/utils/sql/alertSlowQuery.d.ts +2 -0
- package/dist/src/utils/sql/alertSlowQuery.js +25 -0
- package/eslint.config.js +46 -0
- package/images/logo-tekst-white.svg +171 -0
- package/package.json +6 -9
- package/src/components/index.ts +2 -0
- package/src/components/login/loginPage.tsx +81 -0
- package/src/components/toast/toaster.tsx +103 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useDarkMode.ts +23 -0
- package/src/hooks/useVisibility.ts +32 -0
- package/src/index.ts +4 -0
- package/src/scripts/buildCss.ts +25 -0
- package/src/scripts/index.ts +1 -0
- package/src/scripts/rewriteAlias.ts +37 -0
- package/src/types/components.d.ts +29 -0
- package/src/types/hooks.d.ts +9 -0
- package/src/types/svg.ts +4 -0
- package/src/types/utils.d.ts +34 -0
- package/src/utils/discord/discordAlert.ts +44 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/sql/alertSlowQuery.ts +37 -0
- package/tailwind.config.ts +9 -0
- package/tsconfig.json +24 -0
- /package/dist/src/components/{loginPage.d.ts → login/loginPage.d.ts} +0 -0
package/dist/globals.css
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"Courier New", monospace;
|
|
10
10
|
--spacing: 0.25rem;
|
|
11
11
|
--container-xs: 20rem;
|
|
12
|
+
--container-sm: 24rem;
|
|
12
13
|
--container-md: 28rem;
|
|
13
14
|
--text-sm: 0.875rem;
|
|
14
15
|
--text-sm--line-height: calc(1.25 / 0.875);
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
--font-weight-bold: 700;
|
|
21
22
|
--font-weight-extrabold: 800;
|
|
22
23
|
--tracking-tight: -0.025em;
|
|
24
|
+
--radius-lg: 0.5rem;
|
|
23
25
|
--radius-xl: 0.75rem;
|
|
24
26
|
--default-transition-duration: 150ms;
|
|
25
27
|
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
@@ -27,6 +29,8 @@
|
|
|
27
29
|
--default-mono-font-family: var(--font-mono);
|
|
28
30
|
--color-login: #fd8738;
|
|
29
31
|
--color-login-900: #121212;
|
|
32
|
+
--color-login-800: #181818;
|
|
33
|
+
--color-login-700: #1a1a1a;
|
|
30
34
|
--color-login-600: #212121;
|
|
31
35
|
--color-login-100: #b0b0b0;
|
|
32
36
|
--color-login-50: #ededed;
|
|
@@ -181,9 +185,24 @@
|
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
@layer utilities {
|
|
188
|
+
.absolute {
|
|
189
|
+
position: absolute;
|
|
190
|
+
}
|
|
191
|
+
.fixed {
|
|
192
|
+
position: fixed;
|
|
193
|
+
}
|
|
184
194
|
.relative {
|
|
185
195
|
position: relative;
|
|
186
196
|
}
|
|
197
|
+
.right-4 {
|
|
198
|
+
right: calc(var(--spacing) * 4);
|
|
199
|
+
}
|
|
200
|
+
.bottom-4 {
|
|
201
|
+
bottom: calc(var(--spacing) * 4);
|
|
202
|
+
}
|
|
203
|
+
.z-50 {
|
|
204
|
+
z-index: 50;
|
|
205
|
+
}
|
|
187
206
|
.mt-2 {
|
|
188
207
|
margin-top: calc(var(--spacing) * 2);
|
|
189
208
|
}
|
|
@@ -193,36 +212,57 @@
|
|
|
193
212
|
.flex {
|
|
194
213
|
display: flex;
|
|
195
214
|
}
|
|
215
|
+
.inline {
|
|
216
|
+
display: inline;
|
|
217
|
+
}
|
|
196
218
|
.aspect-\[3\/1\] {
|
|
197
219
|
aspect-ratio: 3/1;
|
|
198
220
|
}
|
|
199
221
|
.h-6 {
|
|
200
222
|
height: calc(var(--spacing) * 6);
|
|
201
223
|
}
|
|
224
|
+
.h-10 {
|
|
225
|
+
height: calc(var(--spacing) * 10);
|
|
226
|
+
}
|
|
202
227
|
.min-h-screen {
|
|
203
228
|
min-height: 100vh;
|
|
204
229
|
}
|
|
205
230
|
.w-6 {
|
|
206
231
|
width: calc(var(--spacing) * 6);
|
|
207
232
|
}
|
|
233
|
+
.w-10 {
|
|
234
|
+
width: calc(var(--spacing) * 10);
|
|
235
|
+
}
|
|
208
236
|
.w-full {
|
|
209
237
|
width: 100%;
|
|
210
238
|
}
|
|
239
|
+
.w-sm {
|
|
240
|
+
width: var(--container-sm);
|
|
241
|
+
}
|
|
211
242
|
.max-w-md {
|
|
212
243
|
max-width: var(--container-md);
|
|
213
244
|
}
|
|
214
245
|
.max-w-xs {
|
|
215
246
|
max-width: var(--container-xs);
|
|
216
247
|
}
|
|
248
|
+
.flex-shrink-0 {
|
|
249
|
+
flex-shrink: 0;
|
|
250
|
+
}
|
|
217
251
|
.transform {
|
|
218
252
|
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
|
219
253
|
}
|
|
220
254
|
.flex-col {
|
|
221
255
|
flex-direction: column;
|
|
222
256
|
}
|
|
257
|
+
.flex-col-reverse {
|
|
258
|
+
flex-direction: column-reverse;
|
|
259
|
+
}
|
|
223
260
|
.items-center {
|
|
224
261
|
align-items: center;
|
|
225
262
|
}
|
|
263
|
+
.items-end {
|
|
264
|
+
align-items: flex-end;
|
|
265
|
+
}
|
|
226
266
|
.justify-center {
|
|
227
267
|
justify-content: center;
|
|
228
268
|
}
|
|
@@ -238,6 +278,9 @@
|
|
|
238
278
|
.rounded {
|
|
239
279
|
border-radius: 0.25rem;
|
|
240
280
|
}
|
|
281
|
+
.rounded-lg {
|
|
282
|
+
border-radius: var(--radius-lg);
|
|
283
|
+
}
|
|
241
284
|
.rounded-xl {
|
|
242
285
|
border-radius: var(--radius-xl);
|
|
243
286
|
}
|
|
@@ -247,12 +290,21 @@
|
|
|
247
290
|
.bg-login-600 {
|
|
248
291
|
background-color: var(--color-login-600);
|
|
249
292
|
}
|
|
293
|
+
.bg-login-700 {
|
|
294
|
+
background-color: var(--color-login-700);
|
|
295
|
+
}
|
|
296
|
+
.bg-login-800 {
|
|
297
|
+
background-color: var(--color-login-800);
|
|
298
|
+
}
|
|
250
299
|
.bg-login-900 {
|
|
251
300
|
background-color: var(--color-login-900);
|
|
252
301
|
}
|
|
253
302
|
.object-contain {
|
|
254
303
|
object-fit: contain;
|
|
255
304
|
}
|
|
305
|
+
.p-2 {
|
|
306
|
+
padding: calc(var(--spacing) * 2);
|
|
307
|
+
}
|
|
256
308
|
.p-8 {
|
|
257
309
|
padding: calc(var(--spacing) * 8);
|
|
258
310
|
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { default as LoginPage } from './loginPage';
|
|
1
|
+
export { default as LoginPage } from './login/loginPage';
|
|
2
|
+
export { default as Toaster, addToast } from './toast/toaster';
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { default as LoginPage } from './loginPage';
|
|
1
|
+
export { default as LoginPage } from './login/loginPage';
|
|
2
|
+
export { default as Toaster, addToast } from './toast/toaster';
|
|
@@ -2,9 +2,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { LogIn } from 'lucide-react';
|
|
3
3
|
import Link from 'next/link';
|
|
4
4
|
import Image from 'next/image';
|
|
5
|
-
import logo from '
|
|
5
|
+
import logo from '../../../images/logo-tekst-white.svg';
|
|
6
6
|
export default function LoginPage({ title, description, redirectURI, version, btg, handleSubmit }) {
|
|
7
|
-
return (_jsx("main", { className: 'min-h-screen flex items-center justify-center bg-login-900 p-8', children: _jsxs("div", { className: 'flex flex-col justify-center items-center bg-login-600 px-4 py-12 rounded-xl w-full max-w-md gap-4 md:gap-6', children: [_jsx("div", { className: 'relative aspect-[3/1] w-full', children: _jsx(Image, { src: logo, alt: 'Logo', fill: true, className: 'object-contain sm:px-12' }) }), _jsxs("h1", { className: 'text-3xl font-extrabold text-login text-center tracking-tight', children: [title, " ", btg ? ' - Break the Glass' : ''] }), description && (_jsx("p", { className: 'text-login-100 text-center font-medium text-lg mb-2 max-w-xs', children: description })), btg ? (_jsxs("form", { className: 'w-full flex flex-col gap-3 max-w-xs', onSubmit: e => {
|
|
7
|
+
return (_jsx("main", { className: 'min-h-screen flex items-center justify-center bg-login-900 p-8', children: _jsxs("div", { className: 'flex flex-col justify-center items-center bg-login-600 px-4 py-12 rounded-xl w-full max-w-md gap-4 md:gap-6', children: [_jsx("div", { className: 'relative aspect-[3/1] w-full', children: _jsx(Image, { src: logo, alt: 'Logo', fill: true, className: 'object-contain sm:px-12' }) }), _jsxs("h1", { className: 'text-3xl font-extrabold text-login text-center tracking-tight', children: [title, " ", btg ? ' - Break the Glass' : ''] }), description && (_jsx("p", { className: 'text-login-100 text-center font-medium text-lg mb-2 max-w-xs', children: description })), btg ? (_jsxs("form", { className: 'w-full flex flex-col gap-3 max-w-xs', onSubmit: e => {
|
|
8
|
+
e.preventDefault();
|
|
9
|
+
handleSubmit?.(new FormData(e.currentTarget));
|
|
10
|
+
e.currentTarget.reset();
|
|
11
|
+
}, children: [_jsx("input", { type: 'text', name: 'name', placeholder: 'Name', className: 'py-2 px-3 rounded bg-login-900 text-login-50 font-medium focus:outline-none', required: true }), _jsx("input", { type: 'password', name: 'token', placeholder: 'Token', className: 'py-2 px-3 rounded bg-login-900 text-login-50 font-medium focus:outline-none', required: true }), _jsx("button", { type: 'submit', className: 'py-2 px-4 rounded-xl bg-login font-bold text-lg ' +
|
|
8
12
|
'hover:bg-login/80 text-login-50 transition-all duration-200 mt-2', children: "Login" })] })) : (_jsx(Link, { href: redirectURI, className: 'w-full flex justify-center', children: _jsxs("button", { className: 'flex items-center justify-center gap-2 w-full max-w-xs py-3 px-6 rounded-xl bg-login font-bold text-lg ' +
|
|
9
13
|
'hover:bg-login/80 text-login-50 transition-all duration-200 mb-2 mt-2', children: ["Login", _jsx(LogIn, { className: 'w-6 h-6' })] }) })), _jsxs("span", { className: 'text-login-100 text-sm mt-2', children: ["v", version] })] }) }));
|
|
10
14
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { CircleAlert, CircleCheck, CircleX, Info } from 'lucide-react';
|
|
4
|
+
import { useEffect, useState, useRef } from 'react';
|
|
5
|
+
const observers = [];
|
|
6
|
+
export function addToast(message, type) {
|
|
7
|
+
observers.forEach((observer) => observer({ message, type }));
|
|
8
|
+
}
|
|
9
|
+
export default function Toaster() {
|
|
10
|
+
const [toasts, setToasts] = useState([]);
|
|
11
|
+
const timers = useRef({});
|
|
12
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
13
|
+
const pauseTimes = useRef({});
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const listener = ({ message, type }) => {
|
|
16
|
+
const id = Date.now();
|
|
17
|
+
setToasts(prev => {
|
|
18
|
+
const newToasts = prev.concat({ id, message, type, remaining: 3000, created: Date.now() }).slice(-3);
|
|
19
|
+
return newToasts;
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
observers.push(listener);
|
|
23
|
+
return () => {
|
|
24
|
+
const idx = observers.indexOf(listener);
|
|
25
|
+
if (idx > -1)
|
|
26
|
+
observers.splice(idx, 1);
|
|
27
|
+
Object.values(timers.current).forEach(clearTimeout);
|
|
28
|
+
};
|
|
29
|
+
}, []);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (isHovered) {
|
|
32
|
+
toasts.forEach(toast => {
|
|
33
|
+
if (timers.current[toast.id]) {
|
|
34
|
+
clearTimeout(timers.current[toast.id]);
|
|
35
|
+
const elapsed = Date.now() - toast.created;
|
|
36
|
+
pauseTimes.current[toast.id] = toast.remaining - elapsed > 0 ? toast.remaining - elapsed : 0;
|
|
37
|
+
setToasts(prev => prev.map(t => t.id === toast.id ? { ...t, remaining: pauseTimes.current[toast.id] } : t));
|
|
38
|
+
delete timers.current[toast.id];
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
toasts.forEach(toast => {
|
|
44
|
+
if (!timers.current[toast.id] && toast.remaining > 0) {
|
|
45
|
+
timers.current[toast.id] = setTimeout(() => {
|
|
46
|
+
setToasts(prev => prev.filter(t => t.id !== toast.id));
|
|
47
|
+
delete timers.current[toast.id];
|
|
48
|
+
}, toast.remaining);
|
|
49
|
+
setToasts(prev => prev.map(t => t.id === toast.id ? { ...t, created: Date.now() } : t));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}, [isHovered, toasts]);
|
|
54
|
+
const bgClasses = ['bg-login-600', 'bg-login-700', 'bg-login-800'];
|
|
55
|
+
return (_jsx("div", { className: `fixed bottom-4 right-4 z-50 flex ${isHovered ? 'flex-col-reverse items-end gap-2' : 'flex-col items-end'}`, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: toasts.slice().reverse().map((toast, idx) => (_jsxs("div", { className: 'p-2 rounded-lg text-login-50 animate-fade-in-down transition-all w-sm flex items-center gap-2 ' +
|
|
56
|
+
(bgClasses[idx] || bgClasses[2]), style: isHovered ? {} : {
|
|
57
|
+
position: 'absolute',
|
|
58
|
+
right: 0,
|
|
59
|
+
zIndex: 100 - idx,
|
|
60
|
+
bottom: `${idx * 8}px`,
|
|
61
|
+
transform: `scale(${1 - idx * 0.05})`,
|
|
62
|
+
}, children: [_jsx("span", { className: 'flex-shrink-0 w-10 h-10 flex items-center justify-center', children: _jsx(ToastIcon, { type: toast.type }) }), _jsx("span", { children: toast.message })] }, toast.id))) }));
|
|
63
|
+
}
|
|
64
|
+
function ToastIcon({ type }) {
|
|
65
|
+
switch (type) {
|
|
66
|
+
case 'success':
|
|
67
|
+
return _jsx(CircleCheck, {});
|
|
68
|
+
case 'warning':
|
|
69
|
+
return _jsx(CircleAlert, {});
|
|
70
|
+
case 'error':
|
|
71
|
+
return _jsx(CircleX, {});
|
|
72
|
+
case 'info':
|
|
73
|
+
return _jsx(Info, {});
|
|
74
|
+
}
|
|
75
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from './hooks
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './scripts
|
|
1
|
+
export * from './hooks';
|
|
2
|
+
export * from './components';
|
|
3
|
+
export * from './utils';
|
|
4
|
+
export * from './scripts';
|
package/dist/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from './hooks
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './scripts
|
|
1
|
+
export * from './hooks';
|
|
2
|
+
export * from './components';
|
|
3
|
+
export * from './utils';
|
|
4
|
+
export * from './scripts';
|
|
@@ -14,7 +14,7 @@ export default async function buildCss() {
|
|
|
14
14
|
});
|
|
15
15
|
mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
16
16
|
writeFileSync(outputPath, result.css);
|
|
17
|
-
console.log('🐝
|
|
17
|
+
console.log('🐝 CSS generated successfully');
|
|
18
18
|
}
|
|
19
19
|
buildCss().catch(err => {
|
|
20
20
|
console.error(err);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { globSync } from 'glob';
|
|
4
|
+
const DIST_DIR = path.resolve('dist');
|
|
5
|
+
const IMAGES_DIR = path.join(DIST_DIR, 'images');
|
|
6
|
+
const jsFiles = globSync(`${DIST_DIR}/**/*.js`, { nodir: true });
|
|
7
|
+
jsFiles.forEach((file) => {
|
|
8
|
+
let content = fs.readFileSync(file, 'utf-8');
|
|
9
|
+
const fileDir = path.dirname(file);
|
|
10
|
+
content = content.replace(/from ['"]@images\/(.*?)['"]/g, (_, p1) => {
|
|
11
|
+
const targetPath = path.join(IMAGES_DIR, p1);
|
|
12
|
+
const relative = path.relative(fileDir, targetPath).replace(/\\/g, '/');
|
|
13
|
+
return `from '${relative}'`;
|
|
14
|
+
});
|
|
15
|
+
const inlineImages = content.match(/src: ['"]images\/(.*?)['"]/g);
|
|
16
|
+
if (inlineImages) {
|
|
17
|
+
inlineImages.forEach((match) => {
|
|
18
|
+
const imgPathMatch = match.match(/src: ['"]images\/(.*?)['"]/);
|
|
19
|
+
if (imgPathMatch) {
|
|
20
|
+
const imgRelativePath = imgPathMatch[1];
|
|
21
|
+
const resolvedPath = path.join(IMAGES_DIR, imgRelativePath);
|
|
22
|
+
console.error(`⚠️ Detected inline image in ${file}: ${match}`);
|
|
23
|
+
console.error(` Resolved file path: ${resolvedPath}`);
|
|
24
|
+
console.error(` This is not allowed. Use 'import image from '../../images/${imgRelativePath}'' instead.`);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
fs.writeFileSync(file, content, 'utf-8');
|
|
29
|
+
});
|
|
30
|
+
console.log('🐝 Image imports rewritten');
|
|
31
|
+
console.log('🐝 Build complete');
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export default async function discordAlert({ application, description, type = '', ping = false, criticalRole, webhookURL }) {
|
|
2
|
+
try {
|
|
3
|
+
const data = {
|
|
4
|
+
embeds: [
|
|
5
|
+
{
|
|
6
|
+
title: `🐝 ${application} ${`${type.toUpperCase()} `}🐝`,
|
|
7
|
+
description: description,
|
|
8
|
+
color: 0xff0000,
|
|
9
|
+
timestamp: new Date().toISOString()
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
};
|
|
13
|
+
if (ping) {
|
|
14
|
+
data.content = `🚨 <@&${criticalRole}> 🚨`;
|
|
15
|
+
}
|
|
16
|
+
const response = await fetch(webhookURL ?? '', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/json'
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify(data)
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(await response.text());
|
|
25
|
+
}
|
|
26
|
+
return response.status;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.log(error);
|
|
30
|
+
return 500;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export default async function alertSlowQuery({ application, duration, name, cacheTTL, webhookURL, criticalRole }) {
|
|
2
|
+
const lowerCaseName = name.toLowerCase();
|
|
3
|
+
const firstUpperCaseName = `${name.slice(0, 1).toUpperCase()}${name.slice(1).toLowerCase()}`;
|
|
4
|
+
if (duration > cacheTTL / 2 && webhookURL) {
|
|
5
|
+
const data = {
|
|
6
|
+
embeds: [
|
|
7
|
+
{
|
|
8
|
+
title: `🐝 ${application} ${firstUpperCaseName} Query Timing 🐝`,
|
|
9
|
+
description: `🐝 Slow ${lowerCaseName} query detected: ${duration.toFixed(2)}s`,
|
|
10
|
+
color: 0xff0000,
|
|
11
|
+
timestamp: new Date().toISOString()
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
};
|
|
15
|
+
if (duration > (cacheTTL - 1)) {
|
|
16
|
+
data.content = `🚨 <@&${criticalRole}> 🚨`;
|
|
17
|
+
}
|
|
18
|
+
console.warn(`${firstUpperCaseName} query exceeded half of cache TTL: ${duration.toFixed(2)}s`);
|
|
19
|
+
await fetch(webhookURL, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
body: JSON.stringify(data)
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import eslint from '@eslint/js'
|
|
2
|
+
import tseslint from 'typescript-eslint'
|
|
3
|
+
import typescriptParser from '@typescript-eslint/parser'
|
|
4
|
+
import stylistic from '@stylistic/eslint-plugin'
|
|
5
|
+
import pluginNext from '@next/eslint-plugin-next'
|
|
6
|
+
|
|
7
|
+
export default [
|
|
8
|
+
eslint.configs.recommended,
|
|
9
|
+
...tseslint.configs.recommended,
|
|
10
|
+
{
|
|
11
|
+
plugins: {
|
|
12
|
+
'@stylistic': stylistic,
|
|
13
|
+
'@next/next': pluginNext,
|
|
14
|
+
},
|
|
15
|
+
languageOptions: {
|
|
16
|
+
sourceType: 'module',
|
|
17
|
+
ecmaVersion: 2024,
|
|
18
|
+
parser: typescriptParser,
|
|
19
|
+
},
|
|
20
|
+
rules: {
|
|
21
|
+
...pluginNext.configs.recommended.rules,
|
|
22
|
+
strict: 'error',
|
|
23
|
+
'no-var': 'error',
|
|
24
|
+
'array-callback-return': 'error',
|
|
25
|
+
yoda: 'error',
|
|
26
|
+
'@stylistic/indent': ['error', 4],
|
|
27
|
+
'@stylistic/quotes': ['error', 'single'],
|
|
28
|
+
'@stylistic/semi': ['error', 'never'],
|
|
29
|
+
'@stylistic/jsx-quotes': ['error', 'prefer-single'],
|
|
30
|
+
'@stylistic/type-generic-spacing': ['error'],
|
|
31
|
+
'@stylistic/type-annotation-spacing': 'error',
|
|
32
|
+
'@stylistic/no-trailing-spaces': 'error',
|
|
33
|
+
'@typescript-eslint/no-unused-vars': 'error',
|
|
34
|
+
'@typescript-eslint/ban-ts-comment': 'off',
|
|
35
|
+
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
36
|
+
'@stylistic/max-len': [
|
|
37
|
+
'error',
|
|
38
|
+
{
|
|
39
|
+
code: 140,
|
|
40
|
+
ignoreComments: true,
|
|
41
|
+
ignoreUrls: true,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
]
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<svg
|
|
3
|
+
viewBox="0 0 147.02299 59.20511"
|
|
4
|
+
version="1.1"
|
|
5
|
+
id="svg1433"
|
|
6
|
+
sodipodi:docname="logo-tekst-white.svg"
|
|
7
|
+
width="147.02299"
|
|
8
|
+
height="59.205109"
|
|
9
|
+
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
|
|
10
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
11
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
+
xmlns:svg="http://www.w3.org/2000/svg"
|
|
14
|
+
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
15
|
+
xmlns:cc="http://creativecommons.org/ns#"
|
|
16
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
17
|
+
<sodipodi:namedview
|
|
18
|
+
id="namedview1435"
|
|
19
|
+
pagecolor="#ffffff"
|
|
20
|
+
bordercolor="#666666"
|
|
21
|
+
borderopacity="1.0"
|
|
22
|
+
inkscape:pageshadow="2"
|
|
23
|
+
inkscape:pageopacity="0.0"
|
|
24
|
+
inkscape:pagecheckerboard="0"
|
|
25
|
+
showgrid="false"
|
|
26
|
+
inkscape:zoom="5.1614308"
|
|
27
|
+
inkscape:cx="73.429252"
|
|
28
|
+
inkscape:cy="28.577347"
|
|
29
|
+
inkscape:window-width="1896"
|
|
30
|
+
inkscape:window-height="1028"
|
|
31
|
+
inkscape:window-x="12"
|
|
32
|
+
inkscape:window-y="40"
|
|
33
|
+
inkscape:window-maximized="1"
|
|
34
|
+
inkscape:current-layer="svg1433" />
|
|
35
|
+
<defs
|
|
36
|
+
id="defs1371">
|
|
37
|
+
<style
|
|
38
|
+
id="style1369">.a{fill:#fff;}.b{fill:none;stroke:#f0802a;stroke-miterlimit:10;stroke-width:3.5px;}</style>
|
|
39
|
+
</defs>
|
|
40
|
+
<title
|
|
41
|
+
id="title1373">logo_tekst</title>
|
|
42
|
+
<path
|
|
43
|
+
class="a"
|
|
44
|
+
d="m 28.77713,58.37738 v 0.76465 H 25.531 v -5.61816 h 0.91309 v 4.85351 z"
|
|
45
|
+
id="path1375" />
|
|
46
|
+
<path
|
|
47
|
+
class="a"
|
|
48
|
+
d="m 30.47831,59.142 h -0.917 v -5.61813 h 0.917 z"
|
|
49
|
+
id="path1377" />
|
|
50
|
+
<path
|
|
51
|
+
class="a"
|
|
52
|
+
d="M 36.46268,53.52387 V 59.142 H 35.99881 A 0.36347,0.36347 0 0 1 35.6785,58.98185 L 32.59256,55.018 c 0.0078,0.07617 0.01368,0.15039 0.01758,0.22461 0.0039,0.07422 0.0059,0.14258 0.0059,0.20508 V 59.142 h -0.80371 v -5.61813 h 0.47656 a 0.71321,0.71321 0 0 1 0.09864,0.0059 0.3128,0.3128 0 0 1 0.07617,0.02148 0.22919,0.22919 0 0 1 0.0664,0.04688 0.63781,0.63781 0 0 1 0.06641,0.07812 l 3.08984,3.96779 c -0.0078,-0.08105 -0.01367,-0.16015 -0.01757,-0.23828 -0.0039,-0.07813 -0.0059,-0.15039 -0.0059,-0.21875 v -3.6631 z"
|
|
53
|
+
id="path1379" />
|
|
54
|
+
<path
|
|
55
|
+
class="a"
|
|
56
|
+
d="m 39.84549,57.1684 a 2.91264,2.91264 0 0 1 -0.11523,0.84765 1.748,1.748 0 0 1 -0.34375,0.64063 1.52483,1.52483 0 0 1 -0.56543,0.40527 2.00691,2.00691 0 0 1 -0.78223,0.14258 3.23707,3.23707 0 0 1 -0.40234,-0.0254 3.44454,3.44454 0 0 1 -0.41309,-0.0801 l 0.04688,-0.5459 a 0.2079,0.2079 0 0 1 0.06055,-0.125 0.19754,0.19754 0 0 1 0.14257,-0.0469 0.74035,0.74035 0 0 1 0.18067,0.0293 1.36468,1.36468 0 0 0 0.71387,-0.041 0.74722,0.74722 0 0 0 0.3125,-0.2207 0.9864,0.9864 0 0 0 0.1914,-0.38574 2.19551,2.19551 0 0 0 0.06445,-0.57227 v -3.667 h 0.90918 z"
|
|
57
|
+
id="path1381" />
|
|
58
|
+
<path
|
|
59
|
+
class="a"
|
|
60
|
+
d="m 44.6326,58.40473 -0.0039,0.7373 h -3.50391 v -5.61816 h 3.50391 v 0.7373 h -2.58692 v 1.69336 h 2.06739 v 0.71387 h -2.06739 v 1.73633 z"
|
|
61
|
+
id="path1383" />
|
|
62
|
+
<path
|
|
63
|
+
class="a"
|
|
64
|
+
d="M 46.53592,54.26117 V 56.06 h 2.19629 v 0.7373 h -2.19629 v 2.3447 h -0.917 v -5.61813 h 3.50391 v 0.7373 z"
|
|
65
|
+
id="path1385" />
|
|
66
|
+
<path
|
|
67
|
+
class="a"
|
|
68
|
+
d="m 55.25956,56.33344 a 3.18179,3.18179 0 0 1 -0.20215,1.15039 2.6634,2.6634 0 0 1 -0.57227,0.90722 2.59685,2.59685 0 0 1 -0.88476,0.59473 3.17156,3.17156 0 0 1 -2.29492,0 2.62415,2.62415 0 0 1 -0.88672,-0.59473 2.65327,2.65327 0 0 1 -0.57422,-0.90722 3.37717,3.37717 0 0 1 0,-2.30176 2.67118,2.67118 0 0 1 0.57422,-0.90918 2.61491,2.61491 0 0 1 0.88672,-0.59668 3.1717,3.1717 0 0 1 2.29492,0 2.58781,2.58781 0 0 1 0.88476,0.59668 2.68144,2.68144 0 0 1 0.57227,0.90918 3.1871,3.1871 0 0 1 0.20215,1.15137 z m -0.93164,0 a 2.706,2.706 0 0 0 -0.13086,-0.87012 1.821,1.821 0 0 0 -0.375,-0.65527 1.63008,1.63008 0 0 0 -0.59082,-0.41407 2.16795,2.16795 0 0 0 -1.55664,0 1.65415,1.65415 0 0 0 -0.59278,0.41407 1.84212,1.84212 0 0 0 -0.3789,0.65527 2.92611,2.92611 0 0 0 0,1.74316 1.83766,1.83766 0 0 0 0.3789,0.6543 1.64222,1.64222 0 0 0 0.59278,0.40918 2.19564,2.19564 0 0 0 1.55664,0 1.61838,1.61838 0 0 0 0.59082,-0.40918 1.81652,1.81652 0 0 0 0.375,-0.6543 2.71589,2.71589 0 0 0 0.13086,-0.87304 z"
|
|
69
|
+
id="path1387" />
|
|
70
|
+
<path
|
|
71
|
+
class="a"
|
|
72
|
+
d="m 60.534,59.142 h -0.81543 a 0.377,0.377 0 0 1 -0.35156,-0.1875 l -1.31055,-1.8916 a 0.4267,0.4267 0 0 0 -0.14453,-0.14063 0.50286,0.50286 0 0 0 -0.23437,-0.043 H 57.17072 V 59.142 H 56.2576 v -5.61813 h 1.6543 a 3.54427,3.54427 0 0 1 0.9541,0.11328 1.80649,1.80649 0 0 1 0.65723,0.32226 1.25606,1.25606 0 0 1 0.37988,0.501 1.76427,1.76427 0 0 1 0.03516,1.19727 1.50418,1.50418 0 0 1 -0.25293,0.46093 1.61283,1.61283 0 0 1 -0.40821,0.3584 2.04861,2.04861 0 0 1 -0.5498,0.23828 0.92346,0.92346 0 0 1 0.28906,0.28516 z m -2.64551,-2.92578 a 1.763,1.763 0 0 0 0.5459,-0.07617 1.11007,1.11007 0 0 0 0.39063,-0.21289 0.87892,0.87892 0 0 0 0.23437,-0.3252 1.10093,1.10093 0 0 0 0.07715,-0.41992 0.84276,0.84276 0 0 0 -0.30371,-0.70215 1.46527,1.46527 0 0 0 -0.9209,-0.24219 h -0.74121 v 1.97852 z"
|
|
73
|
+
id="path1389" />
|
|
74
|
+
<path
|
|
75
|
+
class="a"
|
|
76
|
+
d="m 64.72049,58.40473 -0.0039,0.7373 h -3.50391 v -5.61816 h 3.50391 v 0.7373 h -2.58692 v 1.69336 h 2.06739 v 0.71387 h -2.06739 v 1.73633 z"
|
|
77
|
+
id="path1391" />
|
|
78
|
+
<path
|
|
79
|
+
class="a"
|
|
80
|
+
d="M 70.35721,53.52387 V 59.142 H 69.89335 A 0.36347,0.36347 0 0 1 69.57303,58.98185 L 66.4871,55.018 c 0.0078,0.07617 0.01367,0.15039 0.01757,0.22461 0.0039,0.07422 0.0059,0.14258 0.0059,0.20508 V 59.142 h -0.80371 v -5.61813 h 0.47656 a 0.71321,0.71321 0 0 1 0.09864,0.0059 0.3128,0.3128 0 0 1 0.07617,0.02148 0.22936,0.22936 0 0 1 0.06641,0.04688 0.63869,0.63869 0 0 1 0.0664,0.07812 l 3.08985,3.96777 c -0.008,-0.08105 -0.0137,-0.16015 -0.0176,-0.23828 -0.004,-0.07813 -0.006,-0.15039 -0.006,-0.21875 v -3.66308 z"
|
|
81
|
+
id="path1393" />
|
|
82
|
+
<path
|
|
83
|
+
class="a"
|
|
84
|
+
d="m 72.6121,59.142 h -0.917 v -5.61813 h 0.917 z"
|
|
85
|
+
id="path1395" />
|
|
86
|
+
<path
|
|
87
|
+
class="a"
|
|
88
|
+
d="M 78.59794,53.52387 V 59.142 H 78.13358 A 0.36344,0.36344 0 0 1 77.81327,58.98185 L 74.72733,55.018 c 0.008,0.07617 0.0137,0.15039 0.0176,0.22461 0.004,0.07422 0.006,0.14258 0.006,0.20508 V 59.142 h -0.80371 v -5.61813 h 0.47656 a 0.713,0.713 0 0 1 0.0986,0.0059 0.31254,0.31254 0 0 1 0.0762,0.02148 0.22936,0.22936 0 0 1 0.0664,0.04688 0.63869,0.63869 0 0 1 0.0664,0.07812 l 3.08984,3.96777 c -0.008,-0.08105 -0.0137,-0.16015 -0.0176,-0.23828 -0.004,-0.07813 -0.006,-0.15039 -0.006,-0.21875 v -3.66308 z"
|
|
89
|
+
id="path1397" />
|
|
90
|
+
<path
|
|
91
|
+
class="a"
|
|
92
|
+
d="M 84.53544,56.37641 V 58.6 a 3.24425,3.24425 0 0 1 -1.9502,0.60449 3.419,3.419 0 0 1 -1.23535,-0.21289 2.7275,2.7275 0 0 1 -0.94141,-0.59277 2.59231,2.59231 0 0 1 -0.60156,-0.90723 3.08634,3.08634 0 0 1 -0.21,-1.1582 3.23536,3.23536 0 0 1 0.20215,-1.165 2.5484,2.5484 0 0 1 1.49414,-1.498 3.29189,3.29189 0 0 1 1.20215,-0.209 3.52605,3.52605 0 0 1 0.62988,0.05273 3.11559,3.11559 0 0 1 0.54,0.14649 2.535,2.535 0 0 1 0.84668,0.52246 l -0.26074,0.418 a 0.26545,0.26545 0 0 1 -0.16016,0.12109 0.27377,0.27377 0 0 1 -0.21094,-0.04688 c -0.0752,-0.04492 -0.15527,-0.09082 -0.24023,-0.14062 a 2.01137,2.01137 0 0 0 -0.29,-0.13867 2.127,2.127 0 0 0 -0.37891,-0.10547 2.72656,2.72656 0 0 0 -0.50683,-0.041 2.09434,2.09434 0 0 0 -0.80176,0.14649 1.70173,1.70173 0 0 0 -0.61035,0.418 1.84692,1.84692 0 0 0 -0.39063,0.65722 2.5766,2.5766 0 0 0 -0.13672,0.86231 2.62333,2.62333 0 0 0 0.14453,0.89844 1.88731,1.88731 0 0 0 0.41016,0.67187 1.74772,1.74772 0 0 0 0.6416,0.419 2.55647,2.55647 0 0 0 1.459,0.0703 2.80344,2.80344 0 0 0 0.53222,-0.207 V 57.07074 H 82.928 a 0.19287,0.19287 0 0 1 -0.13964,-0.04883 0.16848,0.16848 0 0 1 -0.0508,-0.12695 v -0.51855 z"
|
|
93
|
+
id="path1399" />
|
|
94
|
+
<path
|
|
95
|
+
class="a"
|
|
96
|
+
d="m 89.1243,58.40473 -0.004,0.7373 h -3.50391 v -5.61816 h 3.50391 v 0.7373 h -2.58692 v 1.69336 h 2.06739 v 0.71387 h -2.06739 v 1.73633 z"
|
|
97
|
+
id="path1401" />
|
|
98
|
+
<path
|
|
99
|
+
class="a"
|
|
100
|
+
d="M 94.761,53.52387 V 59.142 H 94.29713 A 0.36347,0.36347 0 0 1 93.97682,58.98185 L 90.8909,55.018 c 0.008,0.07617 0.0137,0.15039 0.0176,0.22461 0.004,0.07422 0.006,0.14258 0.006,0.20508 V 59.142 h -0.80371 v -5.61813 h 0.47656 a 0.71321,0.71321 0 0 1 0.0986,0.0059 0.3128,0.3128 0 0 1 0.0762,0.02148 0.22919,0.22919 0 0 1 0.0664,0.04688 0.63781,0.63781 0 0 1 0.0664,0.07812 l 3.08984,3.96777 c -0.008,-0.08105 -0.0137,-0.16015 -0.0176,-0.23828 -0.004,-0.07813 -0.006,-0.15039 -0.006,-0.21875 v -3.66308 z"
|
|
101
|
+
id="path1403" />
|
|
102
|
+
<path
|
|
103
|
+
class="a"
|
|
104
|
+
d="M 98.95829,54.26117 V 56.06 h 2.19629 v 0.7373 h -2.19629 v 2.3447 h -0.917 v -5.61813 h 3.5039 v 0.7373 z"
|
|
105
|
+
id="path1405" />
|
|
106
|
+
<path
|
|
107
|
+
class="a"
|
|
108
|
+
d="m 107.68192,56.33344 a 3.18158,3.18158 0 0 1 -0.20215,1.15039 2.66353,2.66353 0 0 1 -0.57226,0.90722 2.597,2.597 0 0 1 -0.88477,0.59473 3.17156,3.17156 0 0 1 -2.29492,0 2.62415,2.62415 0 0 1 -0.88672,-0.59473 2.65327,2.65327 0 0 1 -0.57422,-0.90722 3.37717,3.37717 0 0 1 0,-2.30176 2.67118,2.67118 0 0 1 0.57422,-0.90918 2.61491,2.61491 0 0 1 0.88672,-0.59668 3.1717,3.1717 0 0 1 2.29492,0 2.588,2.588 0 0 1 0.88477,0.59668 2.68157,2.68157 0 0 1 0.57226,0.90918 3.18689,3.18689 0 0 1 0.20215,1.15137 z m -0.93164,0 a 2.706,2.706 0 0 0 -0.13086,-0.87012 1.82066,1.82066 0 0 0 -0.375,-0.65527 1.63,1.63 0 0 0 -0.59082,-0.41407 2.16795,2.16795 0 0 0 -1.55664,0 1.65411,1.65411 0 0 0 -0.59277,0.41407 1.84214,1.84214 0 0 0 -0.37891,0.65527 2.92632,2.92632 0 0 0 0,1.74316 1.83768,1.83768 0 0 0 0.37891,0.6543 1.64218,1.64218 0 0 0 0.59277,0.40918 2.19564,2.19564 0 0 0 1.55664,0 1.61829,1.61829 0 0 0 0.59082,-0.40918 1.81623,1.81623 0 0 0 0.375,-0.6543 2.71589,2.71589 0 0 0 0.13086,-0.87304 z"
|
|
109
|
+
id="path1407" />
|
|
110
|
+
<path
|
|
111
|
+
class="a"
|
|
112
|
+
d="m 112.95731,59.142 h -0.81543 a 0.377,0.377 0 0 1 -0.35156,-0.1875 l -1.31055,-1.8916 a 0.4267,0.4267 0 0 0 -0.14453,-0.14063 0.50286,0.50286 0 0 0 -0.23437,-0.043 H 109.594 V 59.142 h -0.91309 v -5.61813 h 1.6543 a 3.54427,3.54427 0 0 1 0.9541,0.11328 1.80649,1.80649 0 0 1 0.65723,0.32226 1.25606,1.25606 0 0 1 0.37988,0.501 1.76427,1.76427 0 0 1 0.0352,1.19727 1.50418,1.50418 0 0 1 -0.25293,0.46093 1.61283,1.61283 0 0 1 -0.40821,0.3584 2.04861,2.04861 0 0 1 -0.5498,0.23828 0.92346,0.92346 0 0 1 0.28906,0.28516 z m -2.64551,-2.92578 a 1.763,1.763 0 0 0 0.5459,-0.07617 1.11007,1.11007 0 0 0 0.39063,-0.21289 0.87892,0.87892 0 0 0 0.23437,-0.3252 1.10093,1.10093 0 0 0 0.0772,-0.41992 0.84276,0.84276 0 0 0 -0.30371,-0.70215 1.46527,1.46527 0 0 0 -0.9209,-0.24219 H 109.594 v 1.97852 z"
|
|
113
|
+
id="path1409" />
|
|
114
|
+
<path
|
|
115
|
+
class="a"
|
|
116
|
+
d="m 116.4954,59.142 h -0.917 v -5.61813 h 0.917 z"
|
|
117
|
+
id="path1411" />
|
|
118
|
+
<path
|
|
119
|
+
class="a"
|
|
120
|
+
d="m 121.69559,54.28461 h -1.75195 V 59.142 h -0.9082 v -4.85739 h -1.75586 v -0.76074 h 4.416 z"
|
|
121
|
+
id="path1413" />
|
|
122
|
+
<polyline
|
|
123
|
+
class="b"
|
|
124
|
+
points="47.871 98.1 33.189 98.1 33.189 83.418"
|
|
125
|
+
id="polyline1415"
|
|
126
|
+
transform="translate(-31.392,-41.894)" />
|
|
127
|
+
<polyline
|
|
128
|
+
class="b"
|
|
129
|
+
points="33.142 58.326 33.142 43.644 47.824 43.644"
|
|
130
|
+
id="polyline1417"
|
|
131
|
+
transform="translate(-31.392,-41.894)" />
|
|
132
|
+
<polyline
|
|
133
|
+
class="b"
|
|
134
|
+
points="161.983 98.122 176.665 98.122 176.665 83.44"
|
|
135
|
+
id="polyline1419"
|
|
136
|
+
transform="translate(-31.392,-41.894)" />
|
|
137
|
+
<polyline
|
|
138
|
+
class="b"
|
|
139
|
+
points="176.665 58.372 176.665 43.69 161.983 43.69"
|
|
140
|
+
id="polyline1421"
|
|
141
|
+
transform="translate(-31.392,-41.894)" />
|
|
142
|
+
<path
|
|
143
|
+
class="a"
|
|
144
|
+
d="m 30.02449,40.19351 v 4.12842 H 12.4991 V 13.99038 h 4.92871 v 26.20313 z"
|
|
145
|
+
id="path1423" />
|
|
146
|
+
<path
|
|
147
|
+
class="a"
|
|
148
|
+
d="m 61.53523,29.1564 a 17.15942,17.15942 0 0 1 -1.09473,6.21338 14.35971,14.35971 0 0 1 -3.08593,4.89746 14.091,14.091 0 0 1 -4.78125,3.21191 17.1289,17.1289 0 0 1 -12.38575,0 13.98317,13.98317 0 0 1 -7.88867,-8.10937 18.18161,18.18161 0 0 1 0,-12.42725 14.39119,14.39119 0 0 1 3.09668,-4.90771 14.13157,14.13157 0 0 1 4.792,-3.22315 17.13565,17.13565 0 0 1 12.38575,0 14.02046,14.02046 0 0 1 4.78125,3.22315 14.47032,14.47032 0 0 1 3.08593,4.90771 17.16209,17.16209 0 0 1 1.09472,6.21387 z m -5.03418,0 a 14.62587,14.62587 0 0 0 -0.70508,-4.69727 9.9446,9.9446 0 0 0 -2.02246,-3.53906 8.80545,8.80545 0 0 0 -3.1914,-2.23242 11.719,11.719 0 0 0 -8.4043,0 8.90077,8.90077 0 0 0 -3.20117,2.23242 9.96735,9.96735 0 0 0 -2.043,3.53906 15.81644,15.81644 0 0 0 0,9.415 9.847,9.847 0 0 0 2.043,3.52832 8.85094,8.85094 0 0 0 3.20117,2.21192 11.87213,11.87213 0 0 0 8.4043,0 8.75623,8.75623 0 0 0 3.1914,-2.21192 9.82454,9.82454 0 0 0 2.02249,-3.52828 14.69371,14.69371 0 0 0 0.70505,-4.71777 z"
|
|
149
|
+
id="path1425" />
|
|
150
|
+
<path
|
|
151
|
+
class="a"
|
|
152
|
+
d="m 91.76082,29.38784 v 12.00635 a 17.5354,17.5354 0 0 1 -10.53125,3.26465 18.41512,18.41512 0 0 1 -6.667,-1.148 14.80254,14.80254 0 0 1 -5.08691,-3.20166 14.0148,14.0148 0 0 1 -3.24316,-4.897 16.691,16.691 0 0 1 -1.1377,-6.25586 17.42935,17.42935 0 0 1 1.09473,-6.2876 13.74023,13.74023 0 0 1 8.06738,-8.08838 17.7222,17.7222 0 0 1 6.48828,-1.127 19.10354,19.10354 0 0 1 3.40137,0.28418 16.85244,16.85244 0 0 1 2.917,0.79 13.68442,13.68442 0 0 1 2.48633,1.22168 13.95372,13.95372 0 0 1 2.085,1.60058 l -1.41113,2.25391 a 1.40229,1.40229 0 0 1 -0.86426,0.65283 1.47784,1.47784 0 0 1 -1.13672,-0.25244 q -0.6123,-0.35816 -1.2959,-0.7583 a 11.33129,11.33129 0 0 0 -1.56933,-0.748 11.53387,11.53387 0 0 0 -2.043,-0.56836 14.78335,14.78335 0 0 0 -2.73828,-0.22119 11.32128,11.32128 0 0 0 -4.32813,0.78955 9.26752,9.26752 0 0 0 -3.29687,2.25391 9.99164,9.99164 0 0 0 -2.10645,3.54931 13.90267,13.90267 0 0 0 -0.7373,4.65528 14.11731,14.11731 0 0 0 0.77929,4.855 10.1425,10.1425 0 0 0 2.21192,3.62305 9.43419,9.43419 0 0 0 3.46484,2.26416 13.81975,13.81975 0 0 0 7.87793,0.3789 15.14816,15.14816 0 0 0 2.875,-1.11621 v -6.02383 h -4.2334 a 1.04883,1.04883 0 0 1 -0.75879,-0.26367 0.90553,0.90553 0 0 1 -0.27343,-0.68457 v -2.80127 z"
|
|
153
|
+
id="path1427" />
|
|
154
|
+
<path
|
|
155
|
+
class="a"
|
|
156
|
+
d="M 102.546,44.32193 H 97.59581 V 13.99038 h 4.95019 z"
|
|
157
|
+
id="path1429" />
|
|
158
|
+
<path
|
|
159
|
+
class="a"
|
|
160
|
+
d="m 134.8575,13.99038 v 30.33155 h -2.50684 a 2.14219,2.14219 0 0 1 -0.96875,-0.2002 2.26108,2.26108 0 0 1 -0.75879,-0.66357 L 113.962,22.05777 q 0.063,0.61083 0.0947,1.21093 0.0322,0.60058 0.0322,1.106 v 19.94723 h -4.3398 V 13.99038 h 2.57031 a 3.89092,3.89092 0 0 1 0.53711,0.03174 1.53328,1.53328 0 0 1 0.41016,0.11572 1.18964,1.18964 0 0 1 0.35839,0.25293 4.01792,4.01792 0 0 1 0.3584,0.4209 l 16.68164,21.42188 q -0.063,-0.65259 -0.0947,-1.28467 -0.0308,-0.63208 -0.0312,-1.17969 V 13.99038 Z"
|
|
161
|
+
id="path1431" />
|
|
162
|
+
<metadata
|
|
163
|
+
id="metadata1437">
|
|
164
|
+
<rdf:RDF>
|
|
165
|
+
<cc:Work
|
|
166
|
+
rdf:about="">
|
|
167
|
+
<dc:title>logo_tekst</dc:title>
|
|
168
|
+
</cc:Work>
|
|
169
|
+
</rdf:RDF>
|
|
170
|
+
</metadata>
|
|
171
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uibee",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Shared components, functions and hooks for reuse across Login projects",
|
|
5
5
|
"homepage": "https://github.com/Login-Linjeforening-for-IT/uibee#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -20,19 +20,16 @@
|
|
|
20
20
|
"./hooks": "./dist/src/hooks/index.js",
|
|
21
21
|
"./components": "./dist/src/components/index.js",
|
|
22
22
|
"./scripts": "./dist/src/scripts/index.js",
|
|
23
|
+
"./utils": "./dist/src/utils/index.js",
|
|
23
24
|
"./styles": "./dist/globals.css"
|
|
24
25
|
},
|
|
25
|
-
"files": [
|
|
26
|
-
"dist",
|
|
27
|
-
"src/globals.css"
|
|
28
|
-
],
|
|
29
26
|
"scripts": {
|
|
30
27
|
"build:clean": "rm -rf dist",
|
|
31
28
|
"build:css": "node --loader ts-node/esm src/scripts/buildCss.ts",
|
|
32
29
|
"build:assets": "cp -r images dist/images",
|
|
33
|
-
"build:
|
|
30
|
+
"build:rewrite": "node dist/src/scripts/rewriteAlias.js",
|
|
34
31
|
"build:ts": "tsc --declaration",
|
|
35
|
-
"build": "npm run build:clean && npm run build:css && npm run build:ts && npm run build:
|
|
32
|
+
"build": "npm run build:clean && npm run build:css && npm run build:ts && npm run build:assets && npm run build:rewrite",
|
|
36
33
|
"lint": "eslint --fix ./src"
|
|
37
34
|
},
|
|
38
35
|
"peerDependencies": {
|
|
@@ -47,14 +44,14 @@
|
|
|
47
44
|
"@types/node": "^24.5.2",
|
|
48
45
|
"@types/react": "^19.1.13",
|
|
49
46
|
"eslint": "^9.36.0",
|
|
47
|
+
"glob": "^11.0.3",
|
|
50
48
|
"postcss": "^8.5.6",
|
|
51
49
|
"tailwindcss": "^4.1.13",
|
|
52
50
|
"ts-node": "^10.9.2",
|
|
53
|
-
"tsc-alias": "^1.8.16",
|
|
54
51
|
"typescript": "^5.9.2",
|
|
55
52
|
"typescript-eslint": "^8.44.1"
|
|
56
53
|
},
|
|
57
54
|
"dependencies": {
|
|
58
55
|
"lucide-react": "^0.544.0"
|
|
59
56
|
}
|
|
60
|
-
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { LoginPageProps } from 'uibee/components'
|
|
2
|
+
import { LogIn } from 'lucide-react'
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import Image from 'next/image'
|
|
5
|
+
import logo from '@images/logo-tekst-white.svg'
|
|
6
|
+
|
|
7
|
+
export default function LoginPage({title, description, redirectURI, version, btg, handleSubmit}: LoginPageProps) {
|
|
8
|
+
return (
|
|
9
|
+
<main className='min-h-screen flex items-center justify-center bg-login-900 p-8'>
|
|
10
|
+
<div
|
|
11
|
+
className={
|
|
12
|
+
'flex flex-col justify-center items-center bg-login-600 px-4 py-12 rounded-xl w-full max-w-md gap-4 md:gap-6'
|
|
13
|
+
}
|
|
14
|
+
>
|
|
15
|
+
<div className='relative aspect-[3/1] w-full'>
|
|
16
|
+
<Image
|
|
17
|
+
src={logo}
|
|
18
|
+
alt='Logo'
|
|
19
|
+
fill
|
|
20
|
+
className='object-contain sm:px-12'
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
<h1 className='text-3xl font-extrabold text-login text-center tracking-tight'>
|
|
24
|
+
{title} {btg ? ' - Break the Glass' : ''}
|
|
25
|
+
</h1>
|
|
26
|
+
{description && (
|
|
27
|
+
<p className='text-login-100 text-center font-medium text-lg mb-2 max-w-xs'>
|
|
28
|
+
{description}
|
|
29
|
+
</p>
|
|
30
|
+
)}
|
|
31
|
+
{btg ? (
|
|
32
|
+
<form
|
|
33
|
+
className='w-full flex flex-col gap-3 max-w-xs'
|
|
34
|
+
onSubmit={e => {
|
|
35
|
+
e.preventDefault()
|
|
36
|
+
handleSubmit?.(new FormData(e.currentTarget))
|
|
37
|
+
e.currentTarget.reset()
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<input
|
|
41
|
+
type='text'
|
|
42
|
+
name='name'
|
|
43
|
+
placeholder='Name'
|
|
44
|
+
className='py-2 px-3 rounded bg-login-900 text-login-50 font-medium focus:outline-none'
|
|
45
|
+
required
|
|
46
|
+
/>
|
|
47
|
+
<input
|
|
48
|
+
type='password'
|
|
49
|
+
name='token'
|
|
50
|
+
placeholder='Token'
|
|
51
|
+
className='py-2 px-3 rounded bg-login-900 text-login-50 font-medium focus:outline-none'
|
|
52
|
+
required
|
|
53
|
+
/>
|
|
54
|
+
<button
|
|
55
|
+
type='submit'
|
|
56
|
+
className={
|
|
57
|
+
'py-2 px-4 rounded-xl bg-login font-bold text-lg ' +
|
|
58
|
+
'hover:bg-login/80 text-login-50 transition-all duration-200 mt-2'
|
|
59
|
+
}
|
|
60
|
+
>
|
|
61
|
+
Login
|
|
62
|
+
</button>
|
|
63
|
+
</form>
|
|
64
|
+
) : (
|
|
65
|
+
<Link href={redirectURI} className='w-full flex justify-center'>
|
|
66
|
+
<button
|
|
67
|
+
className={
|
|
68
|
+
'flex items-center justify-center gap-2 w-full max-w-xs py-3 px-6 rounded-xl bg-login font-bold text-lg ' +
|
|
69
|
+
'hover:bg-login/80 text-login-50 transition-all duration-200 mb-2 mt-2'
|
|
70
|
+
}
|
|
71
|
+
>
|
|
72
|
+
Login
|
|
73
|
+
<LogIn className='w-6 h-6' />
|
|
74
|
+
</button>
|
|
75
|
+
</Link>
|
|
76
|
+
)}
|
|
77
|
+
<span className='text-login-100 text-sm mt-2'>v{version}</span>
|
|
78
|
+
</div>
|
|
79
|
+
</main>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { CircleAlert, CircleCheck, CircleX, Info } from 'lucide-react'
|
|
4
|
+
import { useEffect, useState, useRef } from 'react'
|
|
5
|
+
import { ToastProps, ToastObserverProps } from 'uibee/components'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const observers: ToastObserverProps[] = []
|
|
9
|
+
|
|
10
|
+
export function addToast(message: string, type?: ToastProps['type']) {
|
|
11
|
+
observers.forEach((observer) => observer({ message, type }))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function Toaster() {
|
|
15
|
+
const [toasts, setToasts] = useState<Array<ToastProps & { id: number; remaining: number; created: number }>>([])
|
|
16
|
+
const timers = useRef<{ [id: number]: NodeJS.Timeout }>({})
|
|
17
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
18
|
+
const pauseTimes = useRef<{ [id: number]: number }>({})
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const listener: ToastObserverProps = ({ message, type }) => {
|
|
22
|
+
const id = Date.now()
|
|
23
|
+
setToasts(prev => {
|
|
24
|
+
const newToasts = prev.concat({ id, message, type, remaining: 3000, created: Date.now() }).slice(-3)
|
|
25
|
+
return newToasts
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
observers.push(listener)
|
|
29
|
+
return () => {
|
|
30
|
+
const idx = observers.indexOf(listener)
|
|
31
|
+
if (idx > -1) observers.splice(idx, 1)
|
|
32
|
+
Object.values(timers.current).forEach(clearTimeout)
|
|
33
|
+
}
|
|
34
|
+
}, [])
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (isHovered) {
|
|
38
|
+
toasts.forEach(toast => {
|
|
39
|
+
if (timers.current[toast.id]) {
|
|
40
|
+
clearTimeout(timers.current[toast.id])
|
|
41
|
+
const elapsed = Date.now() - toast.created
|
|
42
|
+
pauseTimes.current[toast.id] = toast.remaining - elapsed > 0 ? toast.remaining - elapsed : 0
|
|
43
|
+
setToasts(prev => prev.map(t => t.id === toast.id ? { ...t, remaining: pauseTimes.current[toast.id] } : t))
|
|
44
|
+
delete timers.current[toast.id]
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
} else {
|
|
48
|
+
toasts.forEach(toast => {
|
|
49
|
+
if (!timers.current[toast.id] && toast.remaining > 0) {
|
|
50
|
+
timers.current[toast.id] = setTimeout(() => {
|
|
51
|
+
setToasts(prev => prev.filter(t => t.id !== toast.id))
|
|
52
|
+
delete timers.current[toast.id]
|
|
53
|
+
}, toast.remaining)
|
|
54
|
+
setToasts(prev => prev.map(t => t.id === toast.id ? { ...t, created: Date.now() } : t))
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}, [isHovered, toasts])
|
|
59
|
+
|
|
60
|
+
const bgClasses = ['bg-login-600', 'bg-login-700', 'bg-login-800']
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
className={`fixed bottom-4 right-4 z-50 flex ${isHovered ? 'flex-col-reverse items-end gap-2' : 'flex-col items-end'}`}
|
|
64
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
65
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
66
|
+
>
|
|
67
|
+
{toasts.slice().reverse().map((toast, idx) => (
|
|
68
|
+
<div
|
|
69
|
+
key={toast.id}
|
|
70
|
+
className={
|
|
71
|
+
'p-2 rounded-lg text-login-50 animate-fade-in-down transition-all w-sm flex items-center gap-2 ' +
|
|
72
|
+
(bgClasses[idx] || bgClasses[2])
|
|
73
|
+
}
|
|
74
|
+
style={isHovered ? {} : {
|
|
75
|
+
position: 'absolute',
|
|
76
|
+
right: 0,
|
|
77
|
+
zIndex: 100 - idx,
|
|
78
|
+
bottom: `${idx * 8}px`,
|
|
79
|
+
transform: `scale(${1 - idx * 0.05})`,
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<span className='flex-shrink-0 w-10 h-10 flex items-center justify-center'>
|
|
83
|
+
<ToastIcon type={toast.type} />
|
|
84
|
+
</span>
|
|
85
|
+
<span>{toast.message}</span>
|
|
86
|
+
</div>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function ToastIcon({ type }: { type?: ToastProps['type'] }) {
|
|
93
|
+
switch (type) {
|
|
94
|
+
case 'success':
|
|
95
|
+
return <CircleCheck />
|
|
96
|
+
case 'warning':
|
|
97
|
+
return <CircleAlert />
|
|
98
|
+
case 'error':
|
|
99
|
+
return <CircleX />
|
|
100
|
+
case 'info':
|
|
101
|
+
return <Info />
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
export default function useDarkMode() {
|
|
4
|
+
const [isDark, setIsDark] = useState(false)
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const darkClassExists = document.documentElement.classList.contains('dark')
|
|
8
|
+
setIsDark(darkClassExists)
|
|
9
|
+
|
|
10
|
+
const observer = new MutationObserver(() => {
|
|
11
|
+
setIsDark(document.documentElement.classList.contains('dark'))
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
observer.observe(document.documentElement, {
|
|
15
|
+
attributes: true,
|
|
16
|
+
attributeFilter: ['class'],
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return () => observer.disconnect()
|
|
20
|
+
}, [])
|
|
21
|
+
|
|
22
|
+
return isDark
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
export default function useVisibility<T extends HTMLElement>(
|
|
4
|
+
onVisible: () => void,
|
|
5
|
+
rootMargin: string = '200px'
|
|
6
|
+
) {
|
|
7
|
+
const [isVisible, setIsVisible] = useState(false)
|
|
8
|
+
const ref = useRef<T | null>(null)
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (typeof window === 'undefined') return
|
|
12
|
+
|
|
13
|
+
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0
|
|
14
|
+
if (!isTouchDevice) return
|
|
15
|
+
|
|
16
|
+
const observer = new IntersectionObserver(
|
|
17
|
+
(entries) => {
|
|
18
|
+
if (entries[0].isIntersecting) {
|
|
19
|
+
setIsVisible(true)
|
|
20
|
+
onVisible()
|
|
21
|
+
observer.disconnect()
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{ rootMargin }
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if (ref.current) observer.observe(ref.current)
|
|
28
|
+
return () => observer.disconnect()
|
|
29
|
+
}, [onVisible, rootMargin])
|
|
30
|
+
|
|
31
|
+
return { ref, isVisible }
|
|
32
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import postcss from 'postcss'
|
|
4
|
+
|
|
5
|
+
export default async function buildCss() {
|
|
6
|
+
const tailwindModule = await import(new URL('../../tailwind.config.ts', import.meta.url).href)
|
|
7
|
+
const tailwindConfig = tailwindModule.default ?? tailwindModule
|
|
8
|
+
const tailwindPostcss = (await import('@tailwindcss/postcss')).default
|
|
9
|
+
const inputPath = path.resolve('./src/globals.css')
|
|
10
|
+
const outputPath = path.resolve('./dist/globals.css')
|
|
11
|
+
const inputCss = readFileSync(inputPath, 'utf-8')
|
|
12
|
+
const result = await postcss([tailwindPostcss(tailwindConfig)]).process(inputCss, {
|
|
13
|
+
from: inputPath,
|
|
14
|
+
to: outputPath,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
mkdirSync(path.dirname(outputPath), { recursive: true })
|
|
18
|
+
writeFileSync(outputPath, result.css)
|
|
19
|
+
console.log('🐝 CSS generated successfully')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
buildCss().catch(err => {
|
|
23
|
+
console.error(err)
|
|
24
|
+
process.exit(1)
|
|
25
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as buildCss } from './buildCss'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { globSync } from 'glob'
|
|
4
|
+
|
|
5
|
+
const DIST_DIR = path.resolve('dist')
|
|
6
|
+
const IMAGES_DIR = path.join(DIST_DIR, 'images')
|
|
7
|
+
|
|
8
|
+
const jsFiles = globSync(`${DIST_DIR}/**/*.js`, { nodir: true })
|
|
9
|
+
|
|
10
|
+
jsFiles.forEach((file) => {
|
|
11
|
+
let content = fs.readFileSync(file, 'utf-8')
|
|
12
|
+
const fileDir = path.dirname(file)
|
|
13
|
+
content = content.replace(/from ['"]@images\/(.*?)['"]/g, (_, p1) => {
|
|
14
|
+
const targetPath = path.join(IMAGES_DIR, p1)
|
|
15
|
+
const relative = path.relative(fileDir, targetPath).replace(/\\/g, '/')
|
|
16
|
+
return `from '${relative}'`
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const inlineImages = content.match(/src: ['"]images\/(.*?)['"]/g)
|
|
20
|
+
if (inlineImages) {
|
|
21
|
+
inlineImages.forEach((match) => {
|
|
22
|
+
const imgPathMatch = match.match(/src: ['"]images\/(.*?)['"]/)
|
|
23
|
+
if (imgPathMatch) {
|
|
24
|
+
const imgRelativePath = imgPathMatch[1]
|
|
25
|
+
const resolvedPath = path.join(IMAGES_DIR, imgRelativePath)
|
|
26
|
+
console.error(`⚠️ Detected inline image in ${file}: ${match}`)
|
|
27
|
+
console.error(` Resolved file path: ${resolvedPath}`)
|
|
28
|
+
console.error(` This is not allowed. Use 'import image from '@images/${imgRelativePath}'' instead.`)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fs.writeFileSync(file, content, 'utf-8')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
console.log('🐝 Image imports rewritten')
|
|
37
|
+
console.log('🐝 Build complete')
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* eslint-disable @stylistic/semi */
|
|
2
|
+
declare module 'uibee/components' {
|
|
3
|
+
export interface LoginPageProps {
|
|
4
|
+
title: string
|
|
5
|
+
description?: string
|
|
6
|
+
redirectURI: string
|
|
7
|
+
version: string
|
|
8
|
+
btg?: boolean
|
|
9
|
+
handleSubmit?: (formData: FormData) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ToastProps {
|
|
13
|
+
id: number
|
|
14
|
+
message: string
|
|
15
|
+
type?: 'success' | 'error' | 'info' | 'warning'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ToastEventProps {
|
|
19
|
+
message: string
|
|
20
|
+
type?: ToastProps['type']
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ToastObserverProps {
|
|
24
|
+
(event: ToastEventProps): void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function LoginPage(props: LoginPageProps): JSX.Element;
|
|
28
|
+
export default function Toaster(props: { toasts: ToastProps[] }): JSX.Element;
|
|
29
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* eslint-disable @stylistic/semi */
|
|
2
|
+
declare module 'uibee/hooks' {
|
|
3
|
+
export default function useVisibility<T extends HTMLElement>(
|
|
4
|
+
onVisible: () => void,
|
|
5
|
+
rootMargin?: string
|
|
6
|
+
): { ref: RefObject<T>; isVisible: boolean };
|
|
7
|
+
|
|
8
|
+
export default function useDarkMode(): boolean;
|
|
9
|
+
}
|
package/src/types/svg.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* eslint-disable @stylistic/semi */
|
|
2
|
+
declare module 'uibee/utils' {
|
|
3
|
+
export interface SlowQueryProps {
|
|
4
|
+
application: string
|
|
5
|
+
duration: number
|
|
6
|
+
name: string
|
|
7
|
+
cacheTTL: number
|
|
8
|
+
webhookURL: string
|
|
9
|
+
criticalRole: string
|
|
10
|
+
}
|
|
11
|
+
export interface Embed {
|
|
12
|
+
title: string
|
|
13
|
+
description: string
|
|
14
|
+
color: number
|
|
15
|
+
timestamp: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Data {
|
|
19
|
+
content?: string
|
|
20
|
+
embeds: Embed[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DiscordAlertProps {
|
|
24
|
+
application: string
|
|
25
|
+
description: string
|
|
26
|
+
type: 'get' | 'post' | ''
|
|
27
|
+
ping: boolean
|
|
28
|
+
criticalRole: string
|
|
29
|
+
webhookURL: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default async function alertSlowQuery(props: SlowQueryProps): Promise<void>;
|
|
33
|
+
export default async function discordAlert(props: DiscordAlertProps): Promise<number>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Data, DiscordAlertProps } from 'uibee/utils'
|
|
2
|
+
|
|
3
|
+
export default async function discordAlert({
|
|
4
|
+
application,
|
|
5
|
+
description,
|
|
6
|
+
type = '',
|
|
7
|
+
ping = false,
|
|
8
|
+
criticalRole,
|
|
9
|
+
webhookURL
|
|
10
|
+
}: DiscordAlertProps): Promise<number> {
|
|
11
|
+
try {
|
|
12
|
+
const data: Data = {
|
|
13
|
+
embeds: [
|
|
14
|
+
{
|
|
15
|
+
title: `🐝 ${application} ${`${type.toUpperCase()} `}🐝`,
|
|
16
|
+
description: description,
|
|
17
|
+
color: 0xff0000,
|
|
18
|
+
timestamp: new Date().toISOString()
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (ping) {
|
|
24
|
+
data.content = `🚨 <@&${criticalRole}> 🚨`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const response = await fetch(webhookURL ?? '', {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: {
|
|
30
|
+
'Content-Type': 'application/json'
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify(data)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(await response.text())
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return response.status
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.log(error)
|
|
42
|
+
return 500
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Data, SlowQueryProps } from 'uibee/utils'
|
|
2
|
+
|
|
3
|
+
export default async function alertSlowQuery({
|
|
4
|
+
application,
|
|
5
|
+
duration,
|
|
6
|
+
name,
|
|
7
|
+
cacheTTL,
|
|
8
|
+
webhookURL,
|
|
9
|
+
criticalRole
|
|
10
|
+
}: SlowQueryProps): Promise<void> {
|
|
11
|
+
const lowerCaseName = name.toLowerCase()
|
|
12
|
+
const firstUpperCaseName = `${name.slice(0, 1).toUpperCase()}${name.slice(1).toLowerCase()}`
|
|
13
|
+
if (duration > cacheTTL / 2 && webhookURL) {
|
|
14
|
+
const data: Data = {
|
|
15
|
+
embeds: [
|
|
16
|
+
{
|
|
17
|
+
title: `🐝 ${application} ${firstUpperCaseName} Query Timing 🐝`,
|
|
18
|
+
description: `🐝 Slow ${lowerCaseName} query detected: ${duration.toFixed(2)}s`,
|
|
19
|
+
color: 0xff0000,
|
|
20
|
+
timestamp: new Date().toISOString()
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (duration > (cacheTTL - 1)) {
|
|
26
|
+
data.content = `🚨 <@&${criticalRole}> 🚨`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.warn(`${firstUpperCaseName} query exceeded half of cache TTL: ${duration.toFixed(2)}s`)
|
|
30
|
+
|
|
31
|
+
await fetch(webhookURL, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34
|
+
body: JSON.stringify(data)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": ".",
|
|
9
|
+
"baseUrl": ".",
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"paths": {
|
|
17
|
+
"@images/*": ["images/*"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": [
|
|
21
|
+
"src",
|
|
22
|
+
"tailwind.config.ts"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
File without changes
|