@lucifer91299/create-portal-app 1.0.4 → 1.0.6
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/README.md +7 -7
- package/dist/cli/index.js +254 -71
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
## Quick start
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx create-portal-app my-portal
|
|
14
|
+
npx @lucifer91299/create-portal-app my-portal
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
The CLI walks you through each choice with arrow-key menus, then generates a ready-to-run project.
|
|
@@ -31,23 +31,23 @@ npm run dev
|
|
|
31
31
|
Skip every prompt and use defaults instantly:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
npx create-portal-app my-portal --yes
|
|
34
|
+
npx @lucifer91299/create-portal-app my-portal --yes
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Override specific options while skipping the rest:
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
# Change login style and sidebar
|
|
41
|
-
npx create-portal-app my-portal --yes --login=simple --sidebar=rail
|
|
41
|
+
npx @lucifer91299/create-portal-app my-portal --yes --login=simple --sidebar=rail
|
|
42
42
|
|
|
43
43
|
# Custom brand colours
|
|
44
|
-
npx create-portal-app my-portal --yes --primary=#E11D48 --accent=#F59E0B --success=#10B981
|
|
44
|
+
npx @lucifer91299/create-portal-app my-portal --yes --primary=#E11D48 --accent=#F59E0B --success=#10B981
|
|
45
45
|
|
|
46
46
|
# Laravel backend
|
|
47
|
-
npx create-portal-app my-portal --yes --auth=laravel
|
|
47
|
+
npx @lucifer91299/create-portal-app my-portal --yes --auth=laravel
|
|
48
48
|
|
|
49
49
|
# Show all flags
|
|
50
|
-
npx create-portal-app --help
|
|
50
|
+
npx @lucifer91299/create-portal-app --help
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
---
|
|
@@ -198,7 +198,7 @@ Clean gradient card login with optional role-select splash. Best for SaaS or min
|
|
|
198
198
|
## Local development (use a local SDK build)
|
|
199
199
|
|
|
200
200
|
```bash
|
|
201
|
-
npx create-portal-app my-portal --yes --local-ui=../../packages/ui
|
|
201
|
+
npx @lucifer91299/create-portal-app my-portal --yes --local-ui=../../packages/ui
|
|
202
202
|
# generates: "@lucifer91299/ui": "file:../../packages/ui" in package.json
|
|
203
203
|
```
|
|
204
204
|
|
package/dist/cli/index.js
CHANGED
|
@@ -399,7 +399,7 @@ export default api
|
|
|
399
399
|
`;
|
|
400
400
|
}
|
|
401
401
|
function genNavConfig(o) {
|
|
402
|
-
return `import { LayoutDashboard, Settings,
|
|
402
|
+
return `import { LayoutDashboard, Settings, Users, FileText } from 'lucide-react'
|
|
403
403
|
import type { NavGroup } from '@lucifer91299/ui'
|
|
404
404
|
|
|
405
405
|
export const navGroups: NavGroup[] = [
|
|
@@ -407,14 +407,14 @@ export const navGroups: NavGroup[] = [
|
|
|
407
407
|
heading: 'Main',
|
|
408
408
|
groupIcon: <LayoutDashboard className="h-3.5 w-3.5" />,
|
|
409
409
|
items: [
|
|
410
|
-
{ label: 'Dashboard', href: '/dashboard',
|
|
410
|
+
{ label: 'Dashboard', href: '/dashboard', icon: <LayoutDashboard className="h-4 w-4" /> },
|
|
411
|
+
{ label: 'Users', href: '/dashboard/users', icon: <Users className="h-4 w-4" /> },
|
|
411
412
|
],
|
|
412
413
|
},
|
|
413
414
|
{
|
|
414
415
|
heading: 'Account',
|
|
415
|
-
groupIcon: <
|
|
416
|
+
groupIcon: <Settings className="h-3.5 w-3.5" />,
|
|
416
417
|
items: [
|
|
417
|
-
{ label: 'Profile', href: '/dashboard/profile', icon: <User className="h-4 w-4" /> },
|
|
418
418
|
{ label: 'Settings', href: '/dashboard/settings', icon: <Settings className="h-4 w-4" /> },
|
|
419
419
|
],
|
|
420
420
|
},
|
|
@@ -424,14 +424,21 @@ export const navGroups: NavGroup[] = [
|
|
|
424
424
|
function genDashboardLayout(o) {
|
|
425
425
|
return `'use client'
|
|
426
426
|
|
|
427
|
-
import { DashboardLayout } from '@lucifer91299/ui'
|
|
428
|
-
import { use${o.authMode === "multi-role" ? "MultiRoleAuth" : "JwtAuth"} } from '@lucifer91299/ui'
|
|
427
|
+
import { DashboardLayout, PageFooter, use${o.authMode === "multi-role" ? "MultiRoleAuth" : "JwtAuth"} } from '@lucifer91299/ui'
|
|
429
428
|
import { usePathname } from 'next/navigation'
|
|
430
429
|
import { navGroups } from '@/components/layout/nav-config'
|
|
431
430
|
|
|
432
431
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
433
432
|
const pathname = usePathname()
|
|
434
|
-
const { user, logout } = use${o.authMode === "multi-role" ? "MultiRoleAuth({ roles: [] })" : "JwtAuth()"}
|
|
433
|
+
const { user, loading, logout } = use${o.authMode === "multi-role" ? "MultiRoleAuth({ roles: [] })" : "JwtAuth()"}
|
|
434
|
+
|
|
435
|
+
if (loading) {
|
|
436
|
+
return (
|
|
437
|
+
<div className="min-h-screen flex items-center justify-center bg-surface-secondary">
|
|
438
|
+
<div className="w-8 h-8 rounded-full border-2 border-primary border-t-transparent animate-spin" />
|
|
439
|
+
</div>
|
|
440
|
+
)
|
|
441
|
+
}
|
|
435
442
|
|
|
436
443
|
return (
|
|
437
444
|
<DashboardLayout
|
|
@@ -439,11 +446,19 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|
|
439
446
|
sidebar="${o.sidebarStyle}"
|
|
440
447
|
projectName="${o.projectName}"
|
|
441
448
|
logoSrc="/brand/logo.svg"
|
|
442
|
-
user={{ name: (user as any)?.name ?? 'User', role: (user as any)?.role ?? '' }}
|
|
449
|
+
user={{ name: String((user as any)?.name ?? 'User'), role: String((user as any)?.role ?? '') }}
|
|
443
450
|
pathname={pathname}
|
|
444
451
|
onLogout={logout}
|
|
445
452
|
>
|
|
446
|
-
|
|
453
|
+
<div className="flex flex-col min-h-full">
|
|
454
|
+
<div className="flex-1">{children}</div>
|
|
455
|
+
<PageFooter
|
|
456
|
+
organizationName="${o.projectName}"
|
|
457
|
+
logoSrc="/brand/logo.svg"
|
|
458
|
+
poweredByText="Powered by"
|
|
459
|
+
poweredByHref="#"
|
|
460
|
+
/>
|
|
461
|
+
</div>
|
|
447
462
|
</DashboardLayout>
|
|
448
463
|
)
|
|
449
464
|
}
|
|
@@ -508,94 +523,260 @@ export default function Home() {
|
|
|
508
523
|
`;
|
|
509
524
|
}
|
|
510
525
|
function genDashboardHomePage(o) {
|
|
511
|
-
return `
|
|
526
|
+
return `'use client'
|
|
527
|
+
|
|
528
|
+
import { PageShell, DataTable, StatusBadge, useJwtAuth } from '@lucifer91299/ui'
|
|
529
|
+
import { TrendingUp, Users, ShoppingCart, Activity } from 'lucide-react'
|
|
512
530
|
|
|
513
531
|
const stats = [
|
|
514
|
-
{ label: 'Total Users', value: '2,847', change: '+12%',
|
|
515
|
-
{ label: 'Revenue', value: '\u20B948,295', change: '+8.2%', icon: TrendingUp },
|
|
516
|
-
{ label: 'Orders', value: '1,429', change: '+5.1%', icon: ShoppingCart },
|
|
517
|
-
{ label: 'Active Now', value: '94', change: '+3', icon: Activity },
|
|
532
|
+
{ label: 'Total Users', value: '2,847', change: '+12%', icon: Users, color: 'bg-blue-50 text-blue-600' },
|
|
533
|
+
{ label: 'Revenue', value: '\u20B948,295', change: '+8.2%', icon: TrendingUp, color: 'bg-green-50 text-green-600' },
|
|
534
|
+
{ label: 'Orders', value: '1,429', change: '+5.1%', icon: ShoppingCart, color: 'bg-orange-50 text-orange-600' },
|
|
535
|
+
{ label: 'Active Now', value: '94', change: '+3', icon: Activity, color: 'bg-purple-50 text-purple-600' },
|
|
518
536
|
]
|
|
519
537
|
|
|
520
538
|
const activity = [
|
|
521
|
-
{ id: 'ORD-001', user: 'Rahul Sharma', action: 'New order placed', time: '2 min ago', status: '
|
|
522
|
-
{ id: 'ORD-002', user: 'Priya Mehta', action: 'Payment received', time: '14 min ago', status: '
|
|
523
|
-
{ id: 'ORD-003', user: 'Amit Patel', action: 'Order approved', time: '1 hr ago', status: '
|
|
524
|
-
{ id: 'ORD-004', user: 'Sneha Iyer', action: 'Account registered', time: '3 hr ago', status: '
|
|
525
|
-
{ id: 'ORD-005', user: 'Vikram Singh', action: 'Order completed', time: 'Yesterday', status: '
|
|
539
|
+
{ id: 'ORD-001', user: 'Rahul Sharma', action: 'New order placed', time: '2 min ago', status: 'pending' },
|
|
540
|
+
{ id: 'ORD-002', user: 'Priya Mehta', action: 'Payment received', time: '14 min ago', status: 'paid' },
|
|
541
|
+
{ id: 'ORD-003', user: 'Amit Patel', action: 'Order approved', time: '1 hr ago', status: 'approved' },
|
|
542
|
+
{ id: 'ORD-004', user: 'Sneha Iyer', action: 'Account registered', time: '3 hr ago', status: 'active' },
|
|
543
|
+
{ id: 'ORD-005', user: 'Vikram Singh', action: 'Order completed', time: 'Yesterday', status: 'completed' },
|
|
526
544
|
]
|
|
527
545
|
|
|
528
|
-
const statusColors: Record<string, string> = {
|
|
529
|
-
Pending: 'bg-yellow-100 text-yellow-800',
|
|
530
|
-
Paid: 'bg-blue-100 text-blue-800',
|
|
531
|
-
Approved: 'bg-green-100 text-green-800',
|
|
532
|
-
Active: 'bg-purple-100 text-purple-800',
|
|
533
|
-
Completed: 'bg-gray-100 text-gray-700',
|
|
534
|
-
}
|
|
535
|
-
|
|
536
546
|
export default function DashboardHome() {
|
|
547
|
+
const { user } = useJwtAuth()
|
|
548
|
+
|
|
537
549
|
return (
|
|
538
550
|
<div className="p-6 space-y-6">
|
|
539
|
-
<
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
551
|
+
<PageShell
|
|
552
|
+
title={\`Welcome back, \${String(user?.name ?? 'Admin')}\`}
|
|
553
|
+
subtitle="Here's what's happening today."
|
|
554
|
+
/>
|
|
543
555
|
|
|
544
556
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
|
545
|
-
{stats.map(({ label, value, change, icon: Icon }) => (
|
|
546
|
-
<div key={label} className="
|
|
557
|
+
{stats.map(({ label, value, change, icon: Icon, color }) => (
|
|
558
|
+
<div key={label} className="bg-white rounded-xl p-5 shadow-sm border border-separator">
|
|
547
559
|
<div className="flex items-center justify-between mb-3">
|
|
548
560
|
<p className="text-subhead text-label-secondary">{label}</p>
|
|
549
|
-
<div className=
|
|
550
|
-
<Icon className="w-4 h-4
|
|
561
|
+
<div className={\`w-8 h-8 rounded-lg flex items-center justify-center \${color}\`}>
|
|
562
|
+
<Icon className="w-4 h-4" />
|
|
551
563
|
</div>
|
|
552
564
|
</div>
|
|
553
565
|
<p className="text-title1 font-bold text-label-primary">{value}</p>
|
|
554
|
-
<p className="text-footnote text-green-600 mt-1
|
|
566
|
+
<p className="text-footnote text-green-600 mt-1">{change} this month</p>
|
|
555
567
|
</div>
|
|
556
568
|
))}
|
|
557
569
|
</div>
|
|
558
570
|
|
|
559
|
-
<div className="
|
|
560
|
-
<div className="px-5 py-4 border-b border-
|
|
571
|
+
<div className="bg-white rounded-xl shadow-sm border border-separator overflow-hidden">
|
|
572
|
+
<div className="px-5 py-4 border-b border-separator">
|
|
561
573
|
<h2 className="text-callout font-semibold text-label-primary">Recent Activity</h2>
|
|
562
574
|
</div>
|
|
563
|
-
<
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
575
|
+
<DataTable
|
|
576
|
+
columns={[
|
|
577
|
+
{ key: 'id', header: 'ID', className: 'font-mono text-xs text-label-secondary' },
|
|
578
|
+
{ key: 'user', header: 'User', render: (r) => <span className="font-medium text-label-primary">{r.user}</span> },
|
|
579
|
+
{ key: 'action', header: 'Action', render: (r) => <span className="text-label-secondary">{r.action}</span> },
|
|
580
|
+
{ key: 'time', header: 'Time', render: (r) => <span className="text-label-tertiary">{r.time}</span> },
|
|
581
|
+
{ key: 'status', header: 'Status', render: (r) => <StatusBadge status={r.status} /> },
|
|
582
|
+
]}
|
|
583
|
+
data={activity}
|
|
584
|
+
keyExtractor={(r) => r.id}
|
|
585
|
+
/>
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
`;
|
|
591
|
+
}
|
|
592
|
+
function genUsersPage(_o) {
|
|
593
|
+
return `'use client'
|
|
594
|
+
|
|
595
|
+
import { useState } from 'react'
|
|
596
|
+
import { PageShell, Breadcrumbs, DataTable, StatusBadge, ActionButtons, Button, Input, Select } from '@lucifer91299/ui'
|
|
597
|
+
import { UserPlus, Search } from 'lucide-react'
|
|
598
|
+
|
|
599
|
+
type User = { id: number; name: string; email: string; role: string; status: string; joined: string }
|
|
600
|
+
|
|
601
|
+
const USERS: User[] = [
|
|
602
|
+
{ id: 1, name: 'Rahul Sharma', email: 'rahul@example.com', role: 'Admin', status: 'active', joined: '12 Jan 2024' },
|
|
603
|
+
{ id: 2, name: 'Priya Mehta', email: 'priya@example.com', role: 'Member', status: 'active', joined: '03 Feb 2024' },
|
|
604
|
+
{ id: 3, name: 'Amit Patel', email: 'amit@example.com', role: 'Member', status: 'pending', joined: '19 Mar 2024' },
|
|
605
|
+
{ id: 4, name: 'Sneha Iyer', email: 'sneha@example.com', role: 'Editor', status: 'active', joined: '07 Apr 2024' },
|
|
606
|
+
{ id: 5, name: 'Vikram Singh', email: 'vikram@example.com', role: 'Member', status: 'inactive', joined: '22 May 2024' },
|
|
607
|
+
]
|
|
608
|
+
|
|
609
|
+
const STATUS_OPTIONS = [
|
|
610
|
+
{ value: '', label: 'All Statuses' },
|
|
611
|
+
{ value: 'active', label: 'Active' },
|
|
612
|
+
{ value: 'pending', label: 'Pending' },
|
|
613
|
+
{ value: 'inactive', label: 'Inactive' },
|
|
614
|
+
]
|
|
615
|
+
|
|
616
|
+
const ROLE_OPTIONS = [
|
|
617
|
+
{ value: '', label: 'All Roles' },
|
|
618
|
+
{ value: 'Admin', label: 'Admin' },
|
|
619
|
+
{ value: 'Member', label: 'Member' },
|
|
620
|
+
{ value: 'Editor', label: 'Editor' },
|
|
621
|
+
]
|
|
622
|
+
|
|
623
|
+
export default function UsersPage() {
|
|
624
|
+
const [search, setSearch] = useState('')
|
|
625
|
+
const [statusFilter, setStatus] = useState('')
|
|
626
|
+
const [roleFilter, setRole] = useState('')
|
|
627
|
+
|
|
628
|
+
const filtered = USERS.filter((u) => {
|
|
629
|
+
const q = search.toLowerCase()
|
|
630
|
+
if (q && !u.name.toLowerCase().includes(q) && !u.email.toLowerCase().includes(q)) return false
|
|
631
|
+
if (statusFilter && u.status !== statusFilter) return false
|
|
632
|
+
if (roleFilter && u.role !== roleFilter) return false
|
|
633
|
+
return true
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
return (
|
|
637
|
+
<div className="p-6">
|
|
638
|
+
<PageShell
|
|
639
|
+
title="Users"
|
|
640
|
+
subtitle={\`\${filtered.length} of \${USERS.length} users\`}
|
|
641
|
+
breadcrumbs={<Breadcrumbs items={[{ label: 'Users' }]} />}
|
|
642
|
+
actions={
|
|
643
|
+
<Button variant="primary">
|
|
644
|
+
<UserPlus className="w-4 h-4 mr-1.5" />
|
|
645
|
+
Add User
|
|
646
|
+
</Button>
|
|
647
|
+
}
|
|
648
|
+
controls={
|
|
649
|
+
<>
|
|
650
|
+
<div className="flex-1 min-w-[200px]">
|
|
651
|
+
<Input
|
|
652
|
+
placeholder="Search by name or email\u2026"
|
|
653
|
+
value={search}
|
|
654
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
655
|
+
suffix={<Search className="w-4 h-4" />}
|
|
656
|
+
/>
|
|
657
|
+
</div>
|
|
658
|
+
<div className="w-40">
|
|
659
|
+
<Select options={STATUS_OPTIONS} value={statusFilter} onChange={setStatus} placeholder="Status" />
|
|
660
|
+
</div>
|
|
661
|
+
<div className="w-36">
|
|
662
|
+
<Select options={ROLE_OPTIONS} value={roleFilter} onChange={setRole} placeholder="Role" />
|
|
663
|
+
</div>
|
|
664
|
+
</>
|
|
665
|
+
}
|
|
666
|
+
/>
|
|
667
|
+
|
|
668
|
+
<div className="bg-white rounded-xl border border-separator shadow-sm overflow-hidden">
|
|
669
|
+
<DataTable
|
|
670
|
+
columns={[
|
|
671
|
+
{ key: 'id', header: '#', render: (_, i) => <span className="text-label-tertiary text-footnote">{i + 1}</span>, className: 'w-10' },
|
|
672
|
+
{
|
|
673
|
+
key: 'name', header: 'Name',
|
|
674
|
+
render: (u: User) => (
|
|
675
|
+
<div>
|
|
676
|
+
<p className="font-medium text-label-primary">{u.name}</p>
|
|
677
|
+
<p className="text-footnote text-label-tertiary">{u.email}</p>
|
|
678
|
+
</div>
|
|
679
|
+
),
|
|
680
|
+
},
|
|
681
|
+
{ key: 'role', header: 'Role' },
|
|
682
|
+
{ key: 'joined', header: 'Joined' },
|
|
683
|
+
{ key: 'status', header: 'Status', render: (u: User) => <StatusBadge status={u.status} /> },
|
|
684
|
+
{
|
|
685
|
+
key: 'actions', header: '', className: 'w-24',
|
|
686
|
+
render: () => (
|
|
687
|
+
<ActionButtons
|
|
688
|
+
showView showEdit showDelete
|
|
689
|
+
onView={() => {}} onEdit={() => {}} onDelete={() => {}}
|
|
690
|
+
/>
|
|
691
|
+
),
|
|
692
|
+
},
|
|
693
|
+
]}
|
|
694
|
+
data={filtered}
|
|
695
|
+
keyExtractor={(u) => u.id}
|
|
696
|
+
emptyMessage="No users match your filters."
|
|
697
|
+
/>
|
|
589
698
|
</div>
|
|
590
699
|
</div>
|
|
591
700
|
)
|
|
592
701
|
}
|
|
593
702
|
`;
|
|
594
703
|
}
|
|
704
|
+
function genSettingsPage(o) {
|
|
705
|
+
return `'use client'
|
|
706
|
+
|
|
707
|
+
import { useState } from 'react'
|
|
708
|
+
import { PageShell, Breadcrumbs, Card, Input, Select, Button, AlertBanner } from '@lucifer91299/ui'
|
|
709
|
+
|
|
710
|
+
const SIDEBAR_OPTIONS = [
|
|
711
|
+
{ value: 'full', label: 'Full \u2014 wide sidebar with labels' },
|
|
712
|
+
{ value: 'rail', label: 'Rail \u2014 icon-only narrow sidebar' },
|
|
713
|
+
{ value: 'both', label: 'Both \u2014 full on desktop, rail on mobile' },
|
|
714
|
+
]
|
|
715
|
+
|
|
716
|
+
export default function SettingsPage() {
|
|
717
|
+
const [saved, setSaved] = useState(false)
|
|
718
|
+
const [projectName, setProject] = useState('${o.projectName}')
|
|
719
|
+
const [sidebar, setSidebar] = useState('${o.sidebarStyle}')
|
|
720
|
+
const [apiUrl, setApiUrl] = useState('${o.apiUrl}')
|
|
721
|
+
|
|
722
|
+
function handleSave() {
|
|
723
|
+
setSaved(true)
|
|
724
|
+
setTimeout(() => setSaved(false), 3000)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return (
|
|
728
|
+
<div className="p-6 space-y-6">
|
|
729
|
+
<PageShell
|
|
730
|
+
title="Settings"
|
|
731
|
+
subtitle="Configure your portal appearance and connection settings."
|
|
732
|
+
breadcrumbs={<Breadcrumbs items={[{ label: 'Settings' }]} />}
|
|
733
|
+
actions={<Button variant="primary" onClick={handleSave}>Save changes</Button>}
|
|
734
|
+
/>
|
|
735
|
+
|
|
736
|
+
{saved && <AlertBanner variant="success" message="Settings saved successfully." />}
|
|
737
|
+
|
|
738
|
+
<Card className="p-6">
|
|
739
|
+
<h3 className="text-callout font-semibold text-label-primary mb-4">Appearance</h3>
|
|
740
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
741
|
+
<Input
|
|
742
|
+
label="Project name"
|
|
743
|
+
value={projectName}
|
|
744
|
+
onChange={(e) => setProject(e.target.value)}
|
|
745
|
+
/>
|
|
746
|
+
<Select
|
|
747
|
+
label="Sidebar style"
|
|
748
|
+
options={SIDEBAR_OPTIONS}
|
|
749
|
+
value={sidebar}
|
|
750
|
+
onChange={setSidebar}
|
|
751
|
+
/>
|
|
752
|
+
</div>
|
|
753
|
+
</Card>
|
|
754
|
+
|
|
755
|
+
<Card className="p-6">
|
|
756
|
+
<h3 className="text-callout font-semibold text-label-primary mb-4">Connection</h3>
|
|
757
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
758
|
+
<Input
|
|
759
|
+
label="Backend API URL"
|
|
760
|
+
value={apiUrl}
|
|
761
|
+
onChange={(e) => setApiUrl(e.target.value)}
|
|
762
|
+
helperText="Used by the API client for all requests."
|
|
763
|
+
/>
|
|
764
|
+
<Input
|
|
765
|
+
label="JWT cookie name"
|
|
766
|
+
defaultValue="${o.jwtCookieName}"
|
|
767
|
+
helperText="Must match the cookie set by your login route."
|
|
768
|
+
/>
|
|
769
|
+
</div>
|
|
770
|
+
</Card>
|
|
771
|
+
</div>
|
|
772
|
+
)
|
|
773
|
+
}
|
|
774
|
+
`;
|
|
775
|
+
}
|
|
595
776
|
|
|
596
777
|
// src/cli/index.ts
|
|
597
778
|
function write(filePath, content) {
|
|
598
|
-
const dir =
|
|
779
|
+
const dir = (0, import_path.dirname)(filePath);
|
|
599
780
|
if (dir) (0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
600
781
|
(0, import_fs.writeFileSync)(filePath, content, "utf-8");
|
|
601
782
|
}
|
|
@@ -775,6 +956,8 @@ export default config
|
|
|
775
956
|
write(f("src/app/login/page.tsx"), genLoginPage(opts));
|
|
776
957
|
write(f("src/app/dashboard/layout.tsx"), genDashboardLayout(opts));
|
|
777
958
|
write(f("src/app/dashboard/page.tsx"), genDashboardHomePage(opts));
|
|
959
|
+
write(f("src/app/dashboard/users/page.tsx"), genUsersPage(opts));
|
|
960
|
+
write(f("src/app/dashboard/settings/page.tsx"), genSettingsPage(opts));
|
|
778
961
|
write(f("src/app/api/auth/login/route.ts"), genLoginRoute(opts));
|
|
779
962
|
write(f("src/app/api/auth/user/route.ts"), genUserRoute(opts));
|
|
780
963
|
write(f("src/app/api/auth/session/route.ts"), genSessionRoute(opts));
|
|
@@ -827,7 +1010,7 @@ function countFiles(dir) {
|
|
|
827
1010
|
async function main() {
|
|
828
1011
|
const { flags, positional } = parseArgs(process.argv.slice(2));
|
|
829
1012
|
if (flags["help"] || flags["h"]) {
|
|
830
|
-
log("Usage: create-portal-app [project-name] [options]");
|
|
1013
|
+
log("Usage: npx @lucifer91299/create-portal-app [project-name] [options]");
|
|
831
1014
|
log("");
|
|
832
1015
|
log(" Without options \u2192 interactive prompts for all settings");
|
|
833
1016
|
log("");
|
|
@@ -843,11 +1026,11 @@ async function main() {
|
|
|
843
1026
|
log(" --local-ui=PATH Use local @lucifer91299/ui (file: reference, for development)");
|
|
844
1027
|
log("");
|
|
845
1028
|
log("Examples:");
|
|
846
|
-
log(" create-portal-app");
|
|
847
|
-
log(" create-portal-app my-portal");
|
|
848
|
-
log(" create-portal-app my-portal --yes");
|
|
849
|
-
log(" create-portal-app my-portal --yes --auth=laravel --primary=#E11D48");
|
|
850
|
-
log(" create-portal-app my-portal --yes --local-ui=../../ui");
|
|
1029
|
+
log(" npx @lucifer91299/create-portal-app");
|
|
1030
|
+
log(" npx @lucifer91299/create-portal-app my-portal");
|
|
1031
|
+
log(" npx @lucifer91299/create-portal-app my-portal --yes");
|
|
1032
|
+
log(" npx @lucifer91299/create-portal-app my-portal --yes --auth=laravel --primary=#E11D48");
|
|
1033
|
+
log(" npx @lucifer91299/create-portal-app my-portal --yes --local-ui=../../ui");
|
|
851
1034
|
process.exit(0);
|
|
852
1035
|
}
|
|
853
1036
|
const projectNameArg = positional[0];
|
package/package.json
CHANGED