@djangocfg/layouts 1.0.6 → 1.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.
- package/package.json +5 -5
- package/src/layouts/AppLayout/AppLayout.tsx +126 -28
- package/src/layouts/AppLayout/components/ErrorBoundary.tsx +99 -0
- package/src/layouts/AppLayout/components/PageProgress.tsx +28 -11
- package/src/layouts/AppLayout/components/index.ts +1 -0
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthContext.tsx +1 -1
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthHelp.tsx +5 -5
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +2 -2
- package/src/layouts/AppLayout/layouts/AuthLayout/IdentifierForm.tsx +2 -2
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +1 -1
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +1 -1
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenu.tsx +53 -36
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +64 -52
- package/src/layouts/AppLayout/types/config.ts +22 -0
- package/src/layouts/ErrorLayout/ErrorLayout.tsx +169 -0
- package/src/layouts/ErrorLayout/errorConfig.tsx +152 -0
- package/src/layouts/ErrorLayout/index.ts +8 -0
- package/src/layouts/UILayout/README.md +267 -0
- package/src/layouts/UILayout/REFACTORING.md +331 -0
- package/src/layouts/UILayout/UIGuideApp.tsx +18 -0
- package/src/layouts/UILayout/UIGuideLanding.tsx +198 -0
- package/src/layouts/UILayout/UIGuideView.tsx +61 -0
- package/src/layouts/UILayout/UILayout.tsx +122 -0
- package/src/layouts/UILayout/components/AutoComponentDemo.tsx +77 -0
- package/src/layouts/UILayout/components/CategoryRenderer.tsx +45 -0
- package/src/layouts/UILayout/components/Header.tsx +114 -0
- package/src/layouts/UILayout/components/MobileOverlay.tsx +33 -0
- package/src/layouts/UILayout/components/Sidebar.tsx +195 -0
- package/src/layouts/UILayout/components/TailwindGuideRenderer.tsx +138 -0
- package/src/layouts/UILayout/config/ai-export.config.ts +80 -0
- package/src/layouts/UILayout/config/categories.config.tsx +114 -0
- package/src/layouts/UILayout/config/components/blocks.config.tsx +233 -0
- package/src/layouts/UILayout/config/components/data.config.tsx +308 -0
- package/src/layouts/UILayout/config/components/feedback.config.tsx +246 -0
- package/src/layouts/UILayout/config/components/forms.config.tsx +171 -0
- package/src/layouts/UILayout/config/components/hooks.config.tsx +131 -0
- package/src/layouts/UILayout/config/components/index.ts +69 -0
- package/src/layouts/UILayout/config/components/layout.config.tsx +133 -0
- package/src/layouts/UILayout/config/components/navigation.config.tsx +244 -0
- package/src/layouts/UILayout/config/components/overlay.config.tsx +561 -0
- package/src/layouts/UILayout/config/components/specialized.config.tsx +125 -0
- package/src/layouts/UILayout/config/components/types.ts +14 -0
- package/src/layouts/UILayout/config/index.ts +42 -0
- package/src/layouts/UILayout/config/tailwind.config.ts +77 -0
- package/src/layouts/UILayout/constants.ts +23 -0
- package/src/layouts/UILayout/context/ShowcaseContext.tsx +53 -0
- package/src/layouts/UILayout/context/index.ts +1 -0
- package/src/layouts/UILayout/index.ts +64 -0
- package/src/layouts/UILayout/types.ts +13 -0
- package/src/layouts/index.ts +5 -1
|
@@ -9,15 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import Link from 'next/link';
|
|
12
|
-
import { Menu } from 'lucide-react';
|
|
12
|
+
import { Menu, ChevronDown } from 'lucide-react';
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
NavigationMenuList,
|
|
19
|
-
NavigationMenuTrigger,
|
|
20
|
-
navigationMenuTriggerStyle,
|
|
14
|
+
DropdownMenu,
|
|
15
|
+
DropdownMenuTrigger,
|
|
16
|
+
DropdownMenuContent,
|
|
17
|
+
DropdownMenuItem,
|
|
21
18
|
} from '@djangocfg/ui/components';
|
|
22
19
|
import { ThemeToggle } from '@djangocfg/ui/theme';
|
|
23
20
|
import { cn } from '@djangocfg/ui/lib';
|
|
@@ -45,15 +42,16 @@ export function Navigation() {
|
|
|
45
42
|
const { user, isAuthenticated, logout } = useAuth();
|
|
46
43
|
const { isActive } = useNavigation();
|
|
47
44
|
const isMobile = useIsMobile();
|
|
45
|
+
const [openDropdown, setOpenDropdown] = React.useState<string | null>(null);
|
|
48
46
|
|
|
49
47
|
const { app, publicLayout } = config;
|
|
50
48
|
|
|
51
49
|
return (
|
|
52
|
-
<nav className="sticky top-0 w-full border-b backdrop-blur-xl z-
|
|
50
|
+
<nav className="sticky top-0 w-full border-b backdrop-blur-xl z-10 bg-background/80 border-border/30">
|
|
53
51
|
<div className="w-full px-4 sm:px-6 lg:px-8">
|
|
54
52
|
<div className="flex items-center justify-between h-16">
|
|
55
53
|
{/* Left side - Logo and Navigation Menu */}
|
|
56
|
-
<div className="flex items-center gap-6
|
|
54
|
+
<div className="flex items-center gap-6">
|
|
57
55
|
{/* Logo */}
|
|
58
56
|
<Link
|
|
59
57
|
href={publicLayout.navigation.homePath}
|
|
@@ -69,11 +67,9 @@ export function Navigation() {
|
|
|
69
67
|
</span>
|
|
70
68
|
</Link>
|
|
71
69
|
|
|
72
|
-
{/* Desktop Navigation Menu
|
|
70
|
+
{/* Desktop Navigation Menu */}
|
|
73
71
|
{!isMobile && (
|
|
74
|
-
|
|
75
|
-
<NavigationMenu>
|
|
76
|
-
<NavigationMenuList>
|
|
72
|
+
<div className="flex items-center gap-1">
|
|
77
73
|
{publicLayout.navigation.menuSections.map((section) => {
|
|
78
74
|
// Single item section - render as direct link
|
|
79
75
|
if (section.items.length === 1) {
|
|
@@ -81,53 +77,69 @@ export function Navigation() {
|
|
|
81
77
|
if (!item) return null;
|
|
82
78
|
|
|
83
79
|
return (
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
</Link>
|
|
95
|
-
</NavigationMenuLink>
|
|
96
|
-
</NavigationMenuItem>
|
|
80
|
+
<Link
|
|
81
|
+
key={section.title}
|
|
82
|
+
href={item.path}
|
|
83
|
+
className={cn(
|
|
84
|
+
'inline-flex h-9 items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50',
|
|
85
|
+
isActive(item.path) && 'bg-accent text-accent-foreground'
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
{item.label}
|
|
89
|
+
</Link>
|
|
97
90
|
);
|
|
98
91
|
}
|
|
99
92
|
|
|
100
93
|
// Multiple items - render as dropdown menu
|
|
101
94
|
return (
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
<div
|
|
96
|
+
key={section.title}
|
|
97
|
+
onMouseEnter={() => setOpenDropdown(section.title)}
|
|
98
|
+
onMouseLeave={() => setOpenDropdown(null)}
|
|
99
|
+
>
|
|
100
|
+
<DropdownMenu
|
|
101
|
+
open={openDropdown === section.title}
|
|
102
|
+
onOpenChange={(open) => setOpenDropdown(open ? section.title : null)}
|
|
103
|
+
modal={false}
|
|
104
|
+
>
|
|
105
|
+
<DropdownMenuTrigger
|
|
106
|
+
className={cn(
|
|
107
|
+
"inline-flex h-9 items-center justify-center gap-1 rounded-md px-4 py-2 text-sm font-medium transition-colors focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50",
|
|
108
|
+
openDropdown === section.title ? "bg-accent text-accent-foreground" : "hover:bg-accent hover:text-accent-foreground"
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
111
|
+
{section.title}
|
|
112
|
+
<ChevronDown className="h-3 w-3 transition-transform duration-200 group-data-[state=open]:rotate-180" />
|
|
113
|
+
</DropdownMenuTrigger>
|
|
114
|
+
<DropdownMenuContent
|
|
115
|
+
align="start"
|
|
116
|
+
sideOffset={0}
|
|
117
|
+
className="p-2"
|
|
118
|
+
style={{
|
|
119
|
+
minWidth: '250px',
|
|
120
|
+
backdropFilter: 'blur(24px)',
|
|
121
|
+
WebkitBackdropFilter: 'blur(24px)'
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
106
124
|
{section.items.map((item) => (
|
|
107
|
-
<
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
</div>
|
|
119
|
-
</Link>
|
|
120
|
-
</NavigationMenuLink>
|
|
121
|
-
</li>
|
|
125
|
+
<DropdownMenuItem key={item.path} asChild>
|
|
126
|
+
<Link
|
|
127
|
+
href={item.path}
|
|
128
|
+
className={cn(
|
|
129
|
+
'cursor-pointer w-full hover:bg-accent hover:text-accent-foreground transition-colors text-base px-4 py-3 rounded-md',
|
|
130
|
+
isActive(item.path) && 'bg-accent/50'
|
|
131
|
+
)}
|
|
132
|
+
>
|
|
133
|
+
{item.label}
|
|
134
|
+
</Link>
|
|
135
|
+
</DropdownMenuItem>
|
|
122
136
|
))}
|
|
123
|
-
</
|
|
124
|
-
</
|
|
125
|
-
</
|
|
137
|
+
</DropdownMenuContent>
|
|
138
|
+
</DropdownMenu>
|
|
139
|
+
</div>
|
|
126
140
|
);
|
|
127
141
|
})}
|
|
128
|
-
</
|
|
129
|
-
</NavigationMenu>
|
|
130
|
-
</div>
|
|
142
|
+
</div>
|
|
131
143
|
)}
|
|
132
144
|
</div>
|
|
133
145
|
|
|
@@ -37,4 +37,26 @@ export interface AppLayoutConfig {
|
|
|
37
37
|
|
|
38
38
|
/** Private layout configuration */
|
|
39
39
|
privateLayout: PrivateLayoutConfig;
|
|
40
|
+
|
|
41
|
+
/** Error handling configuration */
|
|
42
|
+
errors?: {
|
|
43
|
+
/** Enable automatic error boundary (default: true) */
|
|
44
|
+
enableErrorBoundary?: boolean;
|
|
45
|
+
/** Support email for error pages */
|
|
46
|
+
supportEmail?: string;
|
|
47
|
+
/** Custom error handler callback */
|
|
48
|
+
onError?: (error: Error, errorInfo?: React.ErrorInfo) => void;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Auth configuration */
|
|
52
|
+
auth?: {
|
|
53
|
+
/** Terms of Service URL */
|
|
54
|
+
termsUrl?: string;
|
|
55
|
+
/** Privacy Policy URL */
|
|
56
|
+
privacyUrl?: string;
|
|
57
|
+
/** Support URL for auth help */
|
|
58
|
+
supportUrl?: string;
|
|
59
|
+
/** Enable phone authentication (default: false) */
|
|
60
|
+
enablePhoneAuth?: boolean;
|
|
61
|
+
};
|
|
40
62
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ErrorLayout - Universal Error Display
|
|
3
|
+
*
|
|
4
|
+
* Minimalist error page with customizable content
|
|
5
|
+
* Works with Next.js error pages (404.tsx, 500.tsx, error.tsx)
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // pages/404.tsx
|
|
10
|
+
* import { ErrorLayout } from '@djangocfg/layouts/AppLayout';
|
|
11
|
+
*
|
|
12
|
+
* export default function NotFound() {
|
|
13
|
+
* return (
|
|
14
|
+
* <ErrorLayout
|
|
15
|
+
* code="404"
|
|
16
|
+
* title="Page Not Found"
|
|
17
|
+
* description="The page you're looking for doesn't exist."
|
|
18
|
+
* />
|
|
19
|
+
* );
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
'use client';
|
|
25
|
+
|
|
26
|
+
import React from 'react';
|
|
27
|
+
import { useRouter } from 'next/router';
|
|
28
|
+
import { Button } from '@djangocfg/ui/components';
|
|
29
|
+
import { getErrorContent } from './errorConfig';
|
|
30
|
+
|
|
31
|
+
export interface ErrorLayoutProps {
|
|
32
|
+
/** Error code (e.g., "404", "500", "403") - if provided, auto-configures title/description/icon */
|
|
33
|
+
code?: string | number;
|
|
34
|
+
/** Error title (auto-generated from code if not provided) */
|
|
35
|
+
title?: string;
|
|
36
|
+
/** Error description (auto-generated from code if not provided) */
|
|
37
|
+
description?: string;
|
|
38
|
+
/** Custom action buttons */
|
|
39
|
+
actions?: React.ReactNode;
|
|
40
|
+
/** Show default actions (back, home) */
|
|
41
|
+
showDefaultActions?: boolean;
|
|
42
|
+
/** Custom illustration/icon (auto-generated from code if not provided) */
|
|
43
|
+
illustration?: React.ReactNode;
|
|
44
|
+
/** Support email for contact link */
|
|
45
|
+
supportEmail?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* ErrorLayout Component
|
|
50
|
+
*
|
|
51
|
+
* Clean, minimal error display with semantic colors
|
|
52
|
+
* Standalone layout - doesn't depend on AppLayout context
|
|
53
|
+
*
|
|
54
|
+
* Smart auto-configuration:
|
|
55
|
+
* - Pass only `code` prop and everything is configured automatically
|
|
56
|
+
* - Or override with custom title/description/illustration
|
|
57
|
+
*/
|
|
58
|
+
export function ErrorLayout({
|
|
59
|
+
code,
|
|
60
|
+
title,
|
|
61
|
+
description,
|
|
62
|
+
actions,
|
|
63
|
+
showDefaultActions = true,
|
|
64
|
+
illustration,
|
|
65
|
+
supportEmail = 'support@example.com',
|
|
66
|
+
}: ErrorLayoutProps) {
|
|
67
|
+
const router = useRouter();
|
|
68
|
+
|
|
69
|
+
// Auto-configure content from error code if not provided
|
|
70
|
+
const autoContent = code && (!title || !description || !illustration)
|
|
71
|
+
? getErrorContent(code)
|
|
72
|
+
: null;
|
|
73
|
+
|
|
74
|
+
// Use provided values or fall back to auto-generated
|
|
75
|
+
const finalTitle = title || autoContent?.title || 'Error';
|
|
76
|
+
const finalDescription = description || autoContent?.description;
|
|
77
|
+
const finalIllustration = illustration || autoContent?.icon;
|
|
78
|
+
|
|
79
|
+
const handleGoBack = () => {
|
|
80
|
+
if (window.history.length > 1) {
|
|
81
|
+
router.back();
|
|
82
|
+
} else {
|
|
83
|
+
router.push('/');
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const handleGoHome = () => {
|
|
88
|
+
router.push('/');
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="min-h-screen flex items-center justify-center px-4">
|
|
93
|
+
<div className="max-w-2xl w-full text-center space-y-8">
|
|
94
|
+
{/* Error Code */}
|
|
95
|
+
{code && (
|
|
96
|
+
<div className="relative">
|
|
97
|
+
<h1
|
|
98
|
+
className="text-[12rem] font-bold leading-none text-muted/20 select-none"
|
|
99
|
+
aria-hidden="true"
|
|
100
|
+
>
|
|
101
|
+
{code}
|
|
102
|
+
</h1>
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
{/* Illustration */}
|
|
107
|
+
{finalIllustration && (
|
|
108
|
+
<div className="flex justify-center py-8">
|
|
109
|
+
{finalIllustration}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
|
|
113
|
+
{/* Error Content */}
|
|
114
|
+
<div className="space-y-4">
|
|
115
|
+
<h2 className="text-4xl font-bold text-foreground">
|
|
116
|
+
{finalTitle}
|
|
117
|
+
</h2>
|
|
118
|
+
|
|
119
|
+
{finalDescription && (
|
|
120
|
+
<p className="text-lg text-muted-foreground max-w-md mx-auto">
|
|
121
|
+
{finalDescription}
|
|
122
|
+
</p>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Actions */}
|
|
127
|
+
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
|
|
128
|
+
{/* Custom actions */}
|
|
129
|
+
{actions}
|
|
130
|
+
|
|
131
|
+
{/* Default actions */}
|
|
132
|
+
{showDefaultActions && !actions && (
|
|
133
|
+
<>
|
|
134
|
+
<Button
|
|
135
|
+
variant="outline"
|
|
136
|
+
size="lg"
|
|
137
|
+
onClick={handleGoBack}
|
|
138
|
+
className="min-w-[140px]"
|
|
139
|
+
>
|
|
140
|
+
Go Back
|
|
141
|
+
</Button>
|
|
142
|
+
<Button
|
|
143
|
+
variant="default"
|
|
144
|
+
size="lg"
|
|
145
|
+
onClick={handleGoHome}
|
|
146
|
+
className="min-w-[140px]"
|
|
147
|
+
>
|
|
148
|
+
Go Home
|
|
149
|
+
</Button>
|
|
150
|
+
</>
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Additional Info */}
|
|
155
|
+
<div className="pt-8 text-sm text-muted-foreground">
|
|
156
|
+
<p>
|
|
157
|
+
Need help? Contact{' '}
|
|
158
|
+
<a
|
|
159
|
+
href={`mailto:${supportEmail}`}
|
|
160
|
+
className="text-primary hover:underline"
|
|
161
|
+
>
|
|
162
|
+
support
|
|
163
|
+
</a>
|
|
164
|
+
</p>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Error Configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides standard error content for common HTTP status codes
|
|
5
|
+
* Use this to maintain consistency across error pages
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { FileQuestion, ServerCrash, ShieldAlert, Clock, AlertTriangle, Ban } from 'lucide-react';
|
|
10
|
+
|
|
11
|
+
export interface ErrorContent {
|
|
12
|
+
title: string;
|
|
13
|
+
description: string;
|
|
14
|
+
icon: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get standardized error content based on status code
|
|
19
|
+
*
|
|
20
|
+
* @param statusCode - HTTP status code or custom error type
|
|
21
|
+
* @returns Error content configuration
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* const { title, description, icon } = getErrorContent(404);
|
|
26
|
+
* <ErrorLayout title={title} description={description} illustration={icon} />
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function getErrorContent(statusCode?: number | string): ErrorContent {
|
|
30
|
+
const code = typeof statusCode === 'string' ? parseInt(statusCode, 10) : statusCode;
|
|
31
|
+
|
|
32
|
+
switch (code) {
|
|
33
|
+
// 400 Bad Request
|
|
34
|
+
case 400:
|
|
35
|
+
return {
|
|
36
|
+
title: 'Bad Request',
|
|
37
|
+
description: 'The request could not be understood. Please check your input and try again.',
|
|
38
|
+
icon: <AlertTriangle className="w-24 h-24 text-warning/50" strokeWidth={1.5} />,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// 401 Unauthorized
|
|
42
|
+
case 401:
|
|
43
|
+
return {
|
|
44
|
+
title: 'Authentication Required',
|
|
45
|
+
description: 'You need to sign in to access this page.',
|
|
46
|
+
icon: <ShieldAlert className="w-24 h-24 text-warning/50" strokeWidth={1.5} />,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// 403 Forbidden
|
|
50
|
+
case 403:
|
|
51
|
+
return {
|
|
52
|
+
title: 'Access Denied',
|
|
53
|
+
description: "You don't have permission to access this resource.",
|
|
54
|
+
icon: <Ban className="w-24 h-24 text-destructive/50" strokeWidth={1.5} />,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// 404 Not Found
|
|
58
|
+
case 404:
|
|
59
|
+
return {
|
|
60
|
+
title: 'Page Not Found',
|
|
61
|
+
description: "The page you're looking for doesn't exist or has been moved.",
|
|
62
|
+
icon: <FileQuestion className="w-24 h-24 text-muted-foreground/50" strokeWidth={1.5} />,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// 408 Request Timeout
|
|
66
|
+
case 408:
|
|
67
|
+
return {
|
|
68
|
+
title: 'Request Timeout',
|
|
69
|
+
description: 'The request took too long to process. Please try again.',
|
|
70
|
+
icon: <Clock className="w-24 h-24 text-warning/50" strokeWidth={1.5} />,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// 500 Internal Server Error
|
|
74
|
+
case 500:
|
|
75
|
+
return {
|
|
76
|
+
title: 'Server Error',
|
|
77
|
+
description: "Something went wrong on our end. We're working to fix it.",
|
|
78
|
+
icon: <ServerCrash className="w-24 h-24 text-destructive/50" strokeWidth={1.5} />,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// 502 Bad Gateway
|
|
82
|
+
case 502:
|
|
83
|
+
return {
|
|
84
|
+
title: 'Bad Gateway',
|
|
85
|
+
description: 'The server received an invalid response. Please try again later.',
|
|
86
|
+
icon: <ServerCrash className="w-24 h-24 text-destructive/50" strokeWidth={1.5} />,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// 503 Service Unavailable
|
|
90
|
+
case 503:
|
|
91
|
+
return {
|
|
92
|
+
title: 'Service Unavailable',
|
|
93
|
+
description: 'The service is temporarily unavailable. Please try again later.',
|
|
94
|
+
icon: <ServerCrash className="w-24 h-24 text-destructive/50" strokeWidth={1.5} />,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// 504 Gateway Timeout
|
|
98
|
+
case 504:
|
|
99
|
+
return {
|
|
100
|
+
title: 'Gateway Timeout',
|
|
101
|
+
description: 'The server took too long to respond. Please try again.',
|
|
102
|
+
icon: <Clock className="w-24 h-24 text-warning/50" strokeWidth={1.5} />,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Default / Unknown Error
|
|
106
|
+
default:
|
|
107
|
+
return {
|
|
108
|
+
title: 'Something Went Wrong',
|
|
109
|
+
description: 'An unexpected error occurred. Please try again or contact support.',
|
|
110
|
+
icon: <AlertTriangle className="w-24 h-24 text-warning/50" strokeWidth={1.5} />,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Common error codes as constants
|
|
117
|
+
*/
|
|
118
|
+
export const ERROR_CODES = {
|
|
119
|
+
BAD_REQUEST: 400,
|
|
120
|
+
UNAUTHORIZED: 401,
|
|
121
|
+
FORBIDDEN: 403,
|
|
122
|
+
NOT_FOUND: 404,
|
|
123
|
+
TIMEOUT: 408,
|
|
124
|
+
SERVER_ERROR: 500,
|
|
125
|
+
BAD_GATEWAY: 502,
|
|
126
|
+
SERVICE_UNAVAILABLE: 503,
|
|
127
|
+
GATEWAY_TIMEOUT: 504,
|
|
128
|
+
} as const;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Ready-to-use getInitialProps for Next.js _error.tsx page
|
|
132
|
+
*
|
|
133
|
+
* Extracts status code from response or error automatically
|
|
134
|
+
* Works for both server-side and client-side errors
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```tsx
|
|
138
|
+
* // pages/_error.tsx
|
|
139
|
+
* import { ErrorLayout, errorPageGetInitialProps } from '@djangocfg/layouts';
|
|
140
|
+
*
|
|
141
|
+
* function ErrorPage({ statusCode }) {
|
|
142
|
+
* return <ErrorLayout code={statusCode} />;
|
|
143
|
+
* }
|
|
144
|
+
*
|
|
145
|
+
* ErrorPage.getInitialProps = errorPageGetInitialProps;
|
|
146
|
+
* export default ErrorPage;
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
export const errorPageGetInitialProps = ({ res, err }: any) => {
|
|
150
|
+
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
|
|
151
|
+
return { statusCode };
|
|
152
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ErrorLayout Module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { ErrorLayout } from './ErrorLayout';
|
|
6
|
+
export type { ErrorLayoutProps } from './ErrorLayout';
|
|
7
|
+
export { getErrorContent, ERROR_CODES, errorPageGetInitialProps } from './errorConfig';
|
|
8
|
+
export type { ErrorContent } from './errorConfig';
|