nl-d365boilerplate-vite 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +22 -0
- package/README.md +75 -0
- package/bin/cli.js +84 -0
- package/components.json +22 -0
- package/eslint.config.js +23 -0
- package/getToken.js +197 -0
- package/index.html +30 -0
- package/package.json +69 -0
- package/src/App.tsx +28 -0
- package/src/assets/images/novalogica-logo.svg +24 -0
- package/src/components/nl-header.tsx +165 -0
- package/src/components/page-layout.tsx +78 -0
- package/src/components/page-render.tsx +16 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/button.tsx +165 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +156 -0
- package/src/components/ui/input.tsx +23 -0
- package/src/components/ui/label.tsx +23 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/theme-toggle.tsx +28 -0
- package/src/config/pages.config.ts +34 -0
- package/src/contexts/dataverse-context.tsx +12 -0
- package/src/contexts/navigation-context.tsx +14 -0
- package/src/hooks/useAccounts.ts +194 -0
- package/src/hooks/useDataverse.ts +11 -0
- package/src/hooks/useNavigation.ts +41 -0
- package/src/index.css +147 -0
- package/src/lib/nav-items.ts +25 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +12 -0
- package/src/pages/Demo.tsx +465 -0
- package/src/pages/Documentation.tsx +850 -0
- package/src/pages/Home.tsx +132 -0
- package/src/pages/index.ts +4 -0
- package/src/providers/dataverse-provider.tsx +81 -0
- package/src/providers/navigation-provider.tsx +33 -0
- package/src/providers/theme-provider.tsx +92 -0
- package/src/public/novalogica-logo.svg +24 -0
- package/tsconfig.app.json +32 -0
- package/tsconfig.json +17 -0
- package/tsconfig.node.json +26 -0
- package/vite.config.ts +26 -0
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
} from "@/components/ui/card";
|
|
10
|
+
import { Badge } from "@/components/ui/badge";
|
|
11
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
12
|
+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
13
|
+
import {
|
|
14
|
+
ArrowLeft01Icon,
|
|
15
|
+
Copy01Icon,
|
|
16
|
+
Tick01Icon,
|
|
17
|
+
File01Icon,
|
|
18
|
+
Navigation01Icon,
|
|
19
|
+
Database01Icon,
|
|
20
|
+
PuzzleIcon,
|
|
21
|
+
Add01Icon,
|
|
22
|
+
Delete02Icon,
|
|
23
|
+
PencilEdit01Icon,
|
|
24
|
+
PlayIcon,
|
|
25
|
+
BulbIcon,
|
|
26
|
+
CodeIcon,
|
|
27
|
+
BookOpen01Icon,
|
|
28
|
+
FolderOpenIcon,
|
|
29
|
+
} from "hugeicons-react";
|
|
30
|
+
import { Separator } from "@/components/ui/separator";
|
|
31
|
+
import { useNavigation } from "@/hooks/useNavigation";
|
|
32
|
+
import { PAGES } from "@/config/pages.config";
|
|
33
|
+
import { PageLayout } from "@/components/page-layout";
|
|
34
|
+
|
|
35
|
+
interface CodeBlockProps {
|
|
36
|
+
code: string;
|
|
37
|
+
title?: string;
|
|
38
|
+
onCopy: (code: string, id: string) => void;
|
|
39
|
+
isCopied: boolean;
|
|
40
|
+
id: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const CodeBlock = ({ code, title, onCopy, isCopied, id }: CodeBlockProps) => (
|
|
44
|
+
<div className="relative group">
|
|
45
|
+
<div className="bg-muted/30 rounded-lg border">
|
|
46
|
+
{title && (
|
|
47
|
+
<div className="px-4 pt-3 pb-2 border-b">
|
|
48
|
+
<span className="text-sm font-medium text-muted-foreground">
|
|
49
|
+
{title}
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
<div className="p-4 font-mono text-sm overflow-x-auto">
|
|
54
|
+
<pre className="whitespace-pre-wrap">{code}</pre>
|
|
55
|
+
</div>
|
|
56
|
+
<Button
|
|
57
|
+
size="icon"
|
|
58
|
+
variant="ghost"
|
|
59
|
+
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity h-8 w-8"
|
|
60
|
+
onClick={() => onCopy(code, id)}
|
|
61
|
+
>
|
|
62
|
+
{isCopied ? (
|
|
63
|
+
<Tick01Icon className="w-4 h-4 text-green-500" />
|
|
64
|
+
) : (
|
|
65
|
+
<Copy01Icon className="w-4 h-4" />
|
|
66
|
+
)}
|
|
67
|
+
</Button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
interface StepCardProps {
|
|
73
|
+
number: number;
|
|
74
|
+
title: string;
|
|
75
|
+
children: React.ReactNode;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const StepCard = ({ number, title, children }: StepCardProps) => (
|
|
79
|
+
<Card>
|
|
80
|
+
<CardHeader className="pb-3">
|
|
81
|
+
<div className="flex items-center gap-3">
|
|
82
|
+
<div className="flex items-center justify-center w-7 h-7 rounded-full bg-primary text-primary-foreground text-sm font-semibold">
|
|
83
|
+
{number}
|
|
84
|
+
</div>
|
|
85
|
+
<CardTitle className="text-base">{title}</CardTitle>
|
|
86
|
+
</div>
|
|
87
|
+
</CardHeader>
|
|
88
|
+
<CardContent>{children}</CardContent>
|
|
89
|
+
</Card>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const Documentation = () => {
|
|
93
|
+
const [copiedCode, setCopiedCode] = useState("");
|
|
94
|
+
const { navigateTo } = useNavigation();
|
|
95
|
+
|
|
96
|
+
const copyCode = (code: string, id: string) => {
|
|
97
|
+
navigator.clipboard.writeText(code);
|
|
98
|
+
setCopiedCode(id);
|
|
99
|
+
setTimeout(() => setCopiedCode(""), 2000);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<PageLayout
|
|
104
|
+
title="Documentation"
|
|
105
|
+
description="Learn how to build with this boilerplate"
|
|
106
|
+
icon={BookOpen01Icon}
|
|
107
|
+
toolbar={
|
|
108
|
+
<div className="flex gap-2">
|
|
109
|
+
<Button
|
|
110
|
+
variant="outline"
|
|
111
|
+
size="icon"
|
|
112
|
+
onClick={() => navigateTo(PAGES.home)}
|
|
113
|
+
>
|
|
114
|
+
<ArrowLeft01Icon className="w-4 h-4" />
|
|
115
|
+
</Button>
|
|
116
|
+
<Button onClick={() => navigateTo(PAGES.demo)} className="gap-2">
|
|
117
|
+
<PlayIcon className="w-4 h-4" />
|
|
118
|
+
Try Demo
|
|
119
|
+
</Button>
|
|
120
|
+
</div>
|
|
121
|
+
}
|
|
122
|
+
>
|
|
123
|
+
<div className="space-y-6">
|
|
124
|
+
<Tabs defaultValue="structure" className="w-full">
|
|
125
|
+
<TabsList className="grid w-full grid-cols-5">
|
|
126
|
+
<TabsTrigger value="structure" className="gap-2">
|
|
127
|
+
<FolderOpenIcon className="w-4 h-4" />
|
|
128
|
+
Structure
|
|
129
|
+
</TabsTrigger>
|
|
130
|
+
<TabsTrigger value="pages" className="gap-2">
|
|
131
|
+
<File01Icon className="w-4 h-4" />
|
|
132
|
+
Pages
|
|
133
|
+
</TabsTrigger>
|
|
134
|
+
<TabsTrigger value="navigation" className="gap-2">
|
|
135
|
+
<Navigation01Icon className="w-4 h-4" />
|
|
136
|
+
Navigation
|
|
137
|
+
</TabsTrigger>
|
|
138
|
+
<TabsTrigger value="dataverse" className="gap-2">
|
|
139
|
+
<Database01Icon className="w-4 h-4" />
|
|
140
|
+
Dataverse
|
|
141
|
+
</TabsTrigger>
|
|
142
|
+
<TabsTrigger value="components" className="gap-2">
|
|
143
|
+
<PuzzleIcon className="w-4 h-4" />
|
|
144
|
+
Components
|
|
145
|
+
</TabsTrigger>
|
|
146
|
+
</TabsList>
|
|
147
|
+
|
|
148
|
+
{/* Project Structure */}
|
|
149
|
+
<TabsContent value="structure" className="space-y-6 mt-6">
|
|
150
|
+
<Card>
|
|
151
|
+
<CardHeader>
|
|
152
|
+
<CardTitle className="flex items-center gap-2">
|
|
153
|
+
<FolderOpenIcon className="w-5 h-5 text-primary" />
|
|
154
|
+
Project Structure
|
|
155
|
+
</CardTitle>
|
|
156
|
+
<CardDescription>
|
|
157
|
+
Overview of the boilerplate folder organization
|
|
158
|
+
</CardDescription>
|
|
159
|
+
</CardHeader>
|
|
160
|
+
<CardContent>
|
|
161
|
+
<CodeBlock
|
|
162
|
+
id="structure"
|
|
163
|
+
code={`src/
|
|
164
|
+
├── assets/
|
|
165
|
+
│ └── images/ # Static images and SVGs
|
|
166
|
+
├── components/
|
|
167
|
+
│ ├── ui/ # shadcn/ui components
|
|
168
|
+
│ │ ├── alert.tsx
|
|
169
|
+
│ │ ├── badge.tsx
|
|
170
|
+
│ │ ├── button.tsx
|
|
171
|
+
│ │ ├── card.tsx
|
|
172
|
+
│ │ ├── dialog.tsx
|
|
173
|
+
│ │ ├── input.tsx
|
|
174
|
+
│ │ ├── label.tsx
|
|
175
|
+
│ │ ├── separator.tsx
|
|
176
|
+
│ │ ├── table.tsx
|
|
177
|
+
│ │ ├── tabs.tsx
|
|
178
|
+
│ │ └── theme-toggle.tsx
|
|
179
|
+
│ ├── nl-header.tsx # Application header
|
|
180
|
+
│ ├── page-layout.tsx # Reusable page layout
|
|
181
|
+
│ └── page-render.tsx # Dynamic page renderer
|
|
182
|
+
├── config/
|
|
183
|
+
│ └── pages.config.ts # Page registration
|
|
184
|
+
├── contexts/
|
|
185
|
+
│ ├── dataverse-context.tsx
|
|
186
|
+
│ └── navigation-context.tsx
|
|
187
|
+
├── hooks/
|
|
188
|
+
│ ├── useAccounts.ts # Entity-specific hook example
|
|
189
|
+
│ ├── useDataverse.ts # Dataverse API hook
|
|
190
|
+
│ └── useNavigation.ts # Navigation hook
|
|
191
|
+
├── lib/
|
|
192
|
+
│ ├── nav-items.ts # Navigation menu items
|
|
193
|
+
│ └── utils.ts # Utility functions (cn)
|
|
194
|
+
├── pages/
|
|
195
|
+
│ ├── Demo.tsx
|
|
196
|
+
│ ├── Documentation.tsx
|
|
197
|
+
│ ├── Home.tsx
|
|
198
|
+
│ └── index.ts # Page exports
|
|
199
|
+
├── providers/
|
|
200
|
+
│ ├── dataverse-provider.tsx
|
|
201
|
+
│ ├── navigation-provider.tsx
|
|
202
|
+
│ └── theme-provider.tsx
|
|
203
|
+
├── App.tsx # Root component
|
|
204
|
+
├── index.css # Global styles
|
|
205
|
+
└── main.tsx # Entry point`}
|
|
206
|
+
onCopy={copyCode}
|
|
207
|
+
isCopied={copiedCode === "structure"}
|
|
208
|
+
/>
|
|
209
|
+
</CardContent>
|
|
210
|
+
</Card>
|
|
211
|
+
|
|
212
|
+
<Card>
|
|
213
|
+
<CardHeader>
|
|
214
|
+
<CardTitle>Tech Stack</CardTitle>
|
|
215
|
+
<CardDescription>
|
|
216
|
+
Technologies used in this boilerplate
|
|
217
|
+
</CardDescription>
|
|
218
|
+
</CardHeader>
|
|
219
|
+
<CardContent>
|
|
220
|
+
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
221
|
+
{[
|
|
222
|
+
{ name: "Vite 7", desc: "Build tool & dev server" },
|
|
223
|
+
{ name: "React 19", desc: "UI library" },
|
|
224
|
+
{ name: "TypeScript", desc: "Type safety" },
|
|
225
|
+
{ name: "Tailwind CSS 4", desc: "Utility-first CSS" },
|
|
226
|
+
{ name: "shadcn/ui", desc: "UI components" },
|
|
227
|
+
{ name: "dynamics-web-api", desc: "Dataverse integration" },
|
|
228
|
+
].map((tech) => (
|
|
229
|
+
<div
|
|
230
|
+
key={tech.name}
|
|
231
|
+
className="p-3 rounded-lg border bg-muted/30"
|
|
232
|
+
>
|
|
233
|
+
<p className="font-medium text-sm">{tech.name}</p>
|
|
234
|
+
<p className="text-xs text-muted-foreground">
|
|
235
|
+
{tech.desc}
|
|
236
|
+
</p>
|
|
237
|
+
</div>
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
240
|
+
</CardContent>
|
|
241
|
+
</Card>
|
|
242
|
+
</TabsContent>
|
|
243
|
+
|
|
244
|
+
{/* Pages Management */}
|
|
245
|
+
<TabsContent value="pages" className="space-y-6 mt-6">
|
|
246
|
+
<Card>
|
|
247
|
+
<CardHeader>
|
|
248
|
+
<CardTitle className="flex items-center gap-2">
|
|
249
|
+
<Add01Icon className="w-5 h-5 text-green-600" />
|
|
250
|
+
Creating New Pages
|
|
251
|
+
</CardTitle>
|
|
252
|
+
<CardDescription>
|
|
253
|
+
Add new pages to your application in three steps
|
|
254
|
+
</CardDescription>
|
|
255
|
+
</CardHeader>
|
|
256
|
+
<CardContent className="space-y-6">
|
|
257
|
+
<div className="grid gap-4">
|
|
258
|
+
<StepCard number={1} title="Create the Page Component">
|
|
259
|
+
<div className="space-y-3">
|
|
260
|
+
<p className="text-sm text-muted-foreground">
|
|
261
|
+
Create a new file in{" "}
|
|
262
|
+
<code className="bg-muted px-1.5 py-0.5 rounded text-xs">
|
|
263
|
+
src/pages/
|
|
264
|
+
</code>
|
|
265
|
+
</p>
|
|
266
|
+
<CodeBlock
|
|
267
|
+
id="new-page"
|
|
268
|
+
title="src/pages/Contacts.tsx"
|
|
269
|
+
code={`import { PageLayout } from "@/components/page-layout";
|
|
270
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
271
|
+
import { UserIcon } from "hugeicons-react";
|
|
272
|
+
|
|
273
|
+
const Contacts = () => {
|
|
274
|
+
return (
|
|
275
|
+
<PageLayout
|
|
276
|
+
title="Contacts"
|
|
277
|
+
description="Manage your contacts"
|
|
278
|
+
icon={UserIcon}
|
|
279
|
+
>
|
|
280
|
+
<Card>
|
|
281
|
+
<CardHeader>
|
|
282
|
+
<CardTitle>Contact List</CardTitle>
|
|
283
|
+
</CardHeader>
|
|
284
|
+
<CardContent>
|
|
285
|
+
<p>Your contacts content goes here...</p>
|
|
286
|
+
</CardContent>
|
|
287
|
+
</Card>
|
|
288
|
+
</PageLayout>
|
|
289
|
+
);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
export default Contacts;`}
|
|
293
|
+
onCopy={copyCode}
|
|
294
|
+
isCopied={copiedCode === "new-page"}
|
|
295
|
+
/>
|
|
296
|
+
</div>
|
|
297
|
+
</StepCard>
|
|
298
|
+
|
|
299
|
+
<StepCard number={2} title="Export from Index">
|
|
300
|
+
<div className="space-y-3">
|
|
301
|
+
<p className="text-sm text-muted-foreground">
|
|
302
|
+
Add your page export to{" "}
|
|
303
|
+
<code className="bg-muted px-1.5 py-0.5 rounded text-xs">
|
|
304
|
+
src/pages/index.ts
|
|
305
|
+
</code>
|
|
306
|
+
</p>
|
|
307
|
+
<CodeBlock
|
|
308
|
+
id="export-page"
|
|
309
|
+
title="src/pages/index.ts"
|
|
310
|
+
code={`import Home from "./Home";
|
|
311
|
+
import Documentation from "./Documentation";
|
|
312
|
+
import Demo from "./Demo";
|
|
313
|
+
import Contacts from "./Contacts"; // Add this
|
|
314
|
+
|
|
315
|
+
export { Home, Documentation, Demo, Contacts };`}
|
|
316
|
+
onCopy={copyCode}
|
|
317
|
+
isCopied={copiedCode === "export-page"}
|
|
318
|
+
/>
|
|
319
|
+
</div>
|
|
320
|
+
</StepCard>
|
|
321
|
+
|
|
322
|
+
<StepCard number={3} title="Register in Pages Config">
|
|
323
|
+
<div className="space-y-3">
|
|
324
|
+
<p className="text-sm text-muted-foreground">
|
|
325
|
+
Add your page to{" "}
|
|
326
|
+
<code className="bg-muted px-1.5 py-0.5 rounded text-xs">
|
|
327
|
+
src/config/pages.config.ts
|
|
328
|
+
</code>
|
|
329
|
+
</p>
|
|
330
|
+
<CodeBlock
|
|
331
|
+
id="config-page"
|
|
332
|
+
title="src/config/pages.config.ts"
|
|
333
|
+
code={`import { Home, Documentation, Demo, Contacts } from "@/pages";
|
|
334
|
+
import type { ComponentType } from "react";
|
|
335
|
+
|
|
336
|
+
export interface PageConfig {
|
|
337
|
+
id: string;
|
|
338
|
+
title: string;
|
|
339
|
+
component: ComponentType;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export const PAGES: Record<string, PageConfig> = {
|
|
343
|
+
home: {
|
|
344
|
+
id: "home",
|
|
345
|
+
title: "Home",
|
|
346
|
+
component: Home,
|
|
347
|
+
},
|
|
348
|
+
documentation: {
|
|
349
|
+
id: "documentation",
|
|
350
|
+
title: "Documentation",
|
|
351
|
+
component: Documentation,
|
|
352
|
+
},
|
|
353
|
+
demo: {
|
|
354
|
+
id: "demo",
|
|
355
|
+
title: "Demo",
|
|
356
|
+
component: Demo,
|
|
357
|
+
},
|
|
358
|
+
contacts: { // Add your new page
|
|
359
|
+
id: "contacts",
|
|
360
|
+
title: "Contacts",
|
|
361
|
+
component: Contacts,
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
export const DEFAULT_PAGE = PAGES.home;`}
|
|
366
|
+
onCopy={copyCode}
|
|
367
|
+
isCopied={copiedCode === "config-page"}
|
|
368
|
+
/>
|
|
369
|
+
</div>
|
|
370
|
+
</StepCard>
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
<Alert>
|
|
374
|
+
<BulbIcon className="h-4 w-4" />
|
|
375
|
+
<AlertTitle>Done!</AlertTitle>
|
|
376
|
+
<AlertDescription>
|
|
377
|
+
Navigate to your new page using{" "}
|
|
378
|
+
<code className="bg-muted px-1.5 py-0.5 rounded text-xs">
|
|
379
|
+
navigateTo(PAGES.contacts)
|
|
380
|
+
</code>
|
|
381
|
+
</AlertDescription>
|
|
382
|
+
</Alert>
|
|
383
|
+
</CardContent>
|
|
384
|
+
</Card>
|
|
385
|
+
|
|
386
|
+
<div className="grid md:grid-cols-2 gap-6">
|
|
387
|
+
<Card>
|
|
388
|
+
<CardHeader>
|
|
389
|
+
<CardTitle className="flex items-center gap-2">
|
|
390
|
+
<PencilEdit01Icon className="w-5 h-5 text-blue-600" />
|
|
391
|
+
Updating Pages
|
|
392
|
+
</CardTitle>
|
|
393
|
+
</CardHeader>
|
|
394
|
+
<CardContent>
|
|
395
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
396
|
+
<li className="flex items-center gap-2">
|
|
397
|
+
<div className="w-1.5 h-1.5 bg-primary rounded-full" />
|
|
398
|
+
Edit the component file directly
|
|
399
|
+
</li>
|
|
400
|
+
<li className="flex items-center gap-2">
|
|
401
|
+
<div className="w-1.5 h-1.5 bg-primary rounded-full" />
|
|
402
|
+
Update title in pages.config.ts
|
|
403
|
+
</li>
|
|
404
|
+
<li className="flex items-center gap-2">
|
|
405
|
+
<div className="w-1.5 h-1.5 bg-primary rounded-full" />
|
|
406
|
+
Changes reflect with hot reload
|
|
407
|
+
</li>
|
|
408
|
+
</ul>
|
|
409
|
+
</CardContent>
|
|
410
|
+
</Card>
|
|
411
|
+
|
|
412
|
+
<Card>
|
|
413
|
+
<CardHeader>
|
|
414
|
+
<CardTitle className="flex items-center gap-2">
|
|
415
|
+
<Delete02Icon className="w-5 h-5 text-red-600" />
|
|
416
|
+
Removing Pages
|
|
417
|
+
</CardTitle>
|
|
418
|
+
</CardHeader>
|
|
419
|
+
<CardContent>
|
|
420
|
+
<ul className="space-y-2 text-sm text-muted-foreground">
|
|
421
|
+
<li className="flex items-center gap-2">
|
|
422
|
+
<div className="w-1.5 h-1.5 bg-destructive rounded-full" />
|
|
423
|
+
Delete the component file
|
|
424
|
+
</li>
|
|
425
|
+
<li className="flex items-center gap-2">
|
|
426
|
+
<div className="w-1.5 h-1.5 bg-destructive rounded-full" />
|
|
427
|
+
Remove from pages/index.ts
|
|
428
|
+
</li>
|
|
429
|
+
<li className="flex items-center gap-2">
|
|
430
|
+
<div className="w-1.5 h-1.5 bg-destructive rounded-full" />
|
|
431
|
+
Remove from pages.config.ts
|
|
432
|
+
</li>
|
|
433
|
+
</ul>
|
|
434
|
+
</CardContent>
|
|
435
|
+
</Card>
|
|
436
|
+
</div>
|
|
437
|
+
</TabsContent>
|
|
438
|
+
|
|
439
|
+
{/* Navigation System */}
|
|
440
|
+
<TabsContent value="navigation" className="space-y-6 mt-6">
|
|
441
|
+
<Card>
|
|
442
|
+
<CardHeader>
|
|
443
|
+
<CardTitle>Navigation Hook</CardTitle>
|
|
444
|
+
<CardDescription>
|
|
445
|
+
Use the useNavigation hook to navigate between pages
|
|
446
|
+
</CardDescription>
|
|
447
|
+
</CardHeader>
|
|
448
|
+
<CardContent className="space-y-6">
|
|
449
|
+
<div className="space-y-4">
|
|
450
|
+
<h4 className="font-semibold text-sm">Basic Usage</h4>
|
|
451
|
+
<CodeBlock
|
|
452
|
+
id="nav-basic"
|
|
453
|
+
code={`import { useNavigation } from "@/hooks/useNavigation";
|
|
454
|
+
import { PAGES } from "@/config/pages.config";
|
|
455
|
+
|
|
456
|
+
const MyComponent = () => {
|
|
457
|
+
const { currentPage, navigateTo, canGoBack, goBack } = useNavigation();
|
|
458
|
+
|
|
459
|
+
return (
|
|
460
|
+
<div>
|
|
461
|
+
<p>Current: {currentPage.title}</p>
|
|
462
|
+
|
|
463
|
+
{/* Navigate using PAGES config */}
|
|
464
|
+
<Button onClick={() => navigateTo(PAGES.demo)}>
|
|
465
|
+
Go to Demo
|
|
466
|
+
</Button>
|
|
467
|
+
|
|
468
|
+
{/* Go back to previous page */}
|
|
469
|
+
{canGoBack && (
|
|
470
|
+
<Button onClick={goBack}>Go Back</Button>
|
|
471
|
+
)}
|
|
472
|
+
</div>
|
|
473
|
+
);
|
|
474
|
+
};`}
|
|
475
|
+
onCopy={copyCode}
|
|
476
|
+
isCopied={copiedCode === "nav-basic"}
|
|
477
|
+
/>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
<Separator />
|
|
481
|
+
|
|
482
|
+
<div className="space-y-4">
|
|
483
|
+
<h4 className="font-semibold text-sm">Hook API</h4>
|
|
484
|
+
<CodeBlock
|
|
485
|
+
id="nav-api"
|
|
486
|
+
code={`const {
|
|
487
|
+
currentPage, // Current PageConfig object
|
|
488
|
+
navigateTo, // Navigate to a PageConfig
|
|
489
|
+
navigate, // Navigate by page id string
|
|
490
|
+
canGoBack, // Boolean if history exists
|
|
491
|
+
goBack, // Navigate to previous page
|
|
492
|
+
} = useNavigation();
|
|
493
|
+
|
|
494
|
+
// Examples:
|
|
495
|
+
navigateTo(PAGES.home); // Using PageConfig`}
|
|
496
|
+
onCopy={copyCode}
|
|
497
|
+
isCopied={copiedCode === "nav-api"}
|
|
498
|
+
/>
|
|
499
|
+
</div>
|
|
500
|
+
</CardContent>
|
|
501
|
+
</Card>
|
|
502
|
+
|
|
503
|
+
<Card>
|
|
504
|
+
<CardHeader>
|
|
505
|
+
<CardTitle>App Structure</CardTitle>
|
|
506
|
+
<CardDescription>
|
|
507
|
+
How providers are organized in App.tsx
|
|
508
|
+
</CardDescription>
|
|
509
|
+
</CardHeader>
|
|
510
|
+
<CardContent>
|
|
511
|
+
<CodeBlock
|
|
512
|
+
id="app-structure"
|
|
513
|
+
title="src/App.tsx"
|
|
514
|
+
code={`import { ThemeProvider } from "@/providers/theme-provider";
|
|
515
|
+
import { NavigationProvider } from "./providers/navigation-provider";
|
|
516
|
+
import { PageRenderer } from "./components/page-render";
|
|
517
|
+
import NlHeader from "./components/nl-header";
|
|
518
|
+
import { DataverseProvider } from "./providers/dataverse-provider";
|
|
519
|
+
|
|
520
|
+
function App() {
|
|
521
|
+
return (
|
|
522
|
+
<ThemeProvider defaultTheme="light" storageKey="novalogica-ui-theme">
|
|
523
|
+
<DataverseProvider>
|
|
524
|
+
<NavigationProvider>
|
|
525
|
+
<div className="flex min-h-screen w-full flex-col bg-background">
|
|
526
|
+
<NlHeader />
|
|
527
|
+
<main className="flex-1 flex flex-col">
|
|
528
|
+
<PageRenderer />
|
|
529
|
+
</main>
|
|
530
|
+
</div>
|
|
531
|
+
</NavigationProvider>
|
|
532
|
+
</DataverseProvider>
|
|
533
|
+
</ThemeProvider>
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export default App;`}
|
|
538
|
+
onCopy={copyCode}
|
|
539
|
+
isCopied={copiedCode === "app-structure"}
|
|
540
|
+
/>
|
|
541
|
+
</CardContent>
|
|
542
|
+
</Card>
|
|
543
|
+
</TabsContent>
|
|
544
|
+
|
|
545
|
+
{/* Dataverse API */}
|
|
546
|
+
<TabsContent value="dataverse" className="space-y-6 mt-6">
|
|
547
|
+
<Card>
|
|
548
|
+
<CardHeader>
|
|
549
|
+
<CardTitle>Using the Dataverse Hook</CardTitle>
|
|
550
|
+
<CardDescription>
|
|
551
|
+
Connect to Dataverse and perform CRUD operations
|
|
552
|
+
</CardDescription>
|
|
553
|
+
</CardHeader>
|
|
554
|
+
<CardContent className="space-y-6">
|
|
555
|
+
<div className="space-y-4">
|
|
556
|
+
<h4 className="font-semibold text-sm">Basic Connection</h4>
|
|
557
|
+
<CodeBlock
|
|
558
|
+
id="dataverse-basic"
|
|
559
|
+
code={`import { useDataverse } from "@/hooks/useDataverse";
|
|
560
|
+
|
|
561
|
+
const MyComponent = () => {
|
|
562
|
+
const { api, isReady, error } = useDataverse();
|
|
563
|
+
|
|
564
|
+
if (!isReady) return <div>Connecting...</div>;
|
|
565
|
+
if (error) return <div>Error: {error}</div>;
|
|
566
|
+
|
|
567
|
+
return <div>Connected to Dataverse!</div>;
|
|
568
|
+
};`}
|
|
569
|
+
onCopy={copyCode}
|
|
570
|
+
isCopied={copiedCode === "dataverse-basic"}
|
|
571
|
+
/>
|
|
572
|
+
</div>
|
|
573
|
+
|
|
574
|
+
<Separator />
|
|
575
|
+
|
|
576
|
+
<div className="space-y-4">
|
|
577
|
+
<h4 className="font-semibold text-sm">CRUD Operations</h4>
|
|
578
|
+
<div className="grid gap-4">
|
|
579
|
+
<div>
|
|
580
|
+
<Badge variant="outline" className="mb-2">
|
|
581
|
+
READ
|
|
582
|
+
</Badge>
|
|
583
|
+
<CodeBlock
|
|
584
|
+
id="dataverse-read"
|
|
585
|
+
code={`// Multiple records
|
|
586
|
+
const response = await api.retrieveMultiple({
|
|
587
|
+
collection: "accounts",
|
|
588
|
+
select: ["accountid", "name", "emailaddress1"],
|
|
589
|
+
filter: "statecode eq 0",
|
|
590
|
+
orderBy: ["name asc"],
|
|
591
|
+
maxPageSize: 10
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// Single record
|
|
595
|
+
const account = await api.retrieve({
|
|
596
|
+
collection: "accounts",
|
|
597
|
+
key: "account-guid-here",
|
|
598
|
+
select: ["accountid", "name"]
|
|
599
|
+
});`}
|
|
600
|
+
onCopy={copyCode}
|
|
601
|
+
isCopied={copiedCode === "dataverse-read"}
|
|
602
|
+
/>
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
<div>
|
|
606
|
+
<Badge variant="outline" className="mb-2">
|
|
607
|
+
CREATE
|
|
608
|
+
</Badge>
|
|
609
|
+
<CodeBlock
|
|
610
|
+
id="dataverse-create"
|
|
611
|
+
code={`const newId = await api.create({
|
|
612
|
+
collection: "accounts",
|
|
613
|
+
data: {
|
|
614
|
+
name: "New Account",
|
|
615
|
+
emailaddress1: "email@example.com"
|
|
616
|
+
}
|
|
617
|
+
});`}
|
|
618
|
+
onCopy={copyCode}
|
|
619
|
+
isCopied={copiedCode === "dataverse-create"}
|
|
620
|
+
/>
|
|
621
|
+
</div>
|
|
622
|
+
|
|
623
|
+
<div>
|
|
624
|
+
<Badge variant="outline" className="mb-2">
|
|
625
|
+
UPDATE
|
|
626
|
+
</Badge>
|
|
627
|
+
<CodeBlock
|
|
628
|
+
id="dataverse-update"
|
|
629
|
+
code={`await api.update({
|
|
630
|
+
collection: "accounts",
|
|
631
|
+
key: "account-guid-here",
|
|
632
|
+
data: { name: "Updated Name" }
|
|
633
|
+
});`}
|
|
634
|
+
onCopy={copyCode}
|
|
635
|
+
isCopied={copiedCode === "dataverse-update"}
|
|
636
|
+
/>
|
|
637
|
+
</div>
|
|
638
|
+
|
|
639
|
+
<div>
|
|
640
|
+
<Badge variant="outline" className="mb-2">
|
|
641
|
+
DELETE
|
|
642
|
+
</Badge>
|
|
643
|
+
<CodeBlock
|
|
644
|
+
id="dataverse-delete"
|
|
645
|
+
code={`await api.deleteRecord({
|
|
646
|
+
collection: "accounts",
|
|
647
|
+
key: "account-guid-here"
|
|
648
|
+
});`}
|
|
649
|
+
onCopy={copyCode}
|
|
650
|
+
isCopied={copiedCode === "dataverse-delete"}
|
|
651
|
+
/>
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
654
|
+
</div>
|
|
655
|
+
</CardContent>
|
|
656
|
+
</Card>
|
|
657
|
+
|
|
658
|
+
<Card>
|
|
659
|
+
<CardHeader>
|
|
660
|
+
<CardTitle>Entity Hook Pattern</CardTitle>
|
|
661
|
+
<CardDescription>
|
|
662
|
+
Create reusable hooks for specific entities (see
|
|
663
|
+
hooks/useAccounts.ts)
|
|
664
|
+
</CardDescription>
|
|
665
|
+
</CardHeader>
|
|
666
|
+
<CardContent>
|
|
667
|
+
<CodeBlock
|
|
668
|
+
id="entity-hook"
|
|
669
|
+
title="src/hooks/useContacts.ts"
|
|
670
|
+
code={`import { useState, useCallback } from "react";
|
|
671
|
+
import { useDataverse } from "@/hooks/useDataverse";
|
|
672
|
+
|
|
673
|
+
export interface Contact {
|
|
674
|
+
contactid?: string;
|
|
675
|
+
fullname?: string;
|
|
676
|
+
emailaddress1?: string;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export const useContacts = () => {
|
|
680
|
+
const { api, isReady } = useDataverse();
|
|
681
|
+
const [contacts, setContacts] = useState<Contact[]>([]);
|
|
682
|
+
const [loading, setLoading] = useState(false);
|
|
683
|
+
const [error, setError] = useState<string | null>(null);
|
|
684
|
+
|
|
685
|
+
const fetchContacts = useCallback(async () => {
|
|
686
|
+
if (!api || !isReady) return;
|
|
687
|
+
|
|
688
|
+
setLoading(true);
|
|
689
|
+
setError(null);
|
|
690
|
+
|
|
691
|
+
try {
|
|
692
|
+
const response = await api.retrieveMultiple({
|
|
693
|
+
collection: "contacts",
|
|
694
|
+
select: ["contactid", "fullname", "emailaddress1"],
|
|
695
|
+
filter: "statecode eq 0"
|
|
696
|
+
});
|
|
697
|
+
setContacts(response?.value || []);
|
|
698
|
+
} catch (err) {
|
|
699
|
+
setError(err instanceof Error ? err.message : "Failed to fetch");
|
|
700
|
+
} finally {
|
|
701
|
+
setLoading(false);
|
|
702
|
+
}
|
|
703
|
+
}, [api, isReady]);
|
|
704
|
+
|
|
705
|
+
return { contacts, loading, error, fetchContacts, isReady };
|
|
706
|
+
};`}
|
|
707
|
+
onCopy={copyCode}
|
|
708
|
+
isCopied={copiedCode === "entity-hook"}
|
|
709
|
+
/>
|
|
710
|
+
</CardContent>
|
|
711
|
+
</Card>
|
|
712
|
+
</TabsContent>
|
|
713
|
+
|
|
714
|
+
{/* UI Components */}
|
|
715
|
+
<TabsContent value="components" className="space-y-6 mt-6">
|
|
716
|
+
<Card>
|
|
717
|
+
<CardHeader>
|
|
718
|
+
<CardTitle>Included Components</CardTitle>
|
|
719
|
+
<CardDescription>
|
|
720
|
+
shadcn/ui components available in this boilerplate
|
|
721
|
+
</CardDescription>
|
|
722
|
+
</CardHeader>
|
|
723
|
+
<CardContent>
|
|
724
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
|
725
|
+
{[
|
|
726
|
+
"Alert",
|
|
727
|
+
"Badge",
|
|
728
|
+
"Button",
|
|
729
|
+
"Card",
|
|
730
|
+
"Dialog",
|
|
731
|
+
"Input",
|
|
732
|
+
"Label",
|
|
733
|
+
"Separator",
|
|
734
|
+
"Table",
|
|
735
|
+
"Tabs",
|
|
736
|
+
"Theme Toggle",
|
|
737
|
+
].map((component) => (
|
|
738
|
+
<Badge
|
|
739
|
+
key={component}
|
|
740
|
+
variant="secondary"
|
|
741
|
+
className="justify-center py-2"
|
|
742
|
+
>
|
|
743
|
+
{component}
|
|
744
|
+
</Badge>
|
|
745
|
+
))}
|
|
746
|
+
</div>
|
|
747
|
+
</CardContent>
|
|
748
|
+
</Card>
|
|
749
|
+
|
|
750
|
+
<div className="grid md:grid-cols-2 gap-6">
|
|
751
|
+
<Card>
|
|
752
|
+
<CardHeader>
|
|
753
|
+
<CardTitle className="flex items-center gap-2">
|
|
754
|
+
<Add01Icon className="w-5 h-5 text-blue-600" />
|
|
755
|
+
Adding Components
|
|
756
|
+
</CardTitle>
|
|
757
|
+
</CardHeader>
|
|
758
|
+
<CardContent className="space-y-4">
|
|
759
|
+
<p className="text-sm text-muted-foreground">
|
|
760
|
+
Add more shadcn/ui components as needed:
|
|
761
|
+
</p>
|
|
762
|
+
<CodeBlock
|
|
763
|
+
id="add-component"
|
|
764
|
+
code={`npx shadcn@latest add select
|
|
765
|
+
npx shadcn@latest add dropdown-menu
|
|
766
|
+
npx shadcn@latest add form`}
|
|
767
|
+
onCopy={copyCode}
|
|
768
|
+
isCopied={copiedCode === "add-component"}
|
|
769
|
+
/>
|
|
770
|
+
<Button
|
|
771
|
+
variant="outline"
|
|
772
|
+
size="sm"
|
|
773
|
+
onClick={() =>
|
|
774
|
+
window.open(
|
|
775
|
+
"https://ui.shadcn.com/docs/components",
|
|
776
|
+
"_blank",
|
|
777
|
+
)
|
|
778
|
+
}
|
|
779
|
+
className="gap-2"
|
|
780
|
+
>
|
|
781
|
+
<CodeIcon className="w-4 h-4" />
|
|
782
|
+
Browse Components
|
|
783
|
+
</Button>
|
|
784
|
+
</CardContent>
|
|
785
|
+
</Card>
|
|
786
|
+
|
|
787
|
+
<Card>
|
|
788
|
+
<CardHeader>
|
|
789
|
+
<CardTitle className="flex items-center gap-2">
|
|
790
|
+
<Database01Icon className="w-5 h-5 text-green-600" />
|
|
791
|
+
DynamicsWebApi
|
|
792
|
+
</CardTitle>
|
|
793
|
+
</CardHeader>
|
|
794
|
+
<CardContent className="space-y-4">
|
|
795
|
+
<p className="text-sm text-muted-foreground">
|
|
796
|
+
This boilerplate uses DynamicsWebApi for Dataverse
|
|
797
|
+
operations.
|
|
798
|
+
</p>
|
|
799
|
+
<ul className="space-y-1 text-sm text-muted-foreground">
|
|
800
|
+
<li className="flex items-center gap-2">
|
|
801
|
+
<div className="w-1.5 h-1.5 bg-green-500 rounded-full" />
|
|
802
|
+
Full TypeScript support
|
|
803
|
+
</li>
|
|
804
|
+
<li className="flex items-center gap-2">
|
|
805
|
+
<div className="w-1.5 h-1.5 bg-green-500 rounded-full" />
|
|
806
|
+
OData query builder
|
|
807
|
+
</li>
|
|
808
|
+
<li className="flex items-center gap-2">
|
|
809
|
+
<div className="w-1.5 h-1.5 bg-green-500 rounded-full" />
|
|
810
|
+
Batch operations
|
|
811
|
+
</li>
|
|
812
|
+
</ul>
|
|
813
|
+
<Button
|
|
814
|
+
variant="outline"
|
|
815
|
+
size="sm"
|
|
816
|
+
onClick={() =>
|
|
817
|
+
window.open(
|
|
818
|
+
"https://github.com/AleksandrRogov/DynamicsWebApi",
|
|
819
|
+
"_blank",
|
|
820
|
+
)
|
|
821
|
+
}
|
|
822
|
+
className="gap-2"
|
|
823
|
+
>
|
|
824
|
+
<File01Icon className="w-4 h-4" />
|
|
825
|
+
View Docs
|
|
826
|
+
</Button>
|
|
827
|
+
</CardContent>
|
|
828
|
+
</Card>
|
|
829
|
+
</div>
|
|
830
|
+
|
|
831
|
+
<Alert>
|
|
832
|
+
<BulbIcon className="h-4 w-4" />
|
|
833
|
+
<AlertTitle>Tips</AlertTitle>
|
|
834
|
+
<AlertDescription className="space-y-1">
|
|
835
|
+
<p>
|
|
836
|
+
Use TypeScript interfaces for entity types to get better
|
|
837
|
+
IntelliSense
|
|
838
|
+
</p>
|
|
839
|
+
<p>Check the Demo page for working CRUD examples</p>
|
|
840
|
+
<p>Use PageLayout component for consistent page structure</p>
|
|
841
|
+
</AlertDescription>
|
|
842
|
+
</Alert>
|
|
843
|
+
</TabsContent>
|
|
844
|
+
</Tabs>
|
|
845
|
+
</div>
|
|
846
|
+
</PageLayout>
|
|
847
|
+
);
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
export default Documentation;
|