uibee 2.8.6 → 2.9.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.
@@ -0,0 +1,12 @@
1
+ type ConfirmPopupProps = {
2
+ isOpen: boolean;
3
+ header: string;
4
+ description?: string;
5
+ confirmText?: string;
6
+ cancelText?: string;
7
+ onConfirm: () => void;
8
+ onCancel: () => void;
9
+ variant?: 'danger' | 'warning' | 'default';
10
+ };
11
+ export default function ConfirmPopup({ isOpen, header, description, confirmText, cancelText, onConfirm, onCancel, variant, }: ConfirmPopupProps): import("react/jsx-runtime").JSX.Element | null;
12
+ export {};
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { TriangleAlert } from 'lucide-react';
4
+ export default function ConfirmPopup({ isOpen, header, description, confirmText = 'Confirm', cancelText = 'Cancel', onConfirm, onCancel, variant = 'default', }) {
5
+ if (!isOpen)
6
+ return null;
7
+ const confirmBg = variant === 'danger'
8
+ ? 'bg-red-700/80 outline-red-700 hover:bg-red-700'
9
+ : variant === 'warning'
10
+ ? 'bg-yellow-600/80 outline-yellow-600 hover:bg-yellow-600'
11
+ : 'bg-login/70 outline-login hover:bg-login/90';
12
+ return (_jsxs("div", { role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'confirm-popup-header', className: 'fixed inset-0 z-50 flex items-center justify-center', onClick: onCancel, children: [_jsx("div", { className: 'absolute inset-0 bg-black/60 backdrop-blur-sm' }), _jsxs("div", { className: '\n relative z-10 w-full max-w-md mx-4\n bg-login-800 border border-login-500/50 rounded-xl\n shadow-2xl p-6 flex flex-col gap-4\n ', onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: 'flex items-center gap-3', children: [variant !== 'default' && (_jsx(TriangleAlert, { className: `w-6 h-6 shrink-0 ${variant === 'danger' ? 'stroke-red-400' : 'stroke-yellow-400'}` })), _jsx("h2", { id: 'confirm-popup-header', className: 'text-login-50 text-lg font-bold leading-snug', children: header })] }), description && (_jsx("p", { className: 'text-login-100 text-sm leading-relaxed', children: description })), _jsxs("div", { className: 'flex justify-end gap-3 mt-1', children: [_jsx("button", { type: 'button', onClick: onCancel, className: '\n cursor-pointer px-4 py-1.5 rounded-md text-sm font-medium\n bg-login-500/60 text-login-50 outline outline-login-500/60\n hover:bg-login-500/90 focus:outline-none select-none\n transition-colors duration-150\n ', children: cancelText }), _jsx("button", { type: 'button', onClick: onConfirm, className: `
13
+ cursor-pointer px-4 py-1.5 rounded-md text-sm font-bold
14
+ text-white outline focus:outline-none select-none
15
+ transition-colors duration-150
16
+ ${confirmBg}
17
+ `, children: confirmText })] })] })] }));
18
+ }
@@ -20,3 +20,4 @@ export { default as LoginPage } from './login/loginPage';
20
20
  export { default as Toaster, toast } from './toast/toaster';
21
21
  export { default as Button } from './buttons/button';
22
22
  export { default as Alert } from './alert/alert';
23
+ export { default as ConfirmPopup } from './confirm/confirmPopup';
@@ -29,3 +29,5 @@ export { default as Toaster, toast } from './toast/toaster';
29
29
  export { default as Button } from './buttons/button';
30
30
  // Alert
31
31
  export { default as Alert } from './alert/alert';
32
+ // Confirm
33
+ export { default as ConfirmPopup } from './confirm/confirmPopup';
@@ -10,8 +10,11 @@
10
10
  --color-red-200: oklch(88.5% 0.062 18.334);
11
11
  --color-red-400: oklch(70.4% 0.191 22.216);
12
12
  --color-red-500: oklch(63.7% 0.237 25.331);
13
+ --color-red-700: oklch(50.5% 0.213 27.518);
13
14
  --color-red-900: oklch(39.6% 0.141 25.723);
15
+ --color-yellow-400: oklch(85.2% 0.199 91.936);
14
16
  --color-yellow-500: oklch(79.5% 0.184 86.047);
17
+ --color-yellow-600: oklch(68.1% 0.162 75.834);
15
18
  --color-green-500: oklch(72.3% 0.219 149.579);
16
19
  --color-blue-500: oklch(62.3% 0.214 259.815);
17
20
  --color-blue-600: oklch(54.6% 0.245 262.881);
@@ -46,6 +49,8 @@
46
49
  --font-weight-bold: 700;
47
50
  --font-weight-extrabold: 800;
48
51
  --tracking-tight: -0.025em;
52
+ --leading-snug: 1.375;
53
+ --leading-relaxed: 1.625;
49
54
  --radius-xs: 0.125rem;
50
55
  --radius-sm: 0.25rem;
51
56
  --radius-md: 0.375rem;
@@ -53,6 +58,7 @@
53
58
  --radius-xl: 0.75rem;
54
59
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
55
60
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
61
+ --blur-sm: 8px;
56
62
  --blur-xl: 24px;
57
63
  --default-transition-duration: 150ms;
58
64
  --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -377,6 +383,9 @@
377
383
  .m-auto {
378
384
  margin: auto;
379
385
  }
386
+ .mx-4 {
387
+ margin-inline: calc(var(--spacing) * 4);
388
+ }
380
389
  .mx-auto {
381
390
  margin-inline: auto;
382
391
  }
@@ -1442,6 +1451,12 @@
1442
1451
  .border-login-500 {
1443
1452
  border-color: var(--color-login-500);
1444
1453
  }
1454
+ .border-login-500\/50 {
1455
+ border-color: color-mix(in srgb, #323232 50%, transparent);
1456
+ @supports (color: color-mix(in lab, red, red)) {
1457
+ border-color: color-mix(in oklab, var(--color-login-500) 50%, transparent);
1458
+ }
1459
+ }
1445
1460
  .border-red-500 {
1446
1461
  border-color: var(--color-red-500);
1447
1462
  }
@@ -1454,6 +1469,12 @@
1454
1469
  .bg-\[\#18181899\] {
1455
1470
  background-color: #18181899;
1456
1471
  }
1472
+ .bg-black\/60 {
1473
+ background-color: color-mix(in srgb, #000 60%, transparent);
1474
+ @supports (color: color-mix(in lab, red, red)) {
1475
+ background-color: color-mix(in oklab, var(--color-black) 60%, transparent);
1476
+ }
1477
+ }
1457
1478
  .bg-blue-600 {
1458
1479
  background-color: var(--color-blue-600);
1459
1480
  }
@@ -1478,6 +1499,12 @@
1478
1499
  background-color: color-mix(in oklab, var(--color-login-500) 50%, transparent);
1479
1500
  }
1480
1501
  }
1502
+ .bg-login-500\/60 {
1503
+ background-color: color-mix(in srgb, #323232 60%, transparent);
1504
+ @supports (color: color-mix(in lab, red, red)) {
1505
+ background-color: color-mix(in oklab, var(--color-login-500) 60%, transparent);
1506
+ }
1507
+ }
1481
1508
  .bg-login-500\/70 {
1482
1509
  background-color: color-mix(in srgb, #323232 70%, transparent);
1483
1510
  @supports (color: color-mix(in lab, red, red)) {
@@ -1514,6 +1541,12 @@
1514
1541
  background-color: color-mix(in oklab, var(--color-login) 70%, transparent);
1515
1542
  }
1516
1543
  }
1544
+ .bg-red-700\/80 {
1545
+ background-color: color-mix(in srgb, oklch(50.5% 0.213 27.518) 80%, transparent);
1546
+ @supports (color: color-mix(in lab, red, red)) {
1547
+ background-color: color-mix(in oklab, var(--color-red-700) 80%, transparent);
1548
+ }
1549
+ }
1517
1550
  .bg-red-900 {
1518
1551
  background-color: var(--color-red-900);
1519
1552
  }
@@ -1523,6 +1556,12 @@
1523
1556
  .bg-white {
1524
1557
  background-color: var(--color-white);
1525
1558
  }
1559
+ .bg-yellow-600\/80 {
1560
+ background-color: color-mix(in srgb, oklch(68.1% 0.162 75.834) 80%, transparent);
1561
+ @supports (color: color-mix(in lab, red, red)) {
1562
+ background-color: color-mix(in oklab, var(--color-yellow-600) 80%, transparent);
1563
+ }
1564
+ }
1526
1565
  .bg-linear-to-r {
1527
1566
  --tw-gradient-position: to right;
1528
1567
  @supports (background-image: linear-gradient(in lab, red, red)) {
@@ -1576,6 +1615,12 @@
1576
1615
  .stroke-login-50 {
1577
1616
  stroke: var(--color-login-50);
1578
1617
  }
1618
+ .stroke-red-400 {
1619
+ stroke: var(--color-red-400);
1620
+ }
1621
+ .stroke-yellow-400 {
1622
+ stroke: var(--color-yellow-400);
1623
+ }
1579
1624
  .stroke-\[3\.5px\] {
1580
1625
  stroke-width: 3.5px;
1581
1626
  }
@@ -1597,6 +1642,9 @@
1597
1642
  .p-4 {
1598
1643
  padding: calc(var(--spacing) * 4);
1599
1644
  }
1645
+ .p-6 {
1646
+ padding: calc(var(--spacing) * 6);
1647
+ }
1600
1648
  .p-8 {
1601
1649
  padding: calc(var(--spacing) * 8);
1602
1650
  }
@@ -1718,6 +1766,14 @@
1718
1766
  --tw-leading: calc(var(--spacing) * 8);
1719
1767
  line-height: calc(var(--spacing) * 8);
1720
1768
  }
1769
+ .leading-relaxed {
1770
+ --tw-leading: var(--leading-relaxed);
1771
+ line-height: var(--leading-relaxed);
1772
+ }
1773
+ .leading-snug {
1774
+ --tw-leading: var(--leading-snug);
1775
+ line-height: var(--leading-snug);
1776
+ }
1721
1777
  .font-bold {
1722
1778
  --tw-font-weight: var(--font-weight-bold);
1723
1779
  font-weight: var(--font-weight-bold);
@@ -1757,6 +1813,12 @@
1757
1813
  .text-login\! {
1758
1814
  color: var(--color-login) !important;
1759
1815
  }
1816
+ .text-login-50 {
1817
+ color: var(--color-login-50);
1818
+ }
1819
+ .text-login-100 {
1820
+ color: var(--color-login-100);
1821
+ }
1760
1822
  .text-login-200 {
1761
1823
  color: var(--color-login-200);
1762
1824
  }
@@ -1807,6 +1869,10 @@
1807
1869
  .opacity-100 {
1808
1870
  opacity: 100%;
1809
1871
  }
1872
+ .shadow-2xl {
1873
+ --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));
1874
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1875
+ }
1810
1876
  .shadow-\[0_0\.1rem_0\.5rem_rgba\(3\,3\,3\,0\.5\)\] {
1811
1877
  --tw-shadow: 0 0.1rem 0.5rem var(--tw-shadow-color, rgba(3,3,3,0.5));
1812
1878
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -1846,6 +1912,23 @@
1846
1912
  .outline-login-500 {
1847
1913
  outline-color: var(--color-login-500);
1848
1914
  }
1915
+ .outline-login-500\/60 {
1916
+ outline-color: color-mix(in srgb, #323232 60%, transparent);
1917
+ @supports (color: color-mix(in lab, red, red)) {
1918
+ outline-color: color-mix(in oklab, var(--color-login-500) 60%, transparent);
1919
+ }
1920
+ }
1921
+ .outline-red-700 {
1922
+ outline-color: var(--color-red-700);
1923
+ }
1924
+ .outline-yellow-600 {
1925
+ outline-color: var(--color-yellow-600);
1926
+ }
1927
+ .backdrop-blur-sm {
1928
+ --tw-backdrop-blur: blur(var(--blur-sm));
1929
+ -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
1930
+ backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
1931
+ }
1849
1932
  .backdrop-blur-xl {
1850
1933
  --tw-backdrop-blur: blur(var(--blur-xl));
1851
1934
  -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
@@ -2215,6 +2298,20 @@
2215
2298
  }
2216
2299
  }
2217
2300
  }
2301
+ .hover\:bg-red-700 {
2302
+ &:hover {
2303
+ @media (hover: hover) {
2304
+ background-color: var(--color-red-700);
2305
+ }
2306
+ }
2307
+ }
2308
+ .hover\:bg-yellow-600 {
2309
+ &:hover {
2310
+ @media (hover: hover) {
2311
+ background-color: var(--color-yellow-600);
2312
+ }
2313
+ }
2314
+ }
2218
2315
  .hover\:text-login-200 {
2219
2316
  &:hover {
2220
2317
  @media (hover: hover) {
@@ -1,2 +1,2 @@
1
1
  import type { AuthCallbackProps } from 'uibee/utils';
2
- export default function authCallback({ req, tokenURL, clientID, clientSecret, redirectURL, userInfoURL, tokenRedirectURL }: AuthCallbackProps): Promise<Response>;
2
+ export default function authCallback({ req, tokenURL, clientID, clientSecret, redirectPath, userInfoURL, tokenRedirectPath }: AuthCallbackProps): Promise<Response>;
@@ -1,5 +1,7 @@
1
1
  import { NextResponse } from 'next/server';
2
- export default async function authCallback({ req, tokenURL, clientID, clientSecret, redirectURL, userInfoURL, tokenRedirectURL }) {
2
+ import { getDomain } from './getDomain';
3
+ export default async function authCallback({ req, tokenURL, clientID, clientSecret, redirectPath, userInfoURL, tokenRedirectPath }) {
4
+ const domain = getDomain(req);
3
5
  const searchParams = new URL(req.url).searchParams;
4
6
  if (!searchParams) {
5
7
  return NextResponse.json({ error: 'No search parameters found.' }, { status: 400 });
@@ -17,7 +19,7 @@ export default async function authCallback({ req, tokenURL, clientID, clientSecr
17
19
  client_id: clientID,
18
20
  client_secret: clientSecret,
19
21
  code: code,
20
- redirect_uri: redirectURL,
22
+ redirect_uri: `${domain}${redirectPath}`,
21
23
  grant_type: 'authorization_code',
22
24
  }).toString()
23
25
  });
@@ -41,7 +43,7 @@ export default async function authCallback({ req, tokenURL, clientID, clientSecr
41
43
  });
42
44
  }
43
45
  const userInfo = await userInfoResponse.json();
44
- const redirectUrl = new URL(tokenRedirectURL);
46
+ const redirectUrl = new URL(`${domain}${tokenRedirectPath}`);
45
47
  const params = new URLSearchParams({
46
48
  id: userInfo.sub,
47
49
  name: userInfo.name,
@@ -0,0 +1,2 @@
1
+ import { NextRequest } from 'next/server';
2
+ export declare function getDomain(req: NextRequest): string;
@@ -0,0 +1,5 @@
1
+ export function getDomain(req) {
2
+ const proto = req.headers.get('x-forwarded-proto') ?? new URL(req.url).protocol.replace(':', '');
3
+ const host = req.headers.get('x-forwarded-host') ?? req.headers.get('host') ?? new URL(req.url).host;
4
+ return `${proto}://${host}`;
5
+ }
@@ -1,3 +1,3 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import type { AuthLoginProps } from 'uibee/utils';
3
- export default function AuthLogin({ clientID, redirectURL, authURL }: AuthLoginProps): Promise<NextResponse<unknown>>;
3
+ export default function AuthLogin({ req, clientID, redirectPath, authURL }: AuthLoginProps): Promise<NextResponse<unknown>>;
@@ -1,9 +1,11 @@
1
1
  import { NextResponse } from 'next/server';
2
- export default async function AuthLogin({ clientID, redirectURL, authURL }) {
2
+ import { getDomain } from './getDomain';
3
+ export default async function AuthLogin({ req, clientID, redirectPath, authURL }) {
4
+ const domain = getDomain(req);
3
5
  const state = Math.random().toString(36).substring(5);
4
6
  const authQueryParams = new URLSearchParams({
5
7
  client_id: clientID,
6
- redirect_uri: redirectURL,
8
+ redirect_uri: `${domain}${redirectPath}`,
7
9
  response_type: 'code',
8
10
  scope: 'openid profile email',
9
11
  state: state,
@@ -1,3 +1,3 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import type { AuthLogoutProps } from 'uibee/utils';
3
- export default function AuthLogout({ frontendURL, path }: AuthLogoutProps): Promise<NextResponse<unknown>>;
3
+ export default function AuthLogout({ req, path }: AuthLogoutProps): Promise<NextResponse<unknown>>;
@@ -1,6 +1,8 @@
1
1
  import { NextResponse } from 'next/server';
2
- export default async function AuthLogout({ frontendURL, path }) {
3
- const response = NextResponse.redirect(new URL(path || '/', frontendURL));
2
+ import { getDomain } from './getDomain';
3
+ export default async function AuthLogout({ req, path }) {
4
+ const domain = getDomain(req);
5
+ const response = NextResponse.redirect(new URL(path || '/', domain));
4
6
  const cookiesToRemove = [
5
7
  'access_token',
6
8
  'user_id',
@@ -1,3 +1,3 @@
1
1
  import { NextResponse } from 'next/server';
2
2
  import type { AuthTokenProps } from 'uibee/utils';
3
- export default function AuthToken({ req, frontendURL, redirectPath }: AuthTokenProps): Promise<NextResponse<unknown>>;
3
+ export default function AuthToken({ req, redirectPath }: AuthTokenProps): Promise<NextResponse<unknown>>;
@@ -1,5 +1,7 @@
1
1
  import { NextResponse } from 'next/server';
2
- export default async function AuthToken({ req, frontendURL, redirectPath }) {
2
+ import { getDomain } from './getDomain';
3
+ export default async function AuthToken({ req, redirectPath }) {
4
+ const domain = getDomain(req);
3
5
  const url = new URL(req.url);
4
6
  const token = url.searchParams.get('access_token');
5
7
  const btg = url.searchParams.get('btg');
@@ -9,7 +11,7 @@ export default async function AuthToken({ req, frontendURL, redirectPath }) {
9
11
  return NextResponse.json({ error: 'No access token provided' }, { status: 400 });
10
12
  }
11
13
  if (btg) {
12
- return NextResponse.redirect(new URL(redirect, frontendURL));
14
+ return NextResponse.redirect(new URL(redirect, domain));
13
15
  }
14
16
  const accessToken = url.searchParams.get('access_token');
15
17
  const userID = url.searchParams.get('id');
@@ -17,7 +19,7 @@ export default async function AuthToken({ req, frontendURL, redirectPath }) {
17
19
  const userNickname = url.searchParams.get('username');
18
20
  const userEmail = url.searchParams.get('email');
19
21
  const userGroups = url.searchParams.get('groups');
20
- const response = NextResponse.redirect(new URL(redirect, frontendURL));
22
+ const response = NextResponse.redirect(new URL(redirect, domain));
21
23
  response.cookies.set('access_token', accessToken);
22
24
  response.cookies.set('user_id', userID);
23
25
  response.cookies.set('user_name', username);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uibee",
3
- "version": "2.8.6",
3
+ "version": "2.9.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": {
@@ -0,0 +1,103 @@
1
+ 'use client'
2
+
3
+ import { TriangleAlert } from 'lucide-react'
4
+
5
+ type ConfirmPopupProps = {
6
+ isOpen: boolean
7
+ header: string
8
+ description?: string
9
+ confirmText?: string
10
+ cancelText?: string
11
+ onConfirm: () => void
12
+ onCancel: () => void
13
+ variant?: 'danger' | 'warning' | 'default'
14
+ }
15
+
16
+ export default function ConfirmPopup({
17
+ isOpen,
18
+ header,
19
+ description,
20
+ confirmText = 'Confirm',
21
+ cancelText = 'Cancel',
22
+ onConfirm,
23
+ onCancel,
24
+ variant = 'default',
25
+ }: ConfirmPopupProps) {
26
+ if (!isOpen) return null
27
+
28
+ const confirmBg =
29
+ variant === 'danger'
30
+ ? 'bg-red-700/80 outline-red-700 hover:bg-red-700'
31
+ : variant === 'warning'
32
+ ? 'bg-yellow-600/80 outline-yellow-600 hover:bg-yellow-600'
33
+ : 'bg-login/70 outline-login hover:bg-login/90'
34
+
35
+ return (
36
+ <div
37
+ role='dialog'
38
+ aria-modal='true'
39
+ aria-labelledby='confirm-popup-header'
40
+ className='fixed inset-0 z-50 flex items-center justify-center'
41
+ onClick={onCancel}
42
+ >
43
+ <div className='absolute inset-0 bg-black/60 backdrop-blur-sm' />
44
+
45
+ <div
46
+ className='
47
+ relative z-10 w-full max-w-md mx-4
48
+ bg-login-800 border border-login-500/50 rounded-xl
49
+ shadow-2xl p-6 flex flex-col gap-4
50
+ '
51
+ onClick={(e) => e.stopPropagation()}
52
+ >
53
+ <div className='flex items-center gap-3'>
54
+ {variant !== 'default' && (
55
+ <TriangleAlert
56
+ className={`w-6 h-6 shrink-0 ${variant === 'danger' ? 'stroke-red-400' : 'stroke-yellow-400'}`}
57
+ />
58
+ )}
59
+ <h2
60
+ id='confirm-popup-header'
61
+ className='text-login-50 text-lg font-bold leading-snug'
62
+ >
63
+ {header}
64
+ </h2>
65
+ </div>
66
+
67
+ {description && (
68
+ <p className='text-login-100 text-sm leading-relaxed'>
69
+ {description}
70
+ </p>
71
+ )}
72
+
73
+ <div className='flex justify-end gap-3 mt-1'>
74
+ <button
75
+ type='button'
76
+ onClick={onCancel}
77
+ className='
78
+ cursor-pointer px-4 py-1.5 rounded-md text-sm font-medium
79
+ bg-login-500/60 text-login-50 outline outline-login-500/60
80
+ hover:bg-login-500/90 focus:outline-none select-none
81
+ transition-colors duration-150
82
+ '
83
+ >
84
+ {cancelText}
85
+ </button>
86
+
87
+ <button
88
+ type='button'
89
+ onClick={onConfirm}
90
+ className={`
91
+ cursor-pointer px-4 py-1.5 rounded-md text-sm font-bold
92
+ text-white outline focus:outline-none select-none
93
+ transition-colors duration-150
94
+ ${confirmBg}
95
+ `}
96
+ >
97
+ {confirmText}
98
+ </button>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ )
103
+ }
@@ -36,4 +36,7 @@ export { default as Toaster, toast } from './toast/toaster'
36
36
  export { default as Button } from './buttons/button'
37
37
 
38
38
  // Alert
39
- export { default as Alert } from './alert/alert'
39
+ export { default as Alert } from './alert/alert'
40
+
41
+ // Confirm
42
+ export { default as ConfirmPopup } from './confirm/confirmPopup'
@@ -45,4 +45,16 @@ declare module 'uibee/components' {
45
45
  }
46
46
  export default function LanguageToggle(props: { lang: Language }): JSX.Element;
47
47
  export default function ThemeSwitch(props: { className?: string }): JSX.Element;
48
+
49
+ export interface ConfirmPopupProps {
50
+ isOpen: boolean
51
+ header: string
52
+ description?: string
53
+ confirmText?: string
54
+ cancelText?: string
55
+ onConfirm: () => void
56
+ onCancel: () => void
57
+ variant?: 'danger' | 'warning' | 'default'
58
+ }
59
+ export function ConfirmPopup(props: ConfirmPopupProps): JSX.Element | null;
48
60
  }
@@ -2,30 +2,30 @@
2
2
  import { NextRequest } from 'next/server'
3
3
  declare module 'uibee/utils' {
4
4
  export interface AuthLoginProps {
5
+ req: NextRequest
5
6
  clientID: string
6
- redirectURL: string
7
+ redirectPath: string
7
8
  authURL: string
8
9
  }
9
10
 
10
11
  export interface AuthCallbackProps {
11
- req: Request
12
+ req: NextRequest
12
13
  tokenURL: string
13
14
  clientID: string
14
15
  clientSecret: string
15
- redirectURL: string
16
+ redirectPath: string
16
17
  userInfoURL: string
17
- tokenRedirectURL: string
18
+ tokenRedirectPath: string
18
19
  }
19
20
 
20
21
  export interface AuthTokenProps {
21
22
  req: NextRequest
22
- frontendURL: string
23
23
  redirectPath?: string
24
24
  }
25
+
25
26
  export interface AuthLogoutProps {
26
- request: NextRequest
27
+ req: NextRequest
27
28
  path?: string
28
- frontendURL: string
29
29
  }
30
30
 
31
31
  export default async function authLogin(props: AuthLoginProps): Promise<Response>;
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import type { AuthCallbackProps } from 'uibee/utils'
3
+ import { getDomain } from './getDomain'
3
4
 
4
5
  type UserInfo = {
5
6
  sub: string
@@ -14,10 +15,11 @@ export default async function authCallback({
14
15
  tokenURL,
15
16
  clientID,
16
17
  clientSecret,
17
- redirectURL,
18
+ redirectPath,
18
19
  userInfoURL,
19
- tokenRedirectURL
20
+ tokenRedirectPath
20
21
  }: AuthCallbackProps) {
22
+ const domain = getDomain(req)
21
23
  const searchParams = new URL(req.url).searchParams
22
24
 
23
25
  if (!searchParams) {
@@ -38,7 +40,7 @@ export default async function authCallback({
38
40
  client_id: clientID,
39
41
  client_secret: clientSecret,
40
42
  code: code as string,
41
- redirect_uri: redirectURL,
43
+ redirect_uri: `${domain}${redirectPath}`,
42
44
  grant_type: 'authorization_code',
43
45
  }).toString()
44
46
  })
@@ -69,7 +71,7 @@ export default async function authCallback({
69
71
 
70
72
  const userInfo = await userInfoResponse.json() as UserInfo
71
73
 
72
- const redirectUrl = new URL(tokenRedirectURL)
74
+ const redirectUrl = new URL(`${domain}${tokenRedirectPath}`)
73
75
  const params = new URLSearchParams({
74
76
  id: userInfo.sub,
75
77
  name: userInfo.name,
@@ -0,0 +1,7 @@
1
+ import { NextRequest } from 'next/server'
2
+
3
+ export function getDomain(req: NextRequest): string {
4
+ const proto = req.headers.get('x-forwarded-proto') ?? new URL(req.url).protocol.replace(':', '')
5
+ const host = req.headers.get('x-forwarded-host') ?? req.headers.get('host') ?? new URL(req.url).host
6
+ return `${proto}://${host}`
7
+ }
@@ -1,11 +1,13 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import type { AuthLoginProps } from 'uibee/utils'
3
+ import { getDomain } from './getDomain'
3
4
 
4
- export default async function AuthLogin({ clientID, redirectURL, authURL }: AuthLoginProps) {
5
+ export default async function AuthLogin({ req, clientID, redirectPath, authURL }: AuthLoginProps) {
6
+ const domain = getDomain(req)
5
7
  const state = Math.random().toString(36).substring(5)
6
8
  const authQueryParams = new URLSearchParams({
7
9
  client_id: clientID,
8
- redirect_uri: redirectURL,
10
+ redirect_uri: `${domain}${redirectPath}`,
9
11
  response_type: 'code',
10
12
  scope: 'openid profile email',
11
13
  state: state,
@@ -1,8 +1,10 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import type { AuthLogoutProps } from 'uibee/utils'
3
+ import { getDomain } from './getDomain'
3
4
 
4
- export default async function AuthLogout({ frontendURL, path }: AuthLogoutProps) {
5
- const response = NextResponse.redirect(new URL(path || '/', frontendURL))
5
+ export default async function AuthLogout({ req, path }: AuthLogoutProps) {
6
+ const domain = getDomain(req)
7
+ const response = NextResponse.redirect(new URL(path || '/', domain))
6
8
 
7
9
  const cookiesToRemove = [
8
10
  'access_token',
@@ -1,7 +1,9 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import type { AuthTokenProps } from 'uibee/utils'
3
+ import { getDomain } from './getDomain'
3
4
 
4
- export default async function AuthToken({ req, frontendURL, redirectPath }: AuthTokenProps) {
5
+ export default async function AuthToken({ req, redirectPath }: AuthTokenProps) {
6
+ const domain = getDomain(req)
5
7
  const url = new URL(req.url)
6
8
  const token = url.searchParams.get('access_token')
7
9
  const btg = url.searchParams.get('btg')
@@ -13,7 +15,7 @@ export default async function AuthToken({ req, frontendURL, redirectPath }: Auth
13
15
  }
14
16
 
15
17
  if (btg) {
16
- return NextResponse.redirect(new URL(redirect, frontendURL))
18
+ return NextResponse.redirect(new URL(redirect, domain))
17
19
  }
18
20
 
19
21
  const accessToken = url.searchParams.get('access_token')!
@@ -23,7 +25,7 @@ export default async function AuthToken({ req, frontendURL, redirectPath }: Auth
23
25
  const userEmail = url.searchParams.get('email')!
24
26
  const userGroups = url.searchParams.get('groups')!
25
27
 
26
- const response = NextResponse.redirect(new URL(redirect, frontendURL))
28
+ const response = NextResponse.redirect(new URL(redirect, domain))
27
29
  response.cookies.set('access_token', accessToken)
28
30
  response.cookies.set('user_id', userID)
29
31
  response.cookies.set('user_name', username)