@owlmeans/web-panel 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.
- package/LICENSE +21 -0
- package/README.md +1195 -0
- package/build/.gitkeep +0 -0
- package/build/auth/context.d.ts +5 -0
- package/build/auth/context.d.ts.map +1 -0
- package/build/auth/context.js +17 -0
- package/build/auth/context.js.map +1 -0
- package/build/auth/exports.d.ts +25 -0
- package/build/auth/exports.d.ts.map +1 -0
- package/build/auth/exports.js +19 -0
- package/build/auth/exports.js.map +1 -0
- package/build/auth/index.d.ts +5 -0
- package/build/auth/index.d.ts.map +1 -0
- package/build/auth/index.js +4 -0
- package/build/auth/index.js.map +1 -0
- package/build/auth/modules.d.ts +2 -0
- package/build/auth/modules.d.ts.map +1 -0
- package/build/auth/modules.js +4 -0
- package/build/auth/modules.js.map +1 -0
- package/build/auth/plugins/basic-ed25519.d.ts +3 -0
- package/build/auth/plugins/basic-ed25519.d.ts.map +1 -0
- package/build/auth/plugins/basic-ed25519.js +40 -0
- package/build/auth/plugins/basic-ed25519.js.map +1 -0
- package/build/auth/plugins/exports.d.ts +4 -0
- package/build/auth/plugins/exports.d.ts.map +1 -0
- package/build/auth/plugins/exports.js +4 -0
- package/build/auth/plugins/exports.js.map +1 -0
- package/build/auth/plugins/index.d.ts +6 -0
- package/build/auth/plugins/index.d.ts.map +1 -0
- package/build/auth/plugins/index.js +10 -0
- package/build/auth/plugins/index.js.map +1 -0
- package/build/auth/plugins/re-captcha.d.ts +3 -0
- package/build/auth/plugins/re-captcha.d.ts.map +1 -0
- package/build/auth/plugins/re-captcha.js +51 -0
- package/build/auth/plugins/re-captcha.js.map +1 -0
- package/build/auth/plugins/tunnel-consumer.d.ts +3 -0
- package/build/auth/plugins/tunnel-consumer.d.ts.map +1 -0
- package/build/auth/plugins/tunnel-consumer.js +40 -0
- package/build/auth/plugins/tunnel-consumer.js.map +1 -0
- package/build/auth/types.d.ts +10 -0
- package/build/auth/types.d.ts.map +1 -0
- package/build/auth/types.js +2 -0
- package/build/auth/types.js.map +1 -0
- package/build/components/block.d.ts +4 -0
- package/build/components/block.d.ts.map +1 -0
- package/build/components/block.js +15 -0
- package/build/components/block.js.map +1 -0
- package/build/components/button/index.d.ts +3 -0
- package/build/components/button/index.d.ts.map +1 -0
- package/build/components/button/index.js +2 -0
- package/build/components/button/index.js.map +1 -0
- package/build/components/button/selector.d.ts +4 -0
- package/build/components/button/selector.d.ts.map +1 -0
- package/build/components/button/selector.js +8 -0
- package/build/components/button/selector.js.map +1 -0
- package/build/components/button/types.d.ts +7 -0
- package/build/components/button/types.d.ts.map +1 -0
- package/build/components/button/types.js +2 -0
- package/build/components/button/types.js.map +1 -0
- package/build/components/form/button/component.d.ts +5 -0
- package/build/components/form/button/component.d.ts.map +1 -0
- package/build/components/form/button/component.js +34 -0
- package/build/components/form/button/component.js.map +1 -0
- package/build/components/form/button/index.d.ts +3 -0
- package/build/components/form/button/index.d.ts.map +1 -0
- package/build/components/form/button/index.js +3 -0
- package/build/components/form/button/index.js.map +1 -0
- package/build/components/form/button/types.d.ts +15 -0
- package/build/components/form/button/types.d.ts.map +1 -0
- package/build/components/form/button/types.js +2 -0
- package/build/components/form/button/types.js.map +1 -0
- package/build/components/form/component.d.ts +4 -0
- package/build/components/form/component.d.ts.map +1 -0
- package/build/components/form/component.js +59 -0
- package/build/components/form/component.js.map +1 -0
- package/build/components/form/index.d.ts +3 -0
- package/build/components/form/index.d.ts.map +1 -0
- package/build/components/form/index.js +2 -0
- package/build/components/form/index.js.map +1 -0
- package/build/components/form/text/component.d.ts +4 -0
- package/build/components/form/text/component.d.ts.map +1 -0
- package/build/components/form/text/component.js +34 -0
- package/build/components/form/text/component.js.map +1 -0
- package/build/components/form/text/index.d.ts +3 -0
- package/build/components/form/text/index.d.ts.map +1 -0
- package/build/components/form/text/index.js +3 -0
- package/build/components/form/text/index.js.map +1 -0
- package/build/components/form/text/types.d.ts +11 -0
- package/build/components/form/text/types.d.ts.map +1 -0
- package/build/components/form/text/types.js +2 -0
- package/build/components/form/text/types.js.map +1 -0
- package/build/components/form/types.d.ts +6 -0
- package/build/components/form/types.d.ts.map +1 -0
- package/build/components/form/types.js +2 -0
- package/build/components/form/types.js.map +1 -0
- package/build/components/helper.d.ts +6 -0
- package/build/components/helper.d.ts.map +1 -0
- package/build/components/helper.js +70 -0
- package/build/components/helper.js.map +1 -0
- package/build/components/index.d.ts +14 -0
- package/build/components/index.d.ts.map +1 -0
- package/build/components/index.js +13 -0
- package/build/components/index.js.map +1 -0
- package/build/components/layout/component.d.ts +4 -0
- package/build/components/layout/component.d.ts.map +1 -0
- package/build/components/layout/component.js +6 -0
- package/build/components/layout/component.js.map +1 -0
- package/build/components/layout/index.d.ts +3 -0
- package/build/components/layout/index.d.ts.map +1 -0
- package/build/components/layout/index.js +3 -0
- package/build/components/layout/index.js.map +1 -0
- package/build/components/layout/types.d.ts +4 -0
- package/build/components/layout/types.d.ts.map +1 -0
- package/build/components/layout/types.js +2 -0
- package/build/components/layout/types.js.map +1 -0
- package/build/components/link.d.ts +4 -0
- package/build/components/link.d.ts.map +1 -0
- package/build/components/link.js +27 -0
- package/build/components/link.js.map +1 -0
- package/build/components/panel-app/component.d.ts +4 -0
- package/build/components/panel-app/component.d.ts.map +1 -0
- package/build/components/panel-app/component.js +12 -0
- package/build/components/panel-app/component.js.map +1 -0
- package/build/components/panel-app/index.d.ts +3 -0
- package/build/components/panel-app/index.d.ts.map +1 -0
- package/build/components/panel-app/index.js +3 -0
- package/build/components/panel-app/index.js.map +1 -0
- package/build/components/panel-app/types.d.ts +6 -0
- package/build/components/panel-app/types.d.ts.map +1 -0
- package/build/components/panel-app/types.js +2 -0
- package/build/components/panel-app/types.js.map +1 -0
- package/build/components/status.d.ts +4 -0
- package/build/components/status.d.ts.map +1 -0
- package/build/components/status.js +19 -0
- package/build/components/status.js.map +1 -0
- package/build/components/text.d.ts +4 -0
- package/build/components/text.d.ts.map +1 -0
- package/build/components/text.js +9 -0
- package/build/components/text.js.map +1 -0
- package/build/components/types.d.ts +32 -0
- package/build/components/types.d.ts.map +1 -0
- package/build/components/types.js +2 -0
- package/build/components/types.js.map +1 -0
- package/build/components/uploader/image.d.ts +4 -0
- package/build/components/uploader/image.d.ts.map +1 -0
- package/build/components/uploader/image.js +24 -0
- package/build/components/uploader/image.js.map +1 -0
- package/build/components/uploader/index.d.ts +2 -0
- package/build/components/uploader/index.d.ts.map +1 -0
- package/build/components/uploader/index.js +2 -0
- package/build/components/uploader/index.js.map +1 -0
- package/build/components/uploader/types.d.ts +5 -0
- package/build/components/uploader/types.d.ts.map +1 -0
- package/build/components/uploader/types.js +2 -0
- package/build/components/uploader/types.js.map +1 -0
- package/build/context.d.ts +4 -0
- package/build/context.d.ts.map +1 -0
- package/build/context.js +12 -0
- package/build/context.js.map +1 -0
- package/build/exports.d.ts +19 -0
- package/build/exports.d.ts.map +1 -0
- package/build/exports.js +17 -0
- package/build/exports.js.map +1 -0
- package/build/index.d.ts +8 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/build/main.d.ts +5 -0
- package/build/main.d.ts.map +1 -0
- package/build/main.js +14 -0
- package/build/main.js.map +1 -0
- package/build/modules.d.ts +2 -0
- package/build/modules.d.ts.map +1 -0
- package/build/modules.js +4 -0
- package/build/modules.js.map +1 -0
- package/build/types.d.ts +9 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +89 -0
- package/src/auth/context.ts +25 -0
- package/src/auth/exports.ts +29 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/modules.ts +5 -0
- package/src/auth/plugins/basic-ed25519.tsx +57 -0
- package/src/auth/plugins/exports.ts +4 -0
- package/src/auth/plugins/index.ts +15 -0
- package/src/auth/plugins/re-captcha.tsx +73 -0
- package/src/auth/plugins/tunnel-consumer.tsx +65 -0
- package/src/auth/types.ts +11 -0
- package/src/components/block.tsx +27 -0
- package/src/components/button/index.ts +3 -0
- package/src/components/button/selector.tsx +16 -0
- package/src/components/button/types.ts +7 -0
- package/src/components/form/button/component.tsx +53 -0
- package/src/components/form/button/index.ts +3 -0
- package/src/components/form/button/types.ts +16 -0
- package/src/components/form/component.tsx +101 -0
- package/src/components/form/index.ts +3 -0
- package/src/components/form/text/component.tsx +45 -0
- package/src/components/form/text/index.ts +3 -0
- package/src/components/form/text/types.ts +11 -0
- package/src/components/form/types.ts +6 -0
- package/src/components/helper.ts +79 -0
- package/src/components/index.ts +17 -0
- package/src/components/layout/component.tsx +7 -0
- package/src/components/layout/index.ts +3 -0
- package/src/components/layout/types.ts +4 -0
- package/src/components/link.tsx +34 -0
- package/src/components/panel-app/component.tsx +21 -0
- package/src/components/panel-app/index.ts +3 -0
- package/src/components/panel-app/types.ts +6 -0
- package/src/components/status.tsx +23 -0
- package/src/components/text.tsx +14 -0
- package/src/components/types.ts +35 -0
- package/src/components/uploader/image.tsx +31 -0
- package/src/components/uploader/index.ts +2 -0
- package/src/components/uploader/types.ts +6 -0
- package/src/context.ts +18 -0
- package/src/exports.ts +22 -0
- package/src/index.ts +9 -0
- package/src/main.tsx +19 -0
- package/src/modules.ts +5 -0
- package/src/types.ts +11 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,1195 @@
|
|
|
1
|
+
# @owlmeans/web-panel
|
|
2
|
+
|
|
3
|
+
Web-based panel components and infrastructure for OwlMeans Common Libraries. This package extends the `@owlmeans/client-panel` functionality with React web components, Material-UI integration, and web-specific implementations for building comprehensive administrative interfaces and user panels.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@owlmeans/web-panel` package serves as the web-specific implementation of the OwlMeans panel system, designed for fullstack applications with focus on security and modern web user interfaces. It provides:
|
|
8
|
+
|
|
9
|
+
- **Material-UI Integration**: Complete Material Design component system with theming
|
|
10
|
+
- **Web Panel Components**: Comprehensive React components for building web admin panels
|
|
11
|
+
- **Authentication Integration**: Built-in authentication components and flows
|
|
12
|
+
- **Form Management**: Advanced form components with validation and error handling
|
|
13
|
+
- **File Upload**: File and media upload components with progress tracking
|
|
14
|
+
- **Layout System**: Flexible layout components for organizing panel content
|
|
15
|
+
- **Theme Customization**: Material-UI theme integration with customizable styling
|
|
16
|
+
- **Internationalization**: Built-in i18n support with browser language detection
|
|
17
|
+
|
|
18
|
+
This package follows the OwlMeans "quadra" pattern as the **web** implementation, complementing:
|
|
19
|
+
- **@owlmeans/client-panel**: Common panel declarations and base functionality *(base package)*
|
|
20
|
+
- **@owlmeans/native-panel**: React Native panel implementation
|
|
21
|
+
- **@owlmeans/web-panel**: Web browser panel implementation *(this package)*
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @owlmeans/web-panel
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Peer Dependencies
|
|
30
|
+
|
|
31
|
+
This package requires several peer dependencies for web development:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install react react-dom @mui/material @emotion/react @emotion/styled react-hook-form @hookform/resolvers ajv
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Dependencies
|
|
38
|
+
|
|
39
|
+
This package builds upon the OwlMeans ecosystem and web technologies:
|
|
40
|
+
- `@owlmeans/client-panel`: Base panel functionality and context
|
|
41
|
+
- `@owlmeans/web-client`: Web client framework and rendering
|
|
42
|
+
- `@owlmeans/client-auth`: Authentication components and services
|
|
43
|
+
- `@owlmeans/client-flow`: Flow management integration
|
|
44
|
+
- `@owlmeans/web-flow`: Web-specific flow implementations
|
|
45
|
+
- Material-UI: React component library for Material Design
|
|
46
|
+
- React Hook Form: Form management and validation
|
|
47
|
+
|
|
48
|
+
## Key Concepts
|
|
49
|
+
|
|
50
|
+
### Material-UI Integration
|
|
51
|
+
Built on Material-UI (MUI) providing:
|
|
52
|
+
- **Material Design**: Modern Material Design 3 component system
|
|
53
|
+
- **Theme System**: Comprehensive theming with custom color schemes
|
|
54
|
+
- **Responsive Design**: Mobile-first responsive components
|
|
55
|
+
- **Accessibility**: Built-in accessibility support and ARIA compliance
|
|
56
|
+
|
|
57
|
+
### Panel Architecture
|
|
58
|
+
Structured panel system with:
|
|
59
|
+
- **Panel Context**: Shared context for panel-wide settings and state
|
|
60
|
+
- **Authentication Guards**: Built-in authentication and authorization
|
|
61
|
+
- **Layout Management**: Flexible layout system for complex interfaces
|
|
62
|
+
- **Navigation Integration**: Integration with React Router and client routing
|
|
63
|
+
|
|
64
|
+
### Form Management
|
|
65
|
+
Advanced form capabilities with:
|
|
66
|
+
- **React Hook Form**: Performance-optimized form management
|
|
67
|
+
- **AJV Validation**: Schema-based validation using JSON Schema
|
|
68
|
+
- **Error Handling**: Comprehensive error display and user feedback
|
|
69
|
+
- **File Uploads**: Integrated file and media upload components
|
|
70
|
+
|
|
71
|
+
## API Reference
|
|
72
|
+
|
|
73
|
+
### Main Functions
|
|
74
|
+
|
|
75
|
+
#### `render<C, T>(context, theme?, opts?): void`
|
|
76
|
+
|
|
77
|
+
Main rendering function for web panel applications.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
function render<C extends ClientConfig, T extends ClientContext<C>>(
|
|
81
|
+
context: T,
|
|
82
|
+
theme?: Theme,
|
|
83
|
+
opts?: RenderOptions
|
|
84
|
+
): void
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Parameters:**
|
|
88
|
+
- `context`: Client context with configuration and services
|
|
89
|
+
- `theme`: Optional Material-UI theme customization
|
|
90
|
+
- `opts`: Optional rendering options
|
|
91
|
+
|
|
92
|
+
**Usage:**
|
|
93
|
+
```typescript
|
|
94
|
+
import { render } from '@owlmeans/web-panel'
|
|
95
|
+
import { makeWebContext } from '@owlmeans/web-client'
|
|
96
|
+
import { createTheme } from '@mui/material/styles'
|
|
97
|
+
|
|
98
|
+
const context = makeWebContext(config)
|
|
99
|
+
const theme = createTheme({
|
|
100
|
+
palette: {
|
|
101
|
+
primary: { main: '#1976d2' },
|
|
102
|
+
secondary: { main: '#dc004e' }
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
render(context, theme, { rootId: 'app' })
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Core Components
|
|
110
|
+
|
|
111
|
+
#### `PanelApp`
|
|
112
|
+
|
|
113
|
+
Main panel application wrapper with Material-UI theming and context integration.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
interface PanelAppProps {
|
|
117
|
+
context: AppContext<any> // Application context
|
|
118
|
+
provide?: ProvideFunction // Custom provider function
|
|
119
|
+
children?: React.ReactNode // Panel content
|
|
120
|
+
theme?: Theme // Material-UI theme
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const PanelApp: FC<PanelAppProps> = (props) => { /* ... */ }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Features:**
|
|
127
|
+
- Material-UI theme provider integration
|
|
128
|
+
- CSS baseline normalization
|
|
129
|
+
- I18n context with browser language detection
|
|
130
|
+
- OwlMeans client app wrapper
|
|
131
|
+
|
|
132
|
+
**Usage:**
|
|
133
|
+
```typescript
|
|
134
|
+
import { PanelApp } from '@owlmeans/web-panel'
|
|
135
|
+
|
|
136
|
+
<PanelApp context={context} theme={customTheme}>
|
|
137
|
+
<AdminInterface />
|
|
138
|
+
</PanelApp>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Layout Components
|
|
142
|
+
|
|
143
|
+
#### Panel Layout System
|
|
144
|
+
|
|
145
|
+
Flexible layout components for organizing panel content.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import {
|
|
149
|
+
PanelLayout,
|
|
150
|
+
PanelHeader,
|
|
151
|
+
PanelSidebar,
|
|
152
|
+
PanelContent,
|
|
153
|
+
PanelFooter
|
|
154
|
+
} from '@owlmeans/web-panel'
|
|
155
|
+
|
|
156
|
+
<PanelLayout>
|
|
157
|
+
<PanelHeader title="Administration Panel">
|
|
158
|
+
<UserMenu />
|
|
159
|
+
</PanelHeader>
|
|
160
|
+
|
|
161
|
+
<PanelSidebar>
|
|
162
|
+
<NavigationMenu />
|
|
163
|
+
</PanelSidebar>
|
|
164
|
+
|
|
165
|
+
<PanelContent>
|
|
166
|
+
<MainContent />
|
|
167
|
+
</PanelContent>
|
|
168
|
+
|
|
169
|
+
<PanelFooter>
|
|
170
|
+
<StatusBar />
|
|
171
|
+
</PanelFooter>
|
|
172
|
+
</PanelLayout>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Form Components
|
|
176
|
+
|
|
177
|
+
#### Advanced Form System
|
|
178
|
+
|
|
179
|
+
React Hook Form integration with Material-UI components and AJV validation.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import {
|
|
183
|
+
PanelForm,
|
|
184
|
+
FormField,
|
|
185
|
+
FormButton,
|
|
186
|
+
FormSelect,
|
|
187
|
+
FormCheckbox,
|
|
188
|
+
FormValidation
|
|
189
|
+
} from '@owlmeans/web-panel'
|
|
190
|
+
|
|
191
|
+
interface UserFormData {
|
|
192
|
+
name: string
|
|
193
|
+
email: string
|
|
194
|
+
role: string
|
|
195
|
+
active: boolean
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const userSchema = {
|
|
199
|
+
type: 'object',
|
|
200
|
+
properties: {
|
|
201
|
+
name: { type: 'string', minLength: 2 },
|
|
202
|
+
email: { type: 'string', format: 'email' },
|
|
203
|
+
role: { type: 'string', enum: ['admin', 'user'] },
|
|
204
|
+
active: { type: 'boolean' }
|
|
205
|
+
},
|
|
206
|
+
required: ['name', 'email', 'role']
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
<PanelForm<UserFormData>
|
|
210
|
+
schema={userSchema}
|
|
211
|
+
onSubmit={handleSubmit}
|
|
212
|
+
defaultValues={defaultValues}
|
|
213
|
+
>
|
|
214
|
+
<FormField
|
|
215
|
+
name="name"
|
|
216
|
+
label="Full Name"
|
|
217
|
+
type="text"
|
|
218
|
+
required
|
|
219
|
+
/>
|
|
220
|
+
|
|
221
|
+
<FormField
|
|
222
|
+
name="email"
|
|
223
|
+
label="Email Address"
|
|
224
|
+
type="email"
|
|
225
|
+
required
|
|
226
|
+
/>
|
|
227
|
+
|
|
228
|
+
<FormSelect
|
|
229
|
+
name="role"
|
|
230
|
+
label="User Role"
|
|
231
|
+
options={[
|
|
232
|
+
{ value: 'admin', label: 'Administrator' },
|
|
233
|
+
{ value: 'user', label: 'Regular User' }
|
|
234
|
+
]}
|
|
235
|
+
required
|
|
236
|
+
/>
|
|
237
|
+
|
|
238
|
+
<FormCheckbox
|
|
239
|
+
name="active"
|
|
240
|
+
label="Active User"
|
|
241
|
+
/>
|
|
242
|
+
|
|
243
|
+
<FormButton type="submit" variant="contained">
|
|
244
|
+
Save User
|
|
245
|
+
</FormButton>
|
|
246
|
+
</PanelForm>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Button Components
|
|
250
|
+
|
|
251
|
+
Enhanced button components with loading states and Material-UI integration.
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { PanelButton, IconButton, FloatingActionButton } from '@owlmeans/web-panel'
|
|
255
|
+
|
|
256
|
+
<PanelButton
|
|
257
|
+
variant="contained"
|
|
258
|
+
color="primary"
|
|
259
|
+
startIcon={<SaveIcon />}
|
|
260
|
+
loading={isSaving}
|
|
261
|
+
onClick={handleSave}
|
|
262
|
+
>
|
|
263
|
+
Save Changes
|
|
264
|
+
</PanelButton>
|
|
265
|
+
|
|
266
|
+
<IconButton
|
|
267
|
+
color="secondary"
|
|
268
|
+
onClick={handleEdit}
|
|
269
|
+
tooltip="Edit Item"
|
|
270
|
+
>
|
|
271
|
+
<EditIcon />
|
|
272
|
+
</IconButton>
|
|
273
|
+
|
|
274
|
+
<FloatingActionButton
|
|
275
|
+
color="primary"
|
|
276
|
+
onClick={handleAdd}
|
|
277
|
+
position="bottom-right"
|
|
278
|
+
>
|
|
279
|
+
<AddIcon />
|
|
280
|
+
</FloatingActionButton>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### File Upload Components
|
|
284
|
+
|
|
285
|
+
Comprehensive file upload system with progress tracking and validation.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import {
|
|
289
|
+
FileUploader,
|
|
290
|
+
ImageUploader,
|
|
291
|
+
MultiFileUploader,
|
|
292
|
+
DropZone
|
|
293
|
+
} from '@owlmeans/web-panel'
|
|
294
|
+
|
|
295
|
+
<FileUploader
|
|
296
|
+
accept=".pdf,.doc,.docx"
|
|
297
|
+
maxSize={10 * 1024 * 1024} // 10MB
|
|
298
|
+
onUpload={handleFileUpload}
|
|
299
|
+
onProgress={handleProgress}
|
|
300
|
+
onError={handleError}
|
|
301
|
+
/>
|
|
302
|
+
|
|
303
|
+
<ImageUploader
|
|
304
|
+
accept="image/*"
|
|
305
|
+
maxSize={5 * 1024 * 1024} // 5MB
|
|
306
|
+
cropAspectRatio={16/9}
|
|
307
|
+
onUpload={handleImageUpload}
|
|
308
|
+
preview={true}
|
|
309
|
+
/>
|
|
310
|
+
|
|
311
|
+
<MultiFileUploader
|
|
312
|
+
maxFiles={5}
|
|
313
|
+
accept="*/*"
|
|
314
|
+
onUploadAll={handleMultipleUpload}
|
|
315
|
+
showProgress={true}
|
|
316
|
+
/>
|
|
317
|
+
|
|
318
|
+
<DropZone
|
|
319
|
+
onDrop={handleFileDrop}
|
|
320
|
+
accept=".zip,.tar.gz"
|
|
321
|
+
multiple={false}
|
|
322
|
+
>
|
|
323
|
+
Drop files here or click to browse
|
|
324
|
+
</DropZone>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Text and Display Components
|
|
328
|
+
|
|
329
|
+
Typography and content display components.
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import {
|
|
333
|
+
PanelText,
|
|
334
|
+
PanelLink,
|
|
335
|
+
StatusIndicator,
|
|
336
|
+
InfoBlock
|
|
337
|
+
} from '@owlmeans/web-panel'
|
|
338
|
+
|
|
339
|
+
<PanelText variant="h4" color="primary" gutterBottom>
|
|
340
|
+
Panel Title
|
|
341
|
+
</PanelText>
|
|
342
|
+
|
|
343
|
+
<PanelLink href="/admin/users" external={false}>
|
|
344
|
+
Manage Users
|
|
345
|
+
</PanelLink>
|
|
346
|
+
|
|
347
|
+
<StatusIndicator
|
|
348
|
+
status="success"
|
|
349
|
+
message="Operation completed successfully"
|
|
350
|
+
/>
|
|
351
|
+
|
|
352
|
+
<InfoBlock
|
|
353
|
+
title="Important Notice"
|
|
354
|
+
severity="warning"
|
|
355
|
+
actions={[
|
|
356
|
+
{ label: 'Dismiss', onClick: handleDismiss },
|
|
357
|
+
{ label: 'Learn More', onClick: openDetails }
|
|
358
|
+
]}
|
|
359
|
+
>
|
|
360
|
+
This action cannot be undone.
|
|
361
|
+
</InfoBlock>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Authentication Components
|
|
365
|
+
|
|
366
|
+
Built-in authentication UI components and flows.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import {
|
|
370
|
+
LoginForm,
|
|
371
|
+
LogoutButton,
|
|
372
|
+
UserProfile,
|
|
373
|
+
AuthGuard,
|
|
374
|
+
PermissionGuard
|
|
375
|
+
} from '@owlmeans/web-panel/auth'
|
|
376
|
+
|
|
377
|
+
<AuthGuard fallback={<LoginForm />}>
|
|
378
|
+
<PanelApp context={context}>
|
|
379
|
+
<PermissionGuard permissions={['admin']}>
|
|
380
|
+
<AdminPanel />
|
|
381
|
+
</PermissionGuard>
|
|
382
|
+
</PanelApp>
|
|
383
|
+
</AuthGuard>
|
|
384
|
+
|
|
385
|
+
<LoginForm
|
|
386
|
+
onSuccess={handleLoginSuccess}
|
|
387
|
+
onError={handleLoginError}
|
|
388
|
+
showRegister={true}
|
|
389
|
+
showForgotPassword={true}
|
|
390
|
+
/>
|
|
391
|
+
|
|
392
|
+
<UserProfile
|
|
393
|
+
showAvatar={true}
|
|
394
|
+
showMenu={true}
|
|
395
|
+
menuActions={[
|
|
396
|
+
{ label: 'Settings', onClick: openSettings },
|
|
397
|
+
{ label: 'Logout', onClick: handleLogout }
|
|
398
|
+
]}
|
|
399
|
+
/>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Usage Examples
|
|
403
|
+
|
|
404
|
+
### Complete Panel Application
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import React from 'react'
|
|
408
|
+
import { render } from '@owlmeans/web-panel'
|
|
409
|
+
import { makeWebContext } from '@owlmeans/web-client'
|
|
410
|
+
import { createTheme } from '@mui/material/styles'
|
|
411
|
+
import {
|
|
412
|
+
PanelApp,
|
|
413
|
+
PanelLayout,
|
|
414
|
+
PanelHeader,
|
|
415
|
+
PanelSidebar,
|
|
416
|
+
PanelContent,
|
|
417
|
+
AuthGuard
|
|
418
|
+
} from '@owlmeans/web-panel'
|
|
419
|
+
|
|
420
|
+
const config = {
|
|
421
|
+
service: 'admin-panel',
|
|
422
|
+
type: AppType.Frontend,
|
|
423
|
+
layer: Layer.Service
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const theme = createTheme({
|
|
427
|
+
palette: {
|
|
428
|
+
mode: 'light',
|
|
429
|
+
primary: {
|
|
430
|
+
main: '#1976d2',
|
|
431
|
+
contrastText: '#ffffff'
|
|
432
|
+
},
|
|
433
|
+
secondary: {
|
|
434
|
+
main: '#dc004e'
|
|
435
|
+
},
|
|
436
|
+
background: {
|
|
437
|
+
default: '#f5f5f5',
|
|
438
|
+
paper: '#ffffff'
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
typography: {
|
|
442
|
+
fontFamily: 'Roboto, Arial, sans-serif',
|
|
443
|
+
h1: {
|
|
444
|
+
fontSize: '2.5rem',
|
|
445
|
+
fontWeight: 500
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
shape: {
|
|
449
|
+
borderRadius: 8
|
|
450
|
+
}
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
const AdminPanelApp: React.FC = () => {
|
|
454
|
+
const context = makeWebContext(config)
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
<PanelApp context={context} theme={theme}>
|
|
458
|
+
<AuthGuard>
|
|
459
|
+
<PanelLayout>
|
|
460
|
+
<PanelHeader title="Administration Panel">
|
|
461
|
+
<UserProfile />
|
|
462
|
+
</PanelHeader>
|
|
463
|
+
|
|
464
|
+
<PanelSidebar>
|
|
465
|
+
<AdminNavigation />
|
|
466
|
+
</PanelSidebar>
|
|
467
|
+
|
|
468
|
+
<PanelContent>
|
|
469
|
+
<AdminRouter />
|
|
470
|
+
</PanelContent>
|
|
471
|
+
</PanelLayout>
|
|
472
|
+
</AuthGuard>
|
|
473
|
+
</PanelApp>
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Render the application
|
|
478
|
+
render(makeWebContext(config), theme)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### User Management Interface
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
import React, { useState, useEffect } from 'react'
|
|
485
|
+
import {
|
|
486
|
+
PanelForm,
|
|
487
|
+
FormField,
|
|
488
|
+
FormSelect,
|
|
489
|
+
FormButton,
|
|
490
|
+
PanelButton,
|
|
491
|
+
StatusIndicator,
|
|
492
|
+
InfoBlock
|
|
493
|
+
} from '@owlmeans/web-panel'
|
|
494
|
+
import { Grid, Card, CardContent, Typography } from '@mui/material'
|
|
495
|
+
|
|
496
|
+
interface User {
|
|
497
|
+
id: string
|
|
498
|
+
name: string
|
|
499
|
+
email: string
|
|
500
|
+
role: string
|
|
501
|
+
active: boolean
|
|
502
|
+
createdAt: Date
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const UserManagement: React.FC = () => {
|
|
506
|
+
const [users, setUsers] = useState<User[]>([])
|
|
507
|
+
const [loading, setLoading] = useState(true)
|
|
508
|
+
const [editing, setEditing] = useState<User | null>(null)
|
|
509
|
+
const [error, setError] = useState<string | null>(null)
|
|
510
|
+
|
|
511
|
+
useEffect(() => {
|
|
512
|
+
loadUsers()
|
|
513
|
+
}, [])
|
|
514
|
+
|
|
515
|
+
const loadUsers = async () => {
|
|
516
|
+
try {
|
|
517
|
+
setLoading(true)
|
|
518
|
+
const response = await fetch('/api/users')
|
|
519
|
+
const userData = await response.json()
|
|
520
|
+
setUsers(userData)
|
|
521
|
+
} catch (err) {
|
|
522
|
+
setError('Failed to load users')
|
|
523
|
+
} finally {
|
|
524
|
+
setLoading(false)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const handleUserSave = async (userData: Partial<User>) => {
|
|
529
|
+
try {
|
|
530
|
+
const url = editing ? `/api/users/${editing.id}` : '/api/users'
|
|
531
|
+
const method = editing ? 'PUT' : 'POST'
|
|
532
|
+
|
|
533
|
+
const response = await fetch(url, {
|
|
534
|
+
method,
|
|
535
|
+
headers: { 'Content-Type': 'application/json' },
|
|
536
|
+
body: JSON.stringify(userData)
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
if (response.ok) {
|
|
540
|
+
await loadUsers()
|
|
541
|
+
setEditing(null)
|
|
542
|
+
} else {
|
|
543
|
+
setError('Failed to save user')
|
|
544
|
+
}
|
|
545
|
+
} catch (err) {
|
|
546
|
+
setError('Network error')
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const handleUserDelete = async (userId: string) => {
|
|
551
|
+
if (!confirm('Are you sure you want to delete this user?')) return
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
const response = await fetch(`/api/users/${userId}`, { method: 'DELETE' })
|
|
555
|
+
if (response.ok) {
|
|
556
|
+
await loadUsers()
|
|
557
|
+
} else {
|
|
558
|
+
setError('Failed to delete user')
|
|
559
|
+
}
|
|
560
|
+
} catch (err) {
|
|
561
|
+
setError('Network error')
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const userSchema = {
|
|
566
|
+
type: 'object',
|
|
567
|
+
properties: {
|
|
568
|
+
name: { type: 'string', minLength: 2, maxLength: 100 },
|
|
569
|
+
email: { type: 'string', format: 'email' },
|
|
570
|
+
role: { type: 'string', enum: ['admin', 'user', 'moderator'] },
|
|
571
|
+
active: { type: 'boolean' }
|
|
572
|
+
},
|
|
573
|
+
required: ['name', 'email', 'role']
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (loading) {
|
|
577
|
+
return <StatusIndicator status="loading" message="Loading users..." />
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return (
|
|
581
|
+
<Grid container spacing={3}>
|
|
582
|
+
<Grid item xs={12}>
|
|
583
|
+
<Typography variant="h4" gutterBottom>
|
|
584
|
+
User Management
|
|
585
|
+
</Typography>
|
|
586
|
+
|
|
587
|
+
{error && (
|
|
588
|
+
<InfoBlock severity="error" onClose={() => setError(null)}>
|
|
589
|
+
{error}
|
|
590
|
+
</InfoBlock>
|
|
591
|
+
)}
|
|
592
|
+
</Grid>
|
|
593
|
+
|
|
594
|
+
<Grid item xs={12} md={8}>
|
|
595
|
+
<Card>
|
|
596
|
+
<CardContent>
|
|
597
|
+
<Typography variant="h6" gutterBottom>
|
|
598
|
+
Users
|
|
599
|
+
</Typography>
|
|
600
|
+
|
|
601
|
+
<PanelButton
|
|
602
|
+
variant="contained"
|
|
603
|
+
color="primary"
|
|
604
|
+
onClick={() => setEditing({} as User)}
|
|
605
|
+
sx={{ mb: 2 }}
|
|
606
|
+
>
|
|
607
|
+
Add New User
|
|
608
|
+
</PanelButton>
|
|
609
|
+
|
|
610
|
+
{users.map(user => (
|
|
611
|
+
<Card key={user.id} variant="outlined" sx={{ mb: 1 }}>
|
|
612
|
+
<CardContent>
|
|
613
|
+
<Grid container alignItems="center" spacing={2}>
|
|
614
|
+
<Grid item xs>
|
|
615
|
+
<Typography variant="subtitle1">{user.name}</Typography>
|
|
616
|
+
<Typography variant="body2" color="textSecondary">
|
|
617
|
+
{user.email} • {user.role}
|
|
618
|
+
</Typography>
|
|
619
|
+
</Grid>
|
|
620
|
+
<Grid item>
|
|
621
|
+
<StatusIndicator
|
|
622
|
+
status={user.active ? 'success' : 'warning'}
|
|
623
|
+
message={user.active ? 'Active' : 'Inactive'}
|
|
624
|
+
/>
|
|
625
|
+
</Grid>
|
|
626
|
+
<Grid item>
|
|
627
|
+
<PanelButton
|
|
628
|
+
size="small"
|
|
629
|
+
onClick={() => setEditing(user)}
|
|
630
|
+
>
|
|
631
|
+
Edit
|
|
632
|
+
</PanelButton>
|
|
633
|
+
<PanelButton
|
|
634
|
+
size="small"
|
|
635
|
+
color="error"
|
|
636
|
+
onClick={() => handleUserDelete(user.id)}
|
|
637
|
+
sx={{ ml: 1 }}
|
|
638
|
+
>
|
|
639
|
+
Delete
|
|
640
|
+
</PanelButton>
|
|
641
|
+
</Grid>
|
|
642
|
+
</Grid>
|
|
643
|
+
</CardContent>
|
|
644
|
+
</Card>
|
|
645
|
+
))}
|
|
646
|
+
</CardContent>
|
|
647
|
+
</Card>
|
|
648
|
+
</Grid>
|
|
649
|
+
|
|
650
|
+
<Grid item xs={12} md={4}>
|
|
651
|
+
{editing && (
|
|
652
|
+
<Card>
|
|
653
|
+
<CardContent>
|
|
654
|
+
<Typography variant="h6" gutterBottom>
|
|
655
|
+
{editing.id ? 'Edit User' : 'Add User'}
|
|
656
|
+
</Typography>
|
|
657
|
+
|
|
658
|
+
<PanelForm
|
|
659
|
+
schema={userSchema}
|
|
660
|
+
defaultValues={editing}
|
|
661
|
+
onSubmit={handleUserSave}
|
|
662
|
+
>
|
|
663
|
+
<FormField
|
|
664
|
+
name="name"
|
|
665
|
+
label="Full Name"
|
|
666
|
+
required
|
|
667
|
+
fullWidth
|
|
668
|
+
margin="normal"
|
|
669
|
+
/>
|
|
670
|
+
|
|
671
|
+
<FormField
|
|
672
|
+
name="email"
|
|
673
|
+
label="Email Address"
|
|
674
|
+
type="email"
|
|
675
|
+
required
|
|
676
|
+
fullWidth
|
|
677
|
+
margin="normal"
|
|
678
|
+
/>
|
|
679
|
+
|
|
680
|
+
<FormSelect
|
|
681
|
+
name="role"
|
|
682
|
+
label="Role"
|
|
683
|
+
required
|
|
684
|
+
fullWidth
|
|
685
|
+
margin="normal"
|
|
686
|
+
options={[
|
|
687
|
+
{ value: 'user', label: 'Regular User' },
|
|
688
|
+
{ value: 'moderator', label: 'Moderator' },
|
|
689
|
+
{ value: 'admin', label: 'Administrator' }
|
|
690
|
+
]}
|
|
691
|
+
/>
|
|
692
|
+
|
|
693
|
+
<FormField
|
|
694
|
+
name="active"
|
|
695
|
+
label="Active User"
|
|
696
|
+
type="checkbox"
|
|
697
|
+
margin="normal"
|
|
698
|
+
/>
|
|
699
|
+
|
|
700
|
+
<Grid container spacing={2} sx={{ mt: 2 }}>
|
|
701
|
+
<Grid item>
|
|
702
|
+
<FormButton
|
|
703
|
+
type="submit"
|
|
704
|
+
variant="contained"
|
|
705
|
+
color="primary"
|
|
706
|
+
>
|
|
707
|
+
{editing.id ? 'Update' : 'Create'}
|
|
708
|
+
</FormButton>
|
|
709
|
+
</Grid>
|
|
710
|
+
<Grid item>
|
|
711
|
+
<PanelButton
|
|
712
|
+
variant="outlined"
|
|
713
|
+
onClick={() => setEditing(null)}
|
|
714
|
+
>
|
|
715
|
+
Cancel
|
|
716
|
+
</PanelButton>
|
|
717
|
+
</Grid>
|
|
718
|
+
</Grid>
|
|
719
|
+
</PanelForm>
|
|
720
|
+
</CardContent>
|
|
721
|
+
</Card>
|
|
722
|
+
)}
|
|
723
|
+
</Grid>
|
|
724
|
+
</Grid>
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
export default UserManagement
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
### File Upload Interface
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
import React, { useState } from 'react'
|
|
735
|
+
import {
|
|
736
|
+
FileUploader,
|
|
737
|
+
ImageUploader,
|
|
738
|
+
MultiFileUploader,
|
|
739
|
+
StatusIndicator,
|
|
740
|
+
PanelButton
|
|
741
|
+
} from '@owlmeans/web-panel'
|
|
742
|
+
import { Grid, Card, CardContent, Typography, LinearProgress } from '@mui/material'
|
|
743
|
+
|
|
744
|
+
const FileManagement: React.FC = () => {
|
|
745
|
+
const [uploadProgress, setUploadProgress] = useState<Record<string, number>>({})
|
|
746
|
+
const [uploadedFiles, setUploadedFiles] = useState<string[]>([])
|
|
747
|
+
|
|
748
|
+
const handleFileUpload = async (file: File, progressCallback: (progress: number) => void) => {
|
|
749
|
+
const formData = new FormData()
|
|
750
|
+
formData.append('file', file)
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
const xhr = new XMLHttpRequest()
|
|
754
|
+
|
|
755
|
+
xhr.upload.addEventListener('progress', (e) => {
|
|
756
|
+
if (e.lengthComputable) {
|
|
757
|
+
const progress = (e.loaded / e.total) * 100
|
|
758
|
+
progressCallback(progress)
|
|
759
|
+
setUploadProgress(prev => ({ ...prev, [file.name]: progress }))
|
|
760
|
+
}
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
xhr.addEventListener('load', () => {
|
|
764
|
+
if (xhr.status === 200) {
|
|
765
|
+
setUploadedFiles(prev => [...prev, file.name])
|
|
766
|
+
setUploadProgress(prev => {
|
|
767
|
+
const updated = { ...prev }
|
|
768
|
+
delete updated[file.name]
|
|
769
|
+
return updated
|
|
770
|
+
})
|
|
771
|
+
}
|
|
772
|
+
})
|
|
773
|
+
|
|
774
|
+
xhr.open('POST', '/api/upload')
|
|
775
|
+
xhr.send(formData)
|
|
776
|
+
} catch (error) {
|
|
777
|
+
console.error('Upload failed:', error)
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const handleImageUpload = async (file: File, cropData?: any) => {
|
|
782
|
+
console.log('Uploading image:', file.name, 'Crop data:', cropData)
|
|
783
|
+
// Implement image upload with crop data
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const handleMultipleUpload = async (files: File[]) => {
|
|
787
|
+
for (const file of files) {
|
|
788
|
+
await handleFileUpload(file, () => {})
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return (
|
|
793
|
+
<Grid container spacing={3}>
|
|
794
|
+
<Grid item xs={12}>
|
|
795
|
+
<Typography variant="h4" gutterBottom>
|
|
796
|
+
File Management
|
|
797
|
+
</Typography>
|
|
798
|
+
</Grid>
|
|
799
|
+
|
|
800
|
+
<Grid item xs={12} md={6}>
|
|
801
|
+
<Card>
|
|
802
|
+
<CardContent>
|
|
803
|
+
<Typography variant="h6" gutterBottom>
|
|
804
|
+
Document Upload
|
|
805
|
+
</Typography>
|
|
806
|
+
|
|
807
|
+
<FileUploader
|
|
808
|
+
accept=".pdf,.doc,.docx,.txt"
|
|
809
|
+
maxSize={10 * 1024 * 1024} // 10MB
|
|
810
|
+
onUpload={handleFileUpload}
|
|
811
|
+
multiple={false}
|
|
812
|
+
/>
|
|
813
|
+
</CardContent>
|
|
814
|
+
</Card>
|
|
815
|
+
</Grid>
|
|
816
|
+
|
|
817
|
+
<Grid item xs={12} md={6}>
|
|
818
|
+
<Card>
|
|
819
|
+
<CardContent>
|
|
820
|
+
<Typography variant="h6" gutterBottom>
|
|
821
|
+
Image Upload
|
|
822
|
+
</Typography>
|
|
823
|
+
|
|
824
|
+
<ImageUploader
|
|
825
|
+
accept="image/*"
|
|
826
|
+
maxSize={5 * 1024 * 1024} // 5MB
|
|
827
|
+
cropAspectRatio={16/9}
|
|
828
|
+
onUpload={handleImageUpload}
|
|
829
|
+
preview={true}
|
|
830
|
+
cropperOptions={{
|
|
831
|
+
guides: false,
|
|
832
|
+
center: false,
|
|
833
|
+
highlight: false,
|
|
834
|
+
background: false,
|
|
835
|
+
autoCropArea: 1,
|
|
836
|
+
checkOrientation: false
|
|
837
|
+
}}
|
|
838
|
+
/>
|
|
839
|
+
</CardContent>
|
|
840
|
+
</Card>
|
|
841
|
+
</Grid>
|
|
842
|
+
|
|
843
|
+
<Grid item xs={12}>
|
|
844
|
+
<Card>
|
|
845
|
+
<CardContent>
|
|
846
|
+
<Typography variant="h6" gutterBottom>
|
|
847
|
+
Multiple File Upload
|
|
848
|
+
</Typography>
|
|
849
|
+
|
|
850
|
+
<MultiFileUploader
|
|
851
|
+
maxFiles={5}
|
|
852
|
+
accept="*/*"
|
|
853
|
+
onUploadAll={handleMultipleUpload}
|
|
854
|
+
showProgress={true}
|
|
855
|
+
/>
|
|
856
|
+
|
|
857
|
+
{Object.entries(uploadProgress).map(([fileName, progress]) => (
|
|
858
|
+
<div key={fileName}>
|
|
859
|
+
<Typography variant="body2" gutterBottom>
|
|
860
|
+
{fileName}
|
|
861
|
+
</Typography>
|
|
862
|
+
<LinearProgress
|
|
863
|
+
variant="determinate"
|
|
864
|
+
value={progress}
|
|
865
|
+
sx={{ mb: 1 }}
|
|
866
|
+
/>
|
|
867
|
+
</div>
|
|
868
|
+
))}
|
|
869
|
+
</CardContent>
|
|
870
|
+
</Card>
|
|
871
|
+
</Grid>
|
|
872
|
+
|
|
873
|
+
{uploadedFiles.length > 0 && (
|
|
874
|
+
<Grid item xs={12}>
|
|
875
|
+
<Card>
|
|
876
|
+
<CardContent>
|
|
877
|
+
<Typography variant="h6" gutterBottom>
|
|
878
|
+
Uploaded Files
|
|
879
|
+
</Typography>
|
|
880
|
+
|
|
881
|
+
{uploadedFiles.map(fileName => (
|
|
882
|
+
<StatusIndicator
|
|
883
|
+
key={fileName}
|
|
884
|
+
status="success"
|
|
885
|
+
message={`${fileName} uploaded successfully`}
|
|
886
|
+
/>
|
|
887
|
+
))}
|
|
888
|
+
</CardContent>
|
|
889
|
+
</Card>
|
|
890
|
+
</Grid>
|
|
891
|
+
)}
|
|
892
|
+
</Grid>
|
|
893
|
+
)
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
export default FileManagement
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
## Authentication Integration
|
|
900
|
+
|
|
901
|
+
### Protected Routes and Components
|
|
902
|
+
|
|
903
|
+
```typescript
|
|
904
|
+
import { AuthGuard, PermissionGuard } from '@owlmeans/web-panel/auth'
|
|
905
|
+
import { Navigate } from 'react-router-dom'
|
|
906
|
+
|
|
907
|
+
// Protect entire application
|
|
908
|
+
<AuthGuard fallback={<Navigate to="/login" />}>
|
|
909
|
+
<PanelApp context={context}>
|
|
910
|
+
<Router>
|
|
911
|
+
<Routes>
|
|
912
|
+
<Route path="/admin" element={
|
|
913
|
+
<PermissionGuard permissions={['admin']}>
|
|
914
|
+
<AdminPanel />
|
|
915
|
+
</PermissionGuard>
|
|
916
|
+
} />
|
|
917
|
+
<Route path="/users" element={
|
|
918
|
+
<PermissionGuard permissions={['user-management']}>
|
|
919
|
+
<UserManagement />
|
|
920
|
+
</PermissionGuard>
|
|
921
|
+
} />
|
|
922
|
+
</Routes>
|
|
923
|
+
</Router>
|
|
924
|
+
</PanelApp>
|
|
925
|
+
</AuthGuard>
|
|
926
|
+
|
|
927
|
+
// Conditional rendering based on permissions
|
|
928
|
+
const AdminActions: React.FC = () => (
|
|
929
|
+
<PermissionGuard permissions={['admin', 'moderator']}>
|
|
930
|
+
{(hasPermission) => (
|
|
931
|
+
<PanelButton
|
|
932
|
+
disabled={!hasPermission}
|
|
933
|
+
onClick={handleAdminAction}
|
|
934
|
+
>
|
|
935
|
+
Admin Action
|
|
936
|
+
</PanelButton>
|
|
937
|
+
)}
|
|
938
|
+
</PermissionGuard>
|
|
939
|
+
)
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
## Theme Customization
|
|
943
|
+
|
|
944
|
+
### Custom Material-UI Theme
|
|
945
|
+
|
|
946
|
+
```typescript
|
|
947
|
+
import { createTheme, ThemeProvider } from '@mui/material/styles'
|
|
948
|
+
|
|
949
|
+
const customTheme = createTheme({
|
|
950
|
+
palette: {
|
|
951
|
+
mode: 'light',
|
|
952
|
+
primary: {
|
|
953
|
+
main: '#1976d2',
|
|
954
|
+
light: '#42a5f5',
|
|
955
|
+
dark: '#1565c0',
|
|
956
|
+
contrastText: '#ffffff'
|
|
957
|
+
},
|
|
958
|
+
secondary: {
|
|
959
|
+
main: '#dc004e',
|
|
960
|
+
light: '#f06292',
|
|
961
|
+
dark: '#c2185b',
|
|
962
|
+
contrastText: '#ffffff'
|
|
963
|
+
},
|
|
964
|
+
background: {
|
|
965
|
+
default: '#f5f5f5',
|
|
966
|
+
paper: '#ffffff'
|
|
967
|
+
},
|
|
968
|
+
text: {
|
|
969
|
+
primary: '#212121',
|
|
970
|
+
secondary: '#757575'
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
typography: {
|
|
974
|
+
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
|
|
975
|
+
h1: {
|
|
976
|
+
fontSize: '2.5rem',
|
|
977
|
+
fontWeight: 500,
|
|
978
|
+
lineHeight: 1.2
|
|
979
|
+
},
|
|
980
|
+
h2: {
|
|
981
|
+
fontSize: '2rem',
|
|
982
|
+
fontWeight: 500,
|
|
983
|
+
lineHeight: 1.3
|
|
984
|
+
},
|
|
985
|
+
body1: {
|
|
986
|
+
fontSize: '1rem',
|
|
987
|
+
lineHeight: 1.5
|
|
988
|
+
}
|
|
989
|
+
},
|
|
990
|
+
shape: {
|
|
991
|
+
borderRadius: 8
|
|
992
|
+
},
|
|
993
|
+
spacing: 8,
|
|
994
|
+
components: {
|
|
995
|
+
MuiCard: {
|
|
996
|
+
styleOverrides: {
|
|
997
|
+
root: {
|
|
998
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
|
999
|
+
'&:hover': {
|
|
1000
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.15)'
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
MuiButton: {
|
|
1006
|
+
styleOverrides: {
|
|
1007
|
+
root: {
|
|
1008
|
+
borderRadius: 8,
|
|
1009
|
+
textTransform: 'none',
|
|
1010
|
+
fontWeight: 500
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
})
|
|
1016
|
+
|
|
1017
|
+
// Apply theme to panel app
|
|
1018
|
+
<PanelApp context={context} theme={customTheme}>
|
|
1019
|
+
<AdminInterface />
|
|
1020
|
+
</PanelApp>
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
## Internationalization
|
|
1024
|
+
|
|
1025
|
+
### Multi-language Support
|
|
1026
|
+
|
|
1027
|
+
```typescript
|
|
1028
|
+
import { addI18nApp } from '@owlmeans/i18n'
|
|
1029
|
+
|
|
1030
|
+
// Add translations for panel
|
|
1031
|
+
addI18nApp('en', 'panel', {
|
|
1032
|
+
'panel.title': 'Administration Panel',
|
|
1033
|
+
'users.title': 'User Management',
|
|
1034
|
+
'users.add': 'Add New User',
|
|
1035
|
+
'users.edit': 'Edit User',
|
|
1036
|
+
'users.delete': 'Delete User',
|
|
1037
|
+
'form.save': 'Save',
|
|
1038
|
+
'form.cancel': 'Cancel',
|
|
1039
|
+
'status.loading': 'Loading...',
|
|
1040
|
+
'status.error': 'An error occurred',
|
|
1041
|
+
'status.success': 'Operation completed successfully'
|
|
1042
|
+
})
|
|
1043
|
+
|
|
1044
|
+
addI18nApp('es', 'panel', {
|
|
1045
|
+
'panel.title': 'Panel de Administración',
|
|
1046
|
+
'users.title': 'Gestión de Usuarios',
|
|
1047
|
+
'users.add': 'Agregar Nuevo Usuario',
|
|
1048
|
+
'users.edit': 'Editar Usuario',
|
|
1049
|
+
'users.delete': 'Eliminar Usuario',
|
|
1050
|
+
'form.save': 'Guardar',
|
|
1051
|
+
'form.cancel': 'Cancelar',
|
|
1052
|
+
'status.loading': 'Cargando...',
|
|
1053
|
+
'status.error': 'Ocurrió un error',
|
|
1054
|
+
'status.success': 'Operación completada exitosamente'
|
|
1055
|
+
})
|
|
1056
|
+
|
|
1057
|
+
// Use translations in components
|
|
1058
|
+
import { useTranslation } from '@owlmeans/client-i18n'
|
|
1059
|
+
|
|
1060
|
+
const UserPanel: React.FC = () => {
|
|
1061
|
+
const { t } = useTranslation()
|
|
1062
|
+
|
|
1063
|
+
return (
|
|
1064
|
+
<Typography variant="h4">
|
|
1065
|
+
{t('users.title')}
|
|
1066
|
+
</Typography>
|
|
1067
|
+
)
|
|
1068
|
+
}
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
## Performance Optimization
|
|
1072
|
+
|
|
1073
|
+
### Code Splitting and Lazy Loading
|
|
1074
|
+
|
|
1075
|
+
```typescript
|
|
1076
|
+
import React, { lazy, Suspense } from 'react'
|
|
1077
|
+
import { StatusIndicator } from '@owlmeans/web-panel'
|
|
1078
|
+
|
|
1079
|
+
// Lazy load panel sections
|
|
1080
|
+
const UserManagement = lazy(() => import('./UserManagement'))
|
|
1081
|
+
const SystemSettings = lazy(() => import('./SystemSettings'))
|
|
1082
|
+
const Reports = lazy(() => import('./Reports'))
|
|
1083
|
+
|
|
1084
|
+
const AdminRouter: React.FC = () => (
|
|
1085
|
+
<Routes>
|
|
1086
|
+
<Route path="/users" element={
|
|
1087
|
+
<Suspense fallback={<StatusIndicator status="loading" message="Loading users..." />}>
|
|
1088
|
+
<UserManagement />
|
|
1089
|
+
</Suspense>
|
|
1090
|
+
} />
|
|
1091
|
+
<Route path="/settings" element={
|
|
1092
|
+
<Suspense fallback={<StatusIndicator status="loading" message="Loading settings..." />}>
|
|
1093
|
+
<SystemSettings />
|
|
1094
|
+
</Suspense>
|
|
1095
|
+
} />
|
|
1096
|
+
<Route path="/reports" element={
|
|
1097
|
+
<Suspense fallback={<StatusIndicator status="loading" message="Loading reports..." />}>
|
|
1098
|
+
<Reports />
|
|
1099
|
+
</Suspense>
|
|
1100
|
+
} />
|
|
1101
|
+
</Routes>
|
|
1102
|
+
)
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
### Memoization and Optimization
|
|
1106
|
+
|
|
1107
|
+
```typescript
|
|
1108
|
+
import React, { memo, useMemo, useCallback } from 'react'
|
|
1109
|
+
|
|
1110
|
+
const OptimizedUserList = memo<{ users: User[]; onUserSelect: (user: User) => void }>(({
|
|
1111
|
+
users,
|
|
1112
|
+
onUserSelect
|
|
1113
|
+
}) => {
|
|
1114
|
+
const sortedUsers = useMemo(() =>
|
|
1115
|
+
users.sort((a, b) => a.name.localeCompare(b.name)),
|
|
1116
|
+
[users]
|
|
1117
|
+
)
|
|
1118
|
+
|
|
1119
|
+
const handleUserClick = useCallback((user: User) => {
|
|
1120
|
+
onUserSelect(user)
|
|
1121
|
+
}, [onUserSelect])
|
|
1122
|
+
|
|
1123
|
+
return (
|
|
1124
|
+
<div>
|
|
1125
|
+
{sortedUsers.map(user => (
|
|
1126
|
+
<UserCard
|
|
1127
|
+
key={user.id}
|
|
1128
|
+
user={user}
|
|
1129
|
+
onClick={handleUserClick}
|
|
1130
|
+
/>
|
|
1131
|
+
))}
|
|
1132
|
+
</div>
|
|
1133
|
+
)
|
|
1134
|
+
})
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
## Best Practices
|
|
1138
|
+
|
|
1139
|
+
1. **Component Structure**: Use consistent component hierarchy and naming conventions
|
|
1140
|
+
2. **Theme Management**: Leverage Material-UI theme system for consistent styling
|
|
1141
|
+
3. **Form Validation**: Use AJV schemas for robust form validation
|
|
1142
|
+
4. **Error Handling**: Provide comprehensive error feedback and recovery options
|
|
1143
|
+
5. **Performance**: Implement code splitting and memoization for large applications
|
|
1144
|
+
6. **Accessibility**: Ensure all components meet WCAG accessibility standards
|
|
1145
|
+
7. **Internationalization**: Always use i18n for user-facing text
|
|
1146
|
+
8. **Testing**: Write comprehensive tests for form validation and user interactions
|
|
1147
|
+
|
|
1148
|
+
## Authentication Modules
|
|
1149
|
+
|
|
1150
|
+
The package provides authentication-specific modules and components:
|
|
1151
|
+
|
|
1152
|
+
```typescript
|
|
1153
|
+
import { modules } from '@owlmeans/web-panel/auth/modules'
|
|
1154
|
+
import { AuthProvider, LoginForm } from '@owlmeans/web-panel/auth'
|
|
1155
|
+
|
|
1156
|
+
// Register authentication modules
|
|
1157
|
+
context.registerModules(modules)
|
|
1158
|
+
|
|
1159
|
+
// Use authentication components
|
|
1160
|
+
<AuthProvider context={context}>
|
|
1161
|
+
<LoginForm
|
|
1162
|
+
onSuccess={handleLogin}
|
|
1163
|
+
features={{
|
|
1164
|
+
showRegister: true,
|
|
1165
|
+
showForgotPassword: true,
|
|
1166
|
+
showRememberMe: true
|
|
1167
|
+
}}
|
|
1168
|
+
/>
|
|
1169
|
+
</AuthProvider>
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
## Related Packages
|
|
1173
|
+
|
|
1174
|
+
- **@owlmeans/client-panel**: Base panel functionality and context
|
|
1175
|
+
- **@owlmeans/native-panel**: React Native panel implementation
|
|
1176
|
+
- **@owlmeans/web-client**: Web client framework and rendering
|
|
1177
|
+
- **@owlmeans/client-auth**: Authentication components and services
|
|
1178
|
+
- **@owlmeans/client-flow**: Flow management integration
|
|
1179
|
+
- **@owlmeans/web-flow**: Web-specific flow implementations
|
|
1180
|
+
|
|
1181
|
+
## TypeScript Support
|
|
1182
|
+
|
|
1183
|
+
This package is written in TypeScript and provides full type safety:
|
|
1184
|
+
|
|
1185
|
+
```typescript
|
|
1186
|
+
import type {
|
|
1187
|
+
PanelAppProps,
|
|
1188
|
+
FormFieldProps,
|
|
1189
|
+
FileUploaderProps,
|
|
1190
|
+
AuthGuardProps
|
|
1191
|
+
} from '@owlmeans/web-panel'
|
|
1192
|
+
|
|
1193
|
+
const MyPanel: FC<PanelAppProps> = (props) => { /* ... */ }
|
|
1194
|
+
const MyForm: FC<{ onSubmit: (data: FormData) => void }> = ({ onSubmit }) => { /* ... */ }
|
|
1195
|
+
```
|