uibee 1.3.1 → 1.4.1

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.
Files changed (41) hide show
  1. package/dist/globals.css +52 -0
  2. package/dist/src/components/index.d.ts +2 -1
  3. package/dist/src/components/index.js +2 -1
  4. package/dist/src/components/{loginPage.js → login/loginPage.js} +1 -1
  5. package/dist/src/components/toast/toaster.d.ts +3 -0
  6. package/dist/src/components/toast/toaster.js +75 -0
  7. package/dist/src/index.d.ts +4 -4
  8. package/dist/src/index.js +4 -4
  9. package/dist/src/scripts/buildCss.js +1 -1
  10. package/dist/src/scripts/rewriteAlias.d.ts +1 -0
  11. package/dist/src/scripts/rewriteAlias.js +31 -0
  12. package/dist/src/utils/discord/discordAlert.d.ts +2 -0
  13. package/dist/src/utils/discord/discordAlert.js +32 -0
  14. package/dist/src/utils/index.d.ts +2 -0
  15. package/dist/src/utils/index.js +2 -0
  16. package/dist/src/utils/sql/alertSlowQuery.d.ts +2 -0
  17. package/dist/src/utils/sql/alertSlowQuery.js +25 -0
  18. package/eslint.config.js +46 -0
  19. package/images/logo-tekst-white.svg +171 -0
  20. package/index.d.ts +5 -0
  21. package/package.json +6 -9
  22. package/src/components/index.ts +2 -0
  23. package/src/components/login/loginPage.tsx +81 -0
  24. package/src/components/toast/toaster.tsx +103 -0
  25. package/src/hooks/index.ts +2 -0
  26. package/src/hooks/useDarkMode.ts +23 -0
  27. package/src/hooks/useVisibility.ts +32 -0
  28. package/src/index.ts +4 -0
  29. package/src/scripts/buildCss.ts +25 -0
  30. package/src/scripts/index.ts +1 -0
  31. package/src/scripts/rewriteAlias.ts +37 -0
  32. package/src/types/components.d.ts +29 -0
  33. package/src/types/hooks.d.ts +9 -0
  34. package/src/types/svg.ts +4 -0
  35. package/src/types/utils.d.ts +34 -0
  36. package/src/utils/discord/discordAlert.ts +44 -0
  37. package/src/utils/index.ts +2 -0
  38. package/src/utils/sql/alertSlowQuery.ts +37 -0
  39. package/tailwind.config.ts +9 -0
  40. package/tsconfig.json +27 -0
  41. /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,7 +2,7 @@ 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 '../../images/logo-tekst-white.svg';
5
+ import logo from '../../../images/logo-tekst-white.svg';
6
6
  export default function LoginPage({ title, description, redirectURI, version, btg, handleSubmit }) {
7
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
8
  e.preventDefault();
@@ -0,0 +1,3 @@
1
+ import { ToastProps } from 'uibee/components';
2
+ export declare function addToast(message: string, type?: ToastProps['type']): void;
3
+ export default function Toaster(): import("react/jsx-runtime").JSX.Element;
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- export * from './hooks/useVisibility';
2
- export * from './hooks/useDarkMode';
3
- export * from './components/loginPage';
4
- export * from './scripts/buildCss';
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/useVisibility';
2
- export * from './hooks/useDarkMode';
3
- export * from './components/loginPage';
4
- export * from './scripts/buildCss';
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('🐝 dist/globals.css generated successfully');
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,2 @@
1
+ import { DiscordAlertProps } from 'uibee/utils';
2
+ export default function discordAlert({ application, description, type, ping, criticalRole, webhookURL }: DiscordAlertProps): Promise<number>;
@@ -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,2 @@
1
+ export { default as alertSlowQuery } from './sql/alertSlowQuery';
2
+ export { default as discordAlert } from './discord/discordAlert';
@@ -0,0 +1,2 @@
1
+ export { default as alertSlowQuery } from './sql/alertSlowQuery';
2
+ export { default as discordAlert } from './discord/discordAlert';
@@ -0,0 +1,2 @@
1
+ import { SlowQueryProps } from 'uibee/utils';
2
+ export default function alertSlowQuery({ application, duration, name, cacheTTL, webhookURL, criticalRole }: SlowQueryProps): Promise<void>;
@@ -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
+ }
@@ -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/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './components'
2
+ export * from './hooks'
3
+ export * from './utils'
4
+ export * from './scripts'
5
+ export * from './styles'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uibee",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
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:alias": "tsc-alias",
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:alias && npm run build:assets",
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,2 @@
1
+ export { default as LoginPage } from './login/loginPage'
2
+ export { default as Toaster, addToast } from './toast/toaster'
@@ -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,2 @@
1
+ export { default as useVisibility } from './useVisibility'
2
+ export { default as useDarkMode } from './useDarkMode'
@@ -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,4 @@
1
+ export * from './hooks'
2
+ export * from './components'
3
+ export * from './utils'
4
+ export * from './scripts'
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ declare module '*.svg' {
2
+ const content: string
3
+ export default content
4
+ }
@@ -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,2 @@
1
+ export { default as alertSlowQuery } from './sql/alertSlowQuery'
2
+ export { default as discordAlert } from './discord/discordAlert'
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ import type { Config } from 'tailwindcss'
2
+
3
+ const tailwindConfig: Config = {
4
+ content: ['./app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
5
+ theme: { extend: {} },
6
+ plugins: [],
7
+ }
8
+
9
+ export default tailwindConfig
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
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/*": [
18
+ "images/*"
19
+ ]
20
+ }
21
+ },
22
+ "include": [
23
+ "src",
24
+ "tailwind.config.ts",
25
+ "index.d.ts"
26
+ ]
27
+ }