@airoom/nextmin-react 0.1.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.
Files changed (95) hide show
  1. package/LICENSE +49 -0
  2. package/README.md +133 -0
  3. package/dist/auth/AuthPage.d.ts +1 -0
  4. package/dist/auth/AuthPage.js +23 -0
  5. package/dist/auth/ForgotPasswordForm.d.ts +1 -0
  6. package/dist/auth/ForgotPasswordForm.js +28 -0
  7. package/dist/auth/SignInForm.d.ts +6 -0
  8. package/dist/auth/SignInForm.js +38 -0
  9. package/dist/auth/SignUpForm.d.ts +3 -0
  10. package/dist/auth/SignUpForm.js +30 -0
  11. package/dist/components/AddressAutocomplete.d.ts +21 -0
  12. package/dist/components/AddressAutocomplete.js +182 -0
  13. package/dist/components/AdminApp.d.ts +1 -0
  14. package/dist/components/AdminApp.js +134 -0
  15. package/dist/components/ConfirmDialog.d.ts +12 -0
  16. package/dist/components/ConfirmDialog.js +6 -0
  17. package/dist/components/FileUploader.d.ts +32 -0
  18. package/dist/components/FileUploader.js +480 -0
  19. package/dist/components/NoAccess.d.ts +3 -0
  20. package/dist/components/NoAccess.js +5 -0
  21. package/dist/components/PasswordInput.d.ts +19 -0
  22. package/dist/components/PasswordInput.js +11 -0
  23. package/dist/components/PhoneInput.d.ts +23 -0
  24. package/dist/components/PhoneInput.js +147 -0
  25. package/dist/components/RefMultiSelect.d.ts +14 -0
  26. package/dist/components/RefMultiSelect.js +76 -0
  27. package/dist/components/RefSingleSelect.d.ts +17 -0
  28. package/dist/components/RefSingleSelect.js +52 -0
  29. package/dist/components/SchemaForm.d.ts +13 -0
  30. package/dist/components/SchemaForm.js +592 -0
  31. package/dist/components/SectionLoader.d.ts +3 -0
  32. package/dist/components/SectionLoader.js +7 -0
  33. package/dist/components/Sidebar.d.ts +1 -0
  34. package/dist/components/Sidebar.js +87 -0
  35. package/dist/components/TableFilters.d.ts +16 -0
  36. package/dist/components/TableFilters.js +69 -0
  37. package/dist/components/TableSkeleton.d.ts +7 -0
  38. package/dist/components/TableSkeleton.js +5 -0
  39. package/dist/hooks/useGoogleMapsKey.d.ts +5 -0
  40. package/dist/hooks/useGoogleMapsKey.js +16 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +2 -0
  43. package/dist/lib/api.d.ts +31 -0
  44. package/dist/lib/api.js +94 -0
  45. package/dist/lib/auth.d.ts +23 -0
  46. package/dist/lib/auth.js +51 -0
  47. package/dist/lib/googleLoader.d.ts +1 -0
  48. package/dist/lib/googleLoader.js +25 -0
  49. package/dist/lib/schemaService.d.ts +2 -0
  50. package/dist/lib/schemaService.js +39 -0
  51. package/dist/lib/schemaUtils.d.ts +4 -0
  52. package/dist/lib/schemaUtils.js +18 -0
  53. package/dist/lib/types.d.ts +50 -0
  54. package/dist/lib/types.js +1 -0
  55. package/dist/nextmin.css +1 -0
  56. package/dist/providers/NextMinProvider.d.ts +5 -0
  57. package/dist/providers/NextMinProvider.js +30 -0
  58. package/dist/router/AdminRouteNormalizer.d.ts +1 -0
  59. package/dist/router/AdminRouteNormalizer.js +32 -0
  60. package/dist/router/NextMinRouter.d.ts +1 -0
  61. package/dist/router/NextMinRouter.js +99 -0
  62. package/dist/state/nextMinSlice.d.ts +14 -0
  63. package/dist/state/nextMinSlice.js +34 -0
  64. package/dist/state/schemaLive.d.ts +2 -0
  65. package/dist/state/schemaLive.js +19 -0
  66. package/dist/state/schemasSlice.d.ts +20 -0
  67. package/dist/state/schemasSlice.js +43 -0
  68. package/dist/state/sessionSlice.d.ts +10 -0
  69. package/dist/state/sessionSlice.js +18 -0
  70. package/dist/state/store.d.ts +28 -0
  71. package/dist/state/store.js +7 -0
  72. package/dist/views/CreateEditPage.d.ts +4 -0
  73. package/dist/views/CreateEditPage.js +64 -0
  74. package/dist/views/DashboardPage.d.ts +1 -0
  75. package/dist/views/DashboardPage.js +107 -0
  76. package/dist/views/ListPage.d.ts +5 -0
  77. package/dist/views/ListPage.js +76 -0
  78. package/dist/views/NextNotFound.d.ts +1 -0
  79. package/dist/views/NextNotFound.js +6 -0
  80. package/dist/views/ProfilePage.d.ts +1 -0
  81. package/dist/views/ProfilePage.js +193 -0
  82. package/dist/views/SettingsEdit.d.ts +2 -0
  83. package/dist/views/SettingsEdit.js +87 -0
  84. package/dist/views/list/DataTableHero.d.ts +22 -0
  85. package/dist/views/list/DataTableHero.js +350 -0
  86. package/dist/views/list/ListHeader.d.ts +8 -0
  87. package/dist/views/list/ListHeader.js +7 -0
  88. package/dist/views/list/Pagination.d.ts +8 -0
  89. package/dist/views/list/Pagination.js +5 -0
  90. package/dist/views/list/formatters.d.ts +2 -0
  91. package/dist/views/list/formatters.js +62 -0
  92. package/dist/views/list/useListData.d.ts +10 -0
  93. package/dist/views/list/useListData.js +79 -0
  94. package/package.json +51 -0
  95. package/tsconfig.json +18 -0
package/LICENSE ADDED
@@ -0,0 +1,49 @@
1
+ Nextmin Proprietary License v1.0
2
+
3
+ Copyright (c) 2025 GSCodes
4
+ All rights reserved.
5
+
6
+ 1. Definitions
7
+ “Software” means the package and all accompanying files under the package name(s) @airoom/nextmin-react and @airoom/nextmin-node, including any updates provided by Licensor.
8
+ “Licensor” means GSCodes.
9
+ “Licensee” means the person or entity that obtains the Software.
10
+
11
+ 2. Grant of License
12
+ Subject to full compliance with this License, Licensor grants Licensee a limited, non-exclusive, non-transferable, non-sublicensable license to:
13
+ a) install and use the Software internally, solely for evaluating or running Licensee’s own applications; and
14
+ b) make a reasonable number of copies solely for internal backup and archival purposes.
15
+
16
+ 3. Restrictions
17
+ Except as expressly permitted in Section 2, Licensee must NOT:
18
+ a) copy, publish, distribute, sell, rent, lease, lend, sublicense, or otherwise make the Software available to any third party;
19
+ b) modify, translate, adapt, create derivative works of, or merge the Software with other software;
20
+ c) reverse engineer, decompile, disassemble, or otherwise attempt to derive source code or underlying ideas, except to the extent such restrictions are expressly prohibited by applicable law;
21
+ d) remove, obscure, or alter any proprietary notices or markings; or
22
+ e) use the Software to train, fine-tune, or improve any artificial intelligence or machine learning model.
23
+
24
+ 4. Ownership
25
+ The Software is licensed, not sold. Licensor retains all right, title, and interest in and to the Software, including all intellectual property rights and all copies.
26
+
27
+ 5. Confidentiality
28
+ The Software and any non-public information provided by Licensor shall be treated as confidential and not disclosed to any third party without Licensor’s prior written consent, except as required by law.
29
+
30
+ 6. Term and Termination
31
+ This License is effective until terminated. Licensor may terminate this License immediately upon notice if Licensee breaches any term. Upon termination, Licensee must cease all use and destroy all copies of the Software. Sections 3–10 survive termination.
32
+
33
+ 7. Updates; No Support Obligation
34
+ Licensor may, but is not obligated to, provide updates, bug fixes, or support. Any updates are governed by this License unless accompanied by a different license.
35
+
36
+ 8. No Warranty
37
+ THE SOFTWARE IS PROVIDED “AS IS” AND “AS AVAILABLE,” WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT. USE IS AT LICENSEE’S SOLE RISK.
38
+
39
+ 9. Limitation of Liability
40
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT SHALL LICENSOR BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR ANY LOSS OF PROFITS, REVENUE, DATA, GOODWILL, OR BUSINESS INTERRUPTION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. LICENSOR’S TOTAL LIABILITY FOR ALL CLAIMS SHALL NOT EXCEED THE AMOUNT PAID BY LICENSEE FOR THE SOFTWARE (IF ANY) IN THE TWELVE (12) MONTHS PRECEDING THE CLAIM.
41
+
42
+ 10. Governing Law; Venue
43
+ This License is governed by the laws of State of California, USA, without regard to its conflict of laws principles. The parties submit to the exclusive jurisdiction and venue of the courts located in San Francisco County, California, USA.
44
+
45
+ 11. Commercial Licensing
46
+ For commercial use, redistribution, or other rights not expressly granted, contact: tareqaziz0065@gmail.com.
47
+
48
+ 12. Entire Agreement
49
+ This License constitutes the entire agreement with respect to the Software and supersedes all prior or contemporaneous agreements or understandings on the subject matter.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # @airoom/nextmin-react
2
+
3
+ Plug‑and‑play React Admin Panel that connects to a `@airoom/nextmin-node` backend. Render an authenticated dashboard with lists, forms, file uploads, settings, and live updates — generated from your JSON schemas.
4
+
5
+ - “From JSON schema to REST API + Admin”
6
+ - 1 month → 1 hour
7
+ - Works out of the box with Next.js (App Router and Pages Router)
8
+
9
+ ## Features
10
+
11
+ - `<NextMinProvider>`: initializes store, loads schemas, and opens a live socket
12
+ - `<AdminApp>`: full admin shell with auth, sidebar, dashboard, list/create/edit, profile, settings
13
+ - Built‑in router: `<NextMinRouter>` and `<AdminRouteNormalizer>` for admin routes
14
+ - Components: Sidebar, SchemaForm, FileUploader, reference selectors, phone/password inputs, table & filters
15
+ - Hooks/utils: Google address autocomplete, list data helpers, formatters
16
+ - Styling included; no extra CSS import needed
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ # npm
22
+ npm install @airoom/nextmin-react
23
+
24
+ # yarn
25
+ yarn add @airoom/nextmin-react
26
+
27
+ # pnpm
28
+ pnpm add @airoom/nextmin-react
29
+ ```
30
+
31
+ ## Configure environment
32
+
33
+ Create or update your frontend env file (e.g., `.env.local`) with the server base URL and client API key:
34
+
35
+ ```env
36
+ NEXT_PUBLIC_NEXTMIN_API_URL=http://localhost:8081/rest
37
+ # IMPORTANT: Use the API key stored in your database → Settings.apiKey
38
+ NEXT_PUBLIC_NEXTMIN_API_KEY=your_api_key_here
39
+ ```
40
+
41
+ Notes
42
+
43
+ - `NEXT_PUBLIC_NEXTMIN_API_URL` should point to your nextmin-node REST base (e.g., http://host:port/rest)
44
+ - `NEXT_PUBLIC_NEXTMIN_API_KEY` must match the value in your backend DB Settings document’s `apiKey` field
45
+
46
+ Where to find the API key
47
+
48
+ - Start your nextmin-node server once
49
+ - Open the database and locate the Settings collection/table
50
+ - Copy the first Settings document’s `apiKey` value and paste it here
51
+
52
+ ## Next.js setup (App Router)
53
+
54
+ Wrap only the admin area with the provider and render the AdminApp. Mark these files as client components.
55
+
56
+ ```tsx
57
+ // app/admin/layout.tsx
58
+ 'use client'
59
+ import { NextMinProvider } from '@airoom/nextmin-react';
60
+
61
+ export default function AdminLayout({ children }: { children: React.ReactNode }) {
62
+ return <NextMinProvider>{children}</NextMinProvider>;
63
+ }
64
+ ```
65
+
66
+ ```tsx
67
+ // app/admin/page.tsx
68
+ 'use client'
69
+ import { AdminApp } from '@airoom/nextmin-react';
70
+ export default function AdminIndex() {
71
+ return <AdminApp />;
72
+ }
73
+ ```
74
+
75
+ ```tsx
76
+ // app/admin/[...slug]/page.tsx
77
+ 'use client'
78
+ import { AdminApp } from '@airoom/nextmin-react';
79
+ export default function AdminCatchAll() {
80
+ return <AdminApp />;
81
+ }
82
+ ```
83
+
84
+ Behavior
85
+
86
+ - Unauthenticated users are redirected to `/admin/auth/sign-in`
87
+ - Authenticated users visiting `/admin/auth/*` are redirected to `/admin/dashboard`
88
+ - Sidebar and pages are rendered according to the schemas loaded from your backend
89
+
90
+ Pages Router (alternative)
91
+
92
+ ```tsx
93
+ // pages/admin.tsx
94
+ import dynamic from 'next/dynamic';
95
+ const Admin = dynamic(() => import('@airoom/nextmin-react').then(m => m.AdminApp), { ssr: false });
96
+ export default Admin;
97
+ ```
98
+
99
+ Wrap with `<NextMinProvider>` at a top level where appropriate (e.g., `_app.tsx`) and ensure it runs on the client.
100
+
101
+ ## Default admin credentials (after setup)
102
+
103
+ After completing the backend setup, sign in with the default super user and change the password immediately:
104
+
105
+ - Email: super@example.com
106
+ - Username: superadmin
107
+ - Password: supersecurepassword
108
+
109
+ ## Usage notes
110
+
111
+ - No extra CSS import required; the package bundles its styles
112
+ - Backend auth/paths are configured via env vars (see above)
113
+ - Add custom links to routes like `/admin/<model>/create` or `/admin/<model>/<id>`
114
+ - The first Settings document (model name "Settings") controls site name, logo, and Google Maps key for address autocomplete
115
+
116
+ ## TypeScript
117
+
118
+ Types are bundled with the package.
119
+
120
+ ```ts
121
+ import type { ApiItemResponse } from '@airoom/nextmin-react/types';
122
+ ```
123
+
124
+ ## Troubleshooting
125
+
126
+ - 401 Unauthorized: ensure `NEXT_PUBLIC_NEXTMIN_API_KEY` matches your DB `Settings.apiKey`
127
+ - Cannot load schemas: verify `NEXT_PUBLIC_NEXTMIN_API_URL` points to `/rest` and the backend is reachable
128
+ - Address autocomplete: set `NEXT_PUBLIC_GOOGLE_MAPS_KEY` in your environment and in the Settings document if applicable
129
+ - Blank page on Next.js: ensure admin files are client components (`'use client'`) and dynamic imports have `ssr: false` where necessary
130
+
131
+ ## License
132
+
133
+ Licensed under the Nextmin Proprietary License. © 2025 GSCodes. For commercial licensing or extended rights, contact: tareqaziz0065@gmail.com.
@@ -0,0 +1 @@
1
+ export declare function AuthPage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect } from 'react';
4
+ import { usePathname, useRouter } from 'next/navigation';
5
+ import { useSelector } from 'react-redux';
6
+ import { SignInForm } from './SignInForm';
7
+ import { ForgotPasswordForm } from './ForgotPasswordForm';
8
+ export function AuthPage() {
9
+ const pathname = usePathname();
10
+ const router = useRouter();
11
+ const token = useSelector((s) => s?.session?.token ?? null);
12
+ // If already authenticated on /admin/auth/* → go to dashboard
13
+ useEffect(() => {
14
+ if (token && pathname?.startsWith('/admin/auth')) {
15
+ router.replace('/admin/dashboard');
16
+ }
17
+ }, [token, pathname, router]);
18
+ if (pathname?.startsWith('/admin/auth/forgot-password')) {
19
+ return _jsx(ForgotPasswordForm, {});
20
+ }
21
+ // Default = Sign in; SignInForm already stores token+user and updates Redux.
22
+ return _jsx(SignInForm, { onSuccess: () => router.replace('/admin/dashboard') });
23
+ }
@@ -0,0 +1 @@
1
+ export declare function ForgotPasswordForm(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { Card, CardHeader, CardBody, Input, Button, Link, Form, } from '@heroui/react';
5
+ import { requestPasswordReset } from '../lib/auth';
6
+ export function ForgotPasswordForm() {
7
+ const [loading, setLoading] = useState(false);
8
+ const [msg, setMsg] = useState(null);
9
+ const [err, setErr] = useState(null);
10
+ async function submit(formData) {
11
+ setErr(null);
12
+ setMsg(null);
13
+ setLoading(true);
14
+ try {
15
+ await requestPasswordReset(String(formData.get('email') || ''));
16
+ setMsg('If an account exists, reset instructions have been sent.');
17
+ }
18
+ catch (e) {
19
+ setErr(e?.message || 'Request failed');
20
+ }
21
+ finally {
22
+ setLoading(false);
23
+ }
24
+ }
25
+ return (_jsx("div", { className: "min-h-[100dvh] grid place-items-center p-6 bg-default-50", children: _jsxs(Card, { className: "w-full max-w-md rounded-2xl shadow-lg border border-default-200", children: [_jsxs(CardHeader, { className: "flex flex-col items-center text-center gap-1 pt-6 pb-0", children: [_jsx("h1", { className: "text-3xl font-bold tracking-tight", children: "Reset password" }), _jsx("p", { className: "text-small text-foreground/60", children: "Enter your email to receive a reset link" })] }), _jsx(CardBody, { className: "gap-5 mt-4", children: _jsxs(Form, { className: "flex flex-col gap-4",
26
+ // @ts-ignore
27
+ onSubmit: submit, validationBehavior: "native", children: [_jsx(Input, { variant: "bordered", name: "email", type: "email", label: "Email", labelPlacement: "outside", placeholder: "name@example.com", isRequired: true, autoComplete: "email" }), msg && _jsx("p", { className: "text-sm text-success", children: msg }), err && _jsx("p", { className: "text-sm text-danger", children: err }), _jsx(Button, { type: "submit", isLoading: loading, className: "w-full bg-[#0a35ff] text-white", children: "Send reset link" }), _jsx("p", { className: "text-sm text-center text-foreground/70", children: _jsx(Link, { href: "/admin/auth/sign-in", children: "Back to sign in" }) })] }) })] }) }));
28
+ }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ type SignInFormProps = {
3
+ onSuccess?: (token: string, user: any) => void;
4
+ };
5
+ export declare function SignInForm({ onSuccess }: SignInFormProps): React.ReactElement;
6
+ export {};
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { Card, CardHeader, CardBody, Input, Button, Link, Form, } from '@heroui/react';
5
+ import { useDispatch } from 'react-redux';
6
+ import { setSession } from '../state/sessionSlice';
7
+ import { login as loginApi } from '../lib/auth';
8
+ export function SignInForm({ onSuccess }) {
9
+ const [loading, setLoading] = useState(false);
10
+ const [err, setErr] = useState(null);
11
+ const dispatch = useDispatch();
12
+ async function doLogin(email, password) {
13
+ return loginApi({ email, password });
14
+ }
15
+ return (_jsx("div", { className: "min-h-[100dvh] grid place-items-center p-6 bg-default-50", children: _jsxs(Card, { className: "w-full max-w-md rounded-2xl shadow-lg border border-default-200", children: [_jsxs(CardHeader, { className: "flex flex-col items-center text-center gap-1 pt-6 pb-0", children: [_jsx("h1", { className: "text-3xl font-bold tracking-tight", children: "Welcome back" }), _jsx("p", { className: "text-small text-foreground/60", children: "Sign in to your account" })] }), _jsx(CardBody, { className: "gap-5 mt-4", children: _jsxs(Form, { className: "flex flex-col gap-4", validationBehavior: "native", onSubmit: async (e) => {
16
+ e.preventDefault();
17
+ setErr(null);
18
+ setLoading(true);
19
+ const form = new FormData(e.currentTarget);
20
+ const email = String(form.get('email') ?? '');
21
+ const password = String(form.get('password') ?? '');
22
+ try {
23
+ const { token, user } = await doLogin(email, password);
24
+ // persist + redux
25
+ localStorage.setItem('nextmin.token', token);
26
+ localStorage.setItem('nextmin.user', JSON.stringify(user));
27
+ dispatch(setSession({ token, user }));
28
+ // optional callback
29
+ onSuccess?.(token, user);
30
+ }
31
+ catch (er) {
32
+ setErr(er?.message || 'Invalid credentials');
33
+ }
34
+ finally {
35
+ setLoading(false);
36
+ }
37
+ }, children: [_jsx(Input, { isRequired: true, variant: "bordered", label: "Email", labelPlacement: "outside", name: "email", placeholder: "Email", type: "email", autoComplete: "email" }), _jsx(Input, { isRequired: true, variant: "bordered", label: "Password", labelPlacement: "outside", name: "password", placeholder: "Password", type: "password", autoComplete: "current-password" }), err && _jsx("p", { className: "text-sm text-danger", children: err }), _jsx("div", { className: "flex items-center justify-start", children: _jsx(Link, { href: "/admin/auth/forgot-password", className: "text-sm", children: "Forgot Password?" }) }), _jsx(Button, { type: "submit", isLoading: loading, className: "w-full bg-[#0a35ff] text-white", children: "Continue" })] }) })] }) }));
38
+ }
@@ -0,0 +1,3 @@
1
+ export declare function SignUpForm({ onSuccess }: {
2
+ onSuccess?: () => void;
3
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { Card, CardHeader, CardBody, Input, Button, Checkbox, Link, Form, } from '@heroui/react';
5
+ import { register } from '../lib/auth';
6
+ export function SignUpForm({ onSuccess }) {
7
+ const [loading, setLoading] = useState(false);
8
+ const [err, setErr] = useState(null);
9
+ async function submit(formData) {
10
+ setErr(null);
11
+ setLoading(true);
12
+ try {
13
+ await register({
14
+ name: String(formData.get('name') || ''),
15
+ email: String(formData.get('email') || ''),
16
+ password: String(formData.get('password') || ''),
17
+ });
18
+ onSuccess?.();
19
+ }
20
+ catch (e) {
21
+ setErr(e?.message || 'Registration failed');
22
+ }
23
+ finally {
24
+ setLoading(false);
25
+ }
26
+ }
27
+ return (_jsx("div", { className: "min-h-[100dvh] grid place-items-center p-6", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "pb-0", children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Create account" }), _jsx("p", { className: "text-sm text-foreground/70", children: "Start your journey" })] }), _jsx(CardBody, { className: "gap-4 mt-4", children: _jsxs(Form, { className: "flex flex-col gap-4",
28
+ // @ts-ignore
29
+ onSubmit: submit, validationBehavior: "native", children: [_jsx(Input, { variant: "bordered", name: "name", label: "Full Name", isRequired: true, autoComplete: "name" }), _jsx(Input, { variant: "bordered", name: "email", type: "email", label: "Email", isRequired: true, autoComplete: "email" }), _jsx(Input, { variant: "bordered", name: "password", type: "password", label: "Password", isRequired: true, autoComplete: "new-password" }), err && _jsx("p", { className: "text-sm text-danger", children: err }), _jsxs(Checkbox, { required: true, children: ["I agree to the", ' ', _jsx(Link, { href: "/legal/terms", target: "_blank", children: "Terms" }), ' ', "\u00A0and\u00A0", _jsx(Link, { href: "/legal/privacy", target: "_blank", children: "Privacy Policy" })] }), _jsx(Button, { type: "submit", color: "primary", isLoading: loading, children: "Create account" }), _jsxs("p", { className: "text-sm text-center text-foreground/70", children: ["Already have an account? ", _jsx(Link, { href: "/auth/sign-in", children: "Sign in" })] })] }) })] }) }));
30
+ }
@@ -0,0 +1,21 @@
1
+ export type LatLng = {
2
+ lat: number;
3
+ lng: number;
4
+ };
5
+ export type AddressAutocompleteGoogleProps = {
6
+ name: string;
7
+ label?: string;
8
+ description?: string;
9
+ value: string;
10
+ onChange: (address: string, latlng?: LatLng) => void;
11
+ disabled?: boolean;
12
+ required?: boolean;
13
+ className?: string;
14
+ classNames?: {
15
+ inputWrapper?: string;
16
+ };
17
+ countryCodes?: string[];
18
+ limit?: number;
19
+ apiKey?: string;
20
+ };
21
+ export default function AddressAutocompleteGoogle({ name, label, description, value, onChange, disabled, required, className, classNames, countryCodes, limit, apiKey, }: AddressAutocompleteGoogleProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,182 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useRef, useState } from 'react';
4
+ import { Autocomplete, AutocompleteItem } from '@heroui/react';
5
+ import { loadGoogleMaps } from '../lib/googleLoader';
6
+ function deriveStableKey(s) {
7
+ const pid = s?.placePrediction?.placeId ??
8
+ s?.placePrediction?.id ??
9
+ s?.place_id ??
10
+ s?.id ??
11
+ null;
12
+ const label = s?.placePrediction?.text?.toString?.();
13
+ return pid ? String(pid) : label ? String(label) : null;
14
+ }
15
+ export default function AddressAutocompleteGoogle({ name, label, description, value, onChange, disabled, required, className, classNames, countryCodes, limit = 8, apiKey, }) {
16
+ // Controlled input
17
+ const [q, setQ] = useState(value ?? '');
18
+ useEffect(() => {
19
+ if ((value ?? '') !== q)
20
+ setQ(value ?? '');
21
+ // eslint-disable-next-line react-hooks/exhaustive-deps
22
+ }, [value]);
23
+ const [items, setItems] = useState([]);
24
+ const [keyMissing, setKeyMissing] = useState(false);
25
+ // Google + guards
26
+ const tokenRef = useRef(null);
27
+ const suppressFetchRef = useRef(false);
28
+ const fetchGenRef = useRef(0);
29
+ // Clears
30
+ const manualClearRef = useRef(false);
31
+ const locationRestriction = useMemo(() => {
32
+ const cc = (countryCodes?.[0] ?? '').toLowerCase();
33
+ if (cc === 'bd' || !cc)
34
+ return { west: 88.008, south: 20.67, east: 92.68, north: 26.634 };
35
+ return undefined;
36
+ }, [countryCodes]);
37
+ // Load Google once (and on key change)
38
+ useEffect(() => {
39
+ (async () => {
40
+ try {
41
+ const g = await loadGoogleMaps(apiKey);
42
+ tokenRef.current = new g.maps.places.AutocompleteSessionToken();
43
+ setKeyMissing(false);
44
+ }
45
+ catch {
46
+ setKeyMissing(true);
47
+ }
48
+ })();
49
+ }, [apiKey]);
50
+ if (!apiKey || !apiKey.trim()) {
51
+ return (_jsxs("div", { className: "text-danger text-xs", children: ["Google Maps API key is missing. Set ", _jsx("b", { children: "system.googleMapsKey" }), " or define ", _jsx("b", { children: "NEXT_PUBLIC_GOOGLE_MAPS_KEY" }), "."] }));
52
+ }
53
+ if (keyMissing) {
54
+ return (_jsxs("div", { className: "text-danger text-xs", children: ["Google Maps API key is missing or invalid. Set", ' ', _jsx("b", { children: "system.googleMapsKey" }), " or ", _jsx("b", { children: "NEXT_PUBLIC_GOOGLE_MAPS_KEY" }), "."] }));
55
+ }
56
+ // Debounced suggestions fetch (Places API New)
57
+ useEffect(() => {
58
+ if (suppressFetchRef.current)
59
+ return;
60
+ const trimmed = (q ?? '').trim();
61
+ if (!trimmed || trimmed.length < 3) {
62
+ setItems([]);
63
+ return;
64
+ }
65
+ let cancelled = false;
66
+ const myGen = ++fetchGenRef.current;
67
+ const run = async () => {
68
+ try {
69
+ const g = await loadGoogleMaps(apiKey);
70
+ const req = {
71
+ input: trimmed,
72
+ sessionToken: tokenRef.current,
73
+ language: 'en',
74
+ region: (countryCodes?.[0] ?? 'bd').toLowerCase(),
75
+ };
76
+ if (locationRestriction)
77
+ req.locationRestriction = locationRestriction;
78
+ const { suggestions } = await g.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions(req);
79
+ if (cancelled || myGen !== fetchGenRef.current)
80
+ return;
81
+ const list = (suggestions ??
82
+ []);
83
+ const mapped = list
84
+ .slice(0, Math.max(1, limit))
85
+ .map((s) => {
86
+ const key = deriveStableKey(s);
87
+ const label = s.placePrediction?.text?.toString?.() ?? '';
88
+ return key && label ? { key, label, raw: s } : null;
89
+ })
90
+ .filter((r) => !!r);
91
+ setItems(mapped);
92
+ }
93
+ catch {
94
+ setItems([]);
95
+ }
96
+ };
97
+ const t = setTimeout(run, 250);
98
+ return () => {
99
+ cancelled = true;
100
+ clearTimeout(t);
101
+ };
102
+ // eslint-disable-next-line react-hooks/exhaustive-deps
103
+ }, [q, apiKey, countryCodes, locationRestriction, limit]);
104
+ const getLatLng = (loc) => {
105
+ if (!loc)
106
+ return null;
107
+ if (typeof loc.lat === 'function' && typeof loc.lng === 'function')
108
+ return { lat: loc.lat(), lng: loc.lng() };
109
+ if (typeof loc.lat === 'number' && typeof loc.lng === 'number')
110
+ return { lat: loc.lat, lng: loc.lng };
111
+ return null;
112
+ };
113
+ // Resolve and commit
114
+ const resolveByKey = async (key) => {
115
+ const rec = items.find((r) => r.key === key);
116
+ if (!rec)
117
+ return;
118
+ suppressFetchRef.current = true;
119
+ // Show EXACT suggestion text immediately, and keep it
120
+ const label = rec.label;
121
+ setQ(label);
122
+ setItems([]);
123
+ try {
124
+ const g = await loadGoogleMaps(apiKey);
125
+ const pp = rec.raw.placePrediction;
126
+ if (!pp)
127
+ return;
128
+ const place = pp.toPlace();
129
+ await place.fetchFields({
130
+ fields: ['location'], // we only need lat/lng; do NOT fetch formattedAddress
131
+ sessionToken: tokenRef.current ?? undefined,
132
+ });
133
+ const ll = getLatLng(place.location);
134
+ // Always commit the SUGGESTION TEXT to the form; populate latLng when available
135
+ onChange(label, ll ?? undefined);
136
+ // fresh session
137
+ tokenRef.current = new g.maps.places.AutocompleteSessionToken();
138
+ }
139
+ finally {
140
+ suppressFetchRef.current = false;
141
+ }
142
+ };
143
+ // Normalize HeroUI selection payload
144
+ function extractKey(sel) {
145
+ if (sel === null || sel === undefined || sel === 'all')
146
+ return null;
147
+ if (sel instanceof Set) {
148
+ const first = sel.values().next().value;
149
+ return first != null ? String(first) : null;
150
+ }
151
+ return String(sel);
152
+ }
153
+ return (_jsx("div", { className: className, children: _jsx(Autocomplete, { "aria-label": label ?? name, label: label, labelPlacement: "outside", placeholder: "Type address", name: name, isRequired: required, description: description, variant: "bordered", classNames: classNames, isDisabled: disabled, items: items, inputValue: q, isClearable: true, onClear: () => {
154
+ manualClearRef.current = true;
155
+ setQ('');
156
+ setItems([]);
157
+ onChange('', undefined);
158
+ }, onKeyDown: (e) => {
159
+ if ((e.key === 'Backspace' || e.key === 'Delete') && q.length <= 1) {
160
+ manualClearRef.current = true;
161
+ }
162
+ }, onInputChange: (text) => {
163
+ if (text == null)
164
+ return;
165
+ const trimmed = text.trim();
166
+ // Ignore blur-initiated clears unless it was a real manual clear
167
+ if (trimmed === '' && !manualClearRef.current)
168
+ return;
169
+ if (trimmed === '' && manualClearRef.current) {
170
+ manualClearRef.current = false;
171
+ setQ('');
172
+ setItems([]);
173
+ return;
174
+ }
175
+ if (text !== q)
176
+ setQ(text);
177
+ }, onSelectionChange: (sel) => {
178
+ const key = extractKey(sel);
179
+ if (key)
180
+ void resolveByKey(key);
181
+ }, children: (item) => (_jsx(AutocompleteItem, { textValue: item.label, children: item.label }, item.key)) }) }));
182
+ }
@@ -0,0 +1 @@
1
+ export declare function AdminApp(): import("react/jsx-runtime").JSX.Element;