feat: Add complete Docker deployment with web-based setup wizard
Major additions: - Web-based setup wizard (setup.php, setup_wizard.php, setup-wizard.js) - Production Docker configuration (docker-compose.prod.yml, .env.production) - Database initialization SQL files (deploy/init_settings.sql) - Template builder system with drag-and-drop UI - Advanced features (OAuth, CDN, enhanced analytics, monetization) - Comprehensive documentation (deployment guides, quick start, feature docs) - Design system with accessibility and responsive layout - Deployment automation scripts (deploy.ps1, generate-secrets.ps1) Setup wizard allows customization of: - Platform name and branding - Domain configuration - Membership tiers and pricing - Admin credentials - Feature toggles Database includes 270+ tables for complete video streaming platform with advanced features for analytics, moderation, template building, and monetization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
907
f_scripts/fe/css/builder/builder.css
Normal file
907
f_scripts/fe/css/builder/builder.css
Normal file
@@ -0,0 +1,907 @@
|
||||
/**
|
||||
* Template Builder Styles
|
||||
* Drag and drop template builder interface
|
||||
*/
|
||||
|
||||
/* ==========================================================================
|
||||
Variables
|
||||
========================================================================== */
|
||||
|
||||
:root {
|
||||
--tb-primary: #3b82f6;
|
||||
--tb-success: #10b981;
|
||||
--tb-danger: #ef4444;
|
||||
--tb-warning: #f59e0b;
|
||||
--tb-secondary: #6b7280;
|
||||
|
||||
--tb-bg: #ffffff;
|
||||
--tb-bg-secondary: #f9fafb;
|
||||
--tb-bg-tertiary: #f3f4f6;
|
||||
--tb-border: #e5e7eb;
|
||||
--tb-text: #111827;
|
||||
--tb-text-secondary: #6b7280;
|
||||
|
||||
--tb-header-height: 60px;
|
||||
--tb-sidebar-width: 280px;
|
||||
--tb-toolbar-height: 50px;
|
||||
|
||||
--tb-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--tb-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||
--tb-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
|
||||
--tb-radius: 6px;
|
||||
--tb-radius-sm: 4px;
|
||||
--tb-radius-lg: 8px;
|
||||
|
||||
--tb-transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* Dark Mode */
|
||||
[data-theme*="dark"] .template-builder,
|
||||
.template-builder[data-theme*="dark"] {
|
||||
--tb-bg: #1f2937;
|
||||
--tb-bg-secondary: #111827;
|
||||
--tb-bg-tertiary: #374151;
|
||||
--tb-border: #374151;
|
||||
--tb-text: #f9fafb;
|
||||
--tb-text-secondary: #9ca3af;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Layout
|
||||
========================================================================== */
|
||||
|
||||
.template-builder {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--tb-bg);
|
||||
color: var(--tb-text);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.tb-header {
|
||||
height: var(--tb-header-height);
|
||||
background: var(--tb-bg-secondary);
|
||||
border-bottom: 1px solid var(--tb-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.tb-header-left,
|
||||
.tb-header-center,
|
||||
.tb-header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tb-header-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tb-header-center {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tb-header-right {
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.tb-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Sidebars
|
||||
========================================================================== */
|
||||
|
||||
.tb-sidebar {
|
||||
width: var(--tb-sidebar-width);
|
||||
background: var(--tb-bg-secondary);
|
||||
border-right: 1px solid var(--tb-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transition: var(--tb-transition);
|
||||
}
|
||||
|
||||
.tb-sidebar-right {
|
||||
border-right: none;
|
||||
border-left: 1px solid var(--tb-border);
|
||||
}
|
||||
|
||||
.tb-sidebar.collapsed {
|
||||
width: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tb-sidebar-header {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--tb-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tb-sidebar-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tb-sidebar-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--tb-text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: var(--tb-radius-sm);
|
||||
transition: var(--tb-transition);
|
||||
}
|
||||
|
||||
.tb-sidebar-toggle:hover {
|
||||
background: var(--tb-bg-tertiary);
|
||||
color: var(--tb-text);
|
||||
}
|
||||
|
||||
.tb-sidebar-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Canvas
|
||||
========================================================================== */
|
||||
|
||||
.tb-canvas-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--tb-bg-tertiary);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tb-canvas-toolbar {
|
||||
height: var(--tb-toolbar-height);
|
||||
background: var(--tb-bg-secondary);
|
||||
border-bottom: 1px solid var(--tb-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.tb-zoom-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tb-zoom-level {
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: var(--tb-text-secondary);
|
||||
}
|
||||
|
||||
.tb-canvas-options {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.tb-canvas-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.tb-canvas {
|
||||
background: white;
|
||||
min-height: 100%;
|
||||
margin: 0 auto;
|
||||
box-shadow: var(--tb-shadow-lg);
|
||||
position: relative;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.tb-canvas[data-device="desktop"] {
|
||||
width: 100%;
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.tb-canvas[data-device="tablet"] {
|
||||
width: 768px;
|
||||
}
|
||||
|
||||
.tb-canvas[data-device="mobile"] {
|
||||
width: 375px;
|
||||
}
|
||||
|
||||
.tb-canvas.show-grid {
|
||||
background-image:
|
||||
linear-gradient(rgba(0, 0, 0, 0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0, 0, 0, 0.03) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
|
||||
.tb-sections-container {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
/* Drop Hint */
|
||||
.tb-drop-hint {
|
||||
padding: 80px 40px;
|
||||
text-align: center;
|
||||
color: var(--tb-text-secondary);
|
||||
border: 2px dashed var(--tb-border);
|
||||
border-radius: var(--tb-radius-lg);
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
.tb-drop-hint-content i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tb-drop-hint-content p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tb-drop-hint.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Sections
|
||||
========================================================================== */
|
||||
|
||||
.tb-section {
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border: 2px dashed transparent;
|
||||
transition: var(--tb-transition);
|
||||
}
|
||||
|
||||
.tb-section:hover {
|
||||
border-color: var(--tb-border);
|
||||
}
|
||||
|
||||
.tb-section.selected {
|
||||
border-color: var(--tb-primary);
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
}
|
||||
|
||||
.tb-section.drag-over {
|
||||
border-color: var(--tb-success);
|
||||
background: rgba(16, 185, 129, 0.05);
|
||||
}
|
||||
|
||||
.tb-section-controls {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: 10px;
|
||||
background: white;
|
||||
border: 1px solid var(--tb-border);
|
||||
border-radius: var(--tb-radius);
|
||||
display: none;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
box-shadow: var(--tb-shadow);
|
||||
}
|
||||
|
||||
.tb-section:hover .tb-section-controls,
|
||||
.tb-section.selected .tb-section-controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tb-columns {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Blocks (Components)
|
||||
========================================================================== */
|
||||
|
||||
.tb-block {
|
||||
position: relative;
|
||||
min-height: 50px;
|
||||
border: 2px dashed transparent;
|
||||
border-radius: var(--tb-radius);
|
||||
transition: var(--tb-transition);
|
||||
}
|
||||
|
||||
.tb-block:hover {
|
||||
border-color: var(--tb-border);
|
||||
}
|
||||
|
||||
.tb-block.selected {
|
||||
border-color: var(--tb-primary);
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
}
|
||||
|
||||
.tb-block.dragging {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tb-block-controls {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: 10px;
|
||||
background: white;
|
||||
border: 1px solid var(--tb-border);
|
||||
border-radius: var(--tb-radius);
|
||||
display: none;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
box-shadow: var(--tb-shadow);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tb-block:hover .tb-block-controls,
|
||||
.tb-block.selected .tb-block-controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Components List (Sidebar)
|
||||
========================================================================== */
|
||||
|
||||
.tb-search-box {
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tb-search-box input {
|
||||
width: 100%;
|
||||
padding: 8px 12px 8px 36px;
|
||||
border: 1px solid var(--tb-border);
|
||||
border-radius: var(--tb-radius);
|
||||
background: var(--tb-bg);
|
||||
color: var(--tb-text);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tb-search-box i {
|
||||
position: absolute;
|
||||
left: 28px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--tb-text-secondary);
|
||||
}
|
||||
|
||||
.tb-component-categories {
|
||||
padding: 0 16px 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tb-category-btn {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--tb-border);
|
||||
background: var(--tb-bg);
|
||||
color: var(--tb-text);
|
||||
border-radius: var(--tb-radius);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: var(--tb-transition);
|
||||
}
|
||||
|
||||
.tb-category-btn:hover {
|
||||
background: var(--tb-bg-tertiary);
|
||||
}
|
||||
|
||||
.tb-category-btn.active {
|
||||
background: var(--tb-primary);
|
||||
border-color: var(--tb-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tb-components-list {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.tb-component-item {
|
||||
padding: 12px;
|
||||
border: 1px solid var(--tb-border);
|
||||
border-radius: var(--tb-radius);
|
||||
margin-bottom: 12px;
|
||||
cursor: grab;
|
||||
background: var(--tb-bg);
|
||||
transition: var(--tb-transition);
|
||||
}
|
||||
|
||||
.tb-component-item:hover {
|
||||
border-color: var(--tb-primary);
|
||||
box-shadow: var(--tb-shadow);
|
||||
}
|
||||
|
||||
.tb-component-item:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.tb-component-item.dragging {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tb-component-thumb {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background: var(--tb-bg-tertiary);
|
||||
border-radius: var(--tb-radius-sm);
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tb-component-thumb img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.tb-component-thumb i {
|
||||
font-size: 32px;
|
||||
color: var(--tb-text-secondary);
|
||||
}
|
||||
|
||||
.tb-component-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.tb-component-desc {
|
||||
font-size: 12px;
|
||||
color: var(--tb-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Properties Panel
|
||||
========================================================================== */
|
||||
|
||||
.tb-no-selection {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: var(--tb-text-secondary);
|
||||
}
|
||||
|
||||
.tb-no-selection i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tb-properties-section {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.tb-properties-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tb-properties-header h4 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Form Elements */
|
||||
.tb-form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tb-form-group label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6px;
|
||||
color: var(--tb-text);
|
||||
}
|
||||
|
||||
.tb-input,
|
||||
.tb-select,
|
||||
.tb-textarea {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--tb-border);
|
||||
border-radius: var(--tb-radius);
|
||||
background: var(--tb-bg);
|
||||
color: var(--tb-text);
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
transition: var(--tb-transition);
|
||||
}
|
||||
|
||||
.tb-input:focus,
|
||||
.tb-select:focus,
|
||||
.tb-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--tb-primary);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.tb-input-sm {
|
||||
padding: 6px 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tb-textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
input[type="color"].tb-input {
|
||||
height: 40px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.tb-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tb-checkbox input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tb-spacing-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Collapsible */
|
||||
.tb-collapsible {
|
||||
border-top: 1px solid var(--tb-border);
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.tb-collapsible-header {
|
||||
width: 100%;
|
||||
padding: 8px 0;
|
||||
background: none;
|
||||
border: none;
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--tb-text);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tb-collapsible-header i {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.tb-collapsible.open .tb-collapsible-header i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.tb-collapsible-content {
|
||||
display: none;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.tb-collapsible.open .tb-collapsible-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Buttons
|
||||
========================================================================== */
|
||||
|
||||
.tb-btn {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--tb-radius);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: var(--tb-transition);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tb-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.tb-btn-primary {
|
||||
background: var(--tb-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tb-btn-primary:hover:not(:disabled) {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.tb-btn-success {
|
||||
background: var(--tb-success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tb-btn-success:hover:not(:disabled) {
|
||||
background: #059669;
|
||||
}
|
||||
|
||||
.tb-btn-danger {
|
||||
background: var(--tb-danger);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tb-btn-danger:hover:not(:disabled) {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.tb-btn-secondary {
|
||||
background: var(--tb-bg-tertiary);
|
||||
color: var(--tb-text);
|
||||
border-color: var(--tb-border);
|
||||
}
|
||||
|
||||
.tb-btn-secondary:hover:not(:disabled) {
|
||||
background: var(--tb-bg-secondary);
|
||||
}
|
||||
|
||||
.tb-btn-icon {
|
||||
padding: 8px;
|
||||
min-width: 36px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tb-btn-sm {
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tb-btn i {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Device Preview Buttons */
|
||||
.tb-device-preview {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
background: var(--tb-bg-tertiary);
|
||||
padding: 4px;
|
||||
border-radius: var(--tb-radius);
|
||||
}
|
||||
|
||||
.tb-device-btn {
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--tb-text-secondary);
|
||||
cursor: pointer;
|
||||
border-radius: var(--tb-radius-sm);
|
||||
transition: var(--tb-transition);
|
||||
}
|
||||
|
||||
.tb-device-btn:hover {
|
||||
color: var(--tb-text);
|
||||
}
|
||||
|
||||
.tb-device-btn.active {
|
||||
background: var(--tb-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Template Name Input */
|
||||
.tb-template-name-input {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--tb-radius);
|
||||
background: var(--tb-bg-tertiary);
|
||||
color: var(--tb-text);
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
max-width: 300px;
|
||||
transition: var(--tb-transition);
|
||||
}
|
||||
|
||||
.tb-template-name-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--tb-primary);
|
||||
background: var(--tb-bg);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Modal
|
||||
========================================================================== */
|
||||
|
||||
.tb-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.tb-modal-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.tb-modal-content {
|
||||
position: relative;
|
||||
background: var(--tb-bg);
|
||||
max-width: 500px;
|
||||
margin: 100px auto;
|
||||
border-radius: var(--tb-radius-lg);
|
||||
box-shadow: var(--tb-shadow-lg);
|
||||
}
|
||||
|
||||
.tb-modal-large {
|
||||
max-width: 90vw;
|
||||
}
|
||||
|
||||
.tb-modal-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--tb-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tb-modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.tb-modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--tb-text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: var(--tb-radius-sm);
|
||||
transition: var(--tb-transition);
|
||||
}
|
||||
|
||||
.tb-modal-close:hover {
|
||||
background: var(--tb-bg-tertiary);
|
||||
color: var(--tb-text);
|
||||
}
|
||||
|
||||
.tb-modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.tb-modal-footer {
|
||||
padding: 20px;
|
||||
border-top: 1px solid var(--tb-border);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Loading
|
||||
========================================================================== */
|
||||
|
||||
.tb-loading {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: var(--tb-text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Utilities
|
||||
========================================================================== */
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Drag and Drop */
|
||||
.tb-dragging {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.tb-drag-placeholder {
|
||||
border: 2px dashed var(--tb-primary);
|
||||
background: rgba(59, 130, 246, 0.05);
|
||||
border-radius: var(--tb-radius);
|
||||
min-height: 100px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
.tb-sidebar-content::-webkit-scrollbar,
|
||||
.tb-canvas-wrapper::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.tb-sidebar-content::-webkit-scrollbar-track,
|
||||
.tb-canvas-wrapper::-webkit-scrollbar-track {
|
||||
background: var(--tb-bg-secondary);
|
||||
}
|
||||
|
||||
.tb-sidebar-content::-webkit-scrollbar-thumb,
|
||||
.tb-canvas-wrapper::-webkit-scrollbar-thumb {
|
||||
background: var(--tb-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tb-sidebar-content::-webkit-scrollbar-thumb:hover,
|
||||
.tb-canvas-wrapper::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--tb-text-secondary);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Responsive
|
||||
========================================================================== */
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.tb-sidebar {
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tb-header {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.tb-sidebar {
|
||||
position: absolute;
|
||||
top: var(--tb-header-height);
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
box-shadow: var(--tb-shadow-lg);
|
||||
}
|
||||
|
||||
.tb-sidebar-left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tb-sidebar-right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.tb-canvas-wrapper {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
1079
f_scripts/fe/js/builder/builder-core.js
Normal file
1079
f_scripts/fe/js/builder/builder-core.js
Normal file
File diff suppressed because it is too large
Load Diff
423
f_scripts/fe/js/setup-wizard.js
Normal file
423
f_scripts/fe/js/setup-wizard.js
Normal file
@@ -0,0 +1,423 @@
|
||||
/**
|
||||
* EasyStream Setup Wizard - Frontend Logic
|
||||
* Handles wizard navigation, form validation, and AJAX submissions
|
||||
*/
|
||||
|
||||
let currentStep = 1;
|
||||
const totalSteps = 9;
|
||||
let setupData = {};
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
updateProgressBar();
|
||||
attachEventListeners();
|
||||
loadSavedData();
|
||||
});
|
||||
|
||||
function attachEventListeners() {
|
||||
// Color picker updates
|
||||
document.getElementById('primaryColor').addEventListener('input', function(e) {
|
||||
document.getElementById('primaryColorHex').textContent = e.target.value;
|
||||
});
|
||||
|
||||
document.getElementById('secondaryColor').addEventListener('input', function(e) {
|
||||
document.getElementById('secondaryColorHex').textContent = e.target.value;
|
||||
});
|
||||
|
||||
// Save form data on input
|
||||
const inputs = document.querySelectorAll('input, select, textarea');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('change', saveFormData);
|
||||
});
|
||||
}
|
||||
|
||||
function changeStep(direction) {
|
||||
const nextStep = currentStep + direction;
|
||||
|
||||
// Validate before moving forward
|
||||
if (direction > 0) {
|
||||
if (!validateCurrentStep()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for certain steps
|
||||
if (currentStep === 7 && direction > 0) {
|
||||
// Review step - show summary and install
|
||||
showReviewSummary();
|
||||
startInstallation();
|
||||
}
|
||||
|
||||
if (nextStep >= 1 && nextStep <= totalSteps) {
|
||||
// Hide current step
|
||||
document.getElementById(`step${currentStep}`).classList.remove('active');
|
||||
|
||||
// Show next step
|
||||
currentStep = nextStep;
|
||||
document.getElementById(`step${currentStep}`).classList.add('active');
|
||||
|
||||
// Update progress bar
|
||||
updateProgressBar();
|
||||
|
||||
// Update button visibility
|
||||
updateButtons();
|
||||
|
||||
// Scroll to top
|
||||
document.querySelector('.wizard-body').scrollTop = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgressBar() {
|
||||
const progress = ((currentStep - 1) / (totalSteps - 1)) * 100;
|
||||
document.getElementById('progressBar').style.width = progress + '%';
|
||||
}
|
||||
|
||||
function updateButtons() {
|
||||
const prevBtn = document.getElementById('prevBtn');
|
||||
const nextBtn = document.getElementById('nextBtn');
|
||||
|
||||
// Show/hide previous button
|
||||
prevBtn.style.display = currentStep > 1 && currentStep < 8 ? 'block' : 'none';
|
||||
|
||||
// Update next button text and visibility
|
||||
if (currentStep === 1) {
|
||||
nextBtn.textContent = 'Get Started →';
|
||||
} else if (currentStep === 7) {
|
||||
nextBtn.textContent = 'Install Now →';
|
||||
} else if (currentStep === 8 || currentStep === 9) {
|
||||
nextBtn.style.display = 'none';
|
||||
} else {
|
||||
nextBtn.textContent = 'Next →';
|
||||
}
|
||||
}
|
||||
|
||||
function validateCurrentStep() {
|
||||
clearAlert();
|
||||
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
// Welcome page, no validation needed
|
||||
return true;
|
||||
|
||||
case 2:
|
||||
// Platform configuration
|
||||
const platformName = document.getElementById('platformName').value.trim();
|
||||
const domainName = document.getElementById('domainName').value.trim();
|
||||
const contactEmail = document.getElementById('contactEmail').value.trim();
|
||||
|
||||
if (!platformName) {
|
||||
showAlert('error', 'Please enter a platform name');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!domainName) {
|
||||
showAlert('error', 'Please enter a domain name');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!contactEmail || !isValidEmail(contactEmail)) {
|
||||
showAlert('error', 'Please enter a valid contact email');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
case 3:
|
||||
// Branding, no strict validation
|
||||
return true;
|
||||
|
||||
case 4:
|
||||
// Membership tiers
|
||||
const tier1Name = document.getElementById('tier1Name').value.trim();
|
||||
const tier2Name = document.getElementById('tier2Name').value.trim();
|
||||
const tier3Name = document.getElementById('tier3Name').value.trim();
|
||||
|
||||
if (!tier1Name || !tier2Name || !tier3Name) {
|
||||
showAlert('error', 'Please enter names for all membership tiers');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
case 5:
|
||||
// Admin account
|
||||
const username = document.getElementById('adminUsername').value.trim();
|
||||
const email = document.getElementById('adminEmail').value.trim();
|
||||
const password = document.getElementById('adminPassword').value;
|
||||
const passwordConfirm = document.getElementById('adminPasswordConfirm').value;
|
||||
|
||||
if (!username || username.length < 4) {
|
||||
showAlert('error', 'Username must be at least 4 characters');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
|
||||
showAlert('error', 'Username can only contain letters, numbers, and underscores');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!email || !isValidEmail(email)) {
|
||||
showAlert('error', 'Please enter a valid admin email');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!password || password.length < 8) {
|
||||
showAlert('error', 'Password must be at least 8 characters long');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!/[A-Z]/.test(password) || !/[a-z]/.test(password) || !/[0-9]/.test(password)) {
|
||||
showAlert('error', 'Password must contain uppercase, lowercase, and numbers');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (password !== passwordConfirm) {
|
||||
showAlert('error', 'Passwords do not match');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
case 6:
|
||||
// Features, no strict validation
|
||||
return true;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidEmail(email) {
|
||||
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return re.test(email);
|
||||
}
|
||||
|
||||
function saveFormData() {
|
||||
// Save to localStorage for recovery
|
||||
const formData = collectFormData();
|
||||
localStorage.setItem('easystreamSetup', JSON.stringify(formData));
|
||||
}
|
||||
|
||||
function loadSavedData() {
|
||||
// Load from localStorage if available
|
||||
const saved = localStorage.getItem('easystreamSetup');
|
||||
if (saved) {
|
||||
try {
|
||||
const data = JSON.parse(saved);
|
||||
// Populate form fields
|
||||
Object.keys(data).forEach(key => {
|
||||
const element = document.getElementById(key);
|
||||
if (element) {
|
||||
if (element.type === 'checkbox') {
|
||||
element.checked = data[key];
|
||||
} else {
|
||||
element.value = data[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to load saved data:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function collectFormData() {
|
||||
const formData = {};
|
||||
|
||||
// Platform config
|
||||
formData.platformName = document.getElementById('platformName').value;
|
||||
formData.platformTagline = document.getElementById('platformTagline').value;
|
||||
formData.domainName = document.getElementById('domainName').value;
|
||||
formData.contactEmail = document.getElementById('contactEmail').value;
|
||||
formData.timezone = document.getElementById('timezone').value;
|
||||
|
||||
// Branding
|
||||
formData.primaryColor = document.getElementById('primaryColor').value;
|
||||
formData.secondaryColor = document.getElementById('secondaryColor').value;
|
||||
formData.defaultTheme = document.getElementById('defaultTheme').value;
|
||||
formData.enableTheming = document.getElementById('enableTheming').checked;
|
||||
|
||||
// Membership tiers
|
||||
formData.tier1Name = document.getElementById('tier1Name').value;
|
||||
formData.tier1Upload = document.getElementById('tier1Upload').value;
|
||||
formData.tier1Storage = document.getElementById('tier1Storage').value;
|
||||
|
||||
formData.tier2Name = document.getElementById('tier2Name').value;
|
||||
formData.tier2Upload = document.getElementById('tier2Upload').value;
|
||||
formData.tier2Storage = document.getElementById('tier2Storage').value;
|
||||
formData.tier2Price = document.getElementById('tier2Price').value;
|
||||
|
||||
formData.tier3Name = document.getElementById('tier3Name').value;
|
||||
formData.tier3Upload = document.getElementById('tier3Upload').value;
|
||||
formData.tier3Storage = document.getElementById('tier3Storage').value;
|
||||
formData.tier3Price = document.getElementById('tier3Price').value;
|
||||
|
||||
// Admin account
|
||||
formData.adminUsername = document.getElementById('adminUsername').value;
|
||||
formData.adminEmail = document.getElementById('adminEmail').value;
|
||||
formData.adminPassword = document.getElementById('adminPassword').value;
|
||||
formData.adminPasswordConfirm = document.getElementById('adminPasswordConfirm').value;
|
||||
formData.adminDisplayName = document.getElementById('adminDisplayName').value;
|
||||
|
||||
// Features
|
||||
formData.enableRegistration = document.getElementById('enableRegistration').checked;
|
||||
formData.enableEmailVerification = document.getElementById('enableEmailVerification').checked;
|
||||
formData.enableLiveStreaming = document.getElementById('enableLiveStreaming').checked;
|
||||
formData.enableComments = document.getElementById('enableComments').checked;
|
||||
formData.enableDownloads = document.getElementById('enableDownloads').checked;
|
||||
formData.enableMonetization = document.getElementById('enableMonetization').checked;
|
||||
formData.enableTemplateBuilder = document.getElementById('enableTemplateBuilder').checked;
|
||||
formData.enableAnalytics = document.getElementById('enableAnalytics').checked;
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
function showReviewSummary() {
|
||||
const data = collectFormData();
|
||||
const summary = document.getElementById('reviewSummary');
|
||||
|
||||
summary.innerHTML = `
|
||||
<div style="margin-bottom: 24px;">
|
||||
<h3 style="color: #667eea; margin-bottom: 12px;">Platform Configuration</h3>
|
||||
<p><strong>Name:</strong> ${data.platformName}</p>
|
||||
<p><strong>Domain:</strong> ${data.domainName}</p>
|
||||
<p><strong>Email:</strong> ${data.contactEmail}</p>
|
||||
<p><strong>Timezone:</strong> ${data.timezone}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 24px;">
|
||||
<h3 style="color: #667eea; margin-bottom: 12px;">Membership Tiers</h3>
|
||||
<p><strong>${data.tier1Name}:</strong> ${data.tier1Upload}MB upload, ${data.tier1Storage}GB storage</p>
|
||||
<p><strong>${data.tier2Name}:</strong> ${data.tier2Upload}MB upload, ${data.tier2Storage}GB storage ($${data.tier2Price}/month)</p>
|
||||
<p><strong>${data.tier3Name}:</strong> ${data.tier3Upload}MB upload, ${data.tier3Storage}GB storage ($${data.tier3Price}/month)</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 24px;">
|
||||
<h3 style="color: #667eea; margin-bottom: 12px;">Admin Account</h3>
|
||||
<p><strong>Username:</strong> ${data.adminUsername}</p>
|
||||
<p><strong>Email:</strong> ${data.adminEmail}</p>
|
||||
<p><strong>Display Name:</strong> ${data.adminDisplayName || 'Administrator'}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 style="color: #667eea; margin-bottom: 12px;">Enabled Features</h3>
|
||||
<p>${data.enableRegistration ? '✓' : '✗'} User Registration</p>
|
||||
<p>${data.enableLiveStreaming ? '✓' : '✗'} Live Streaming</p>
|
||||
<p>${data.enableComments ? '✓' : '✗'} Video Comments</p>
|
||||
<p>${data.enableMonetization ? '✓' : '✗'} Monetization</p>
|
||||
<p>${data.enableTemplateBuilder ? '✓' : '✗'} Template Builder</p>
|
||||
<p>${data.enableAnalytics ? '✓' : '✗'} Analytics</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function startInstallation() {
|
||||
// Move to installation step
|
||||
document.getElementById('step7').classList.remove('active');
|
||||
currentStep = 8;
|
||||
document.getElementById('step8').classList.add('active');
|
||||
updateProgressBar();
|
||||
updateButtons();
|
||||
|
||||
const data = collectFormData();
|
||||
setupData = data;
|
||||
|
||||
try {
|
||||
// Step 1: Save configuration
|
||||
await installStep('Saving configuration...', async () => {
|
||||
const response = await fetch('setup.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ action: 'save_configuration', ...data })
|
||||
});
|
||||
return await response.json();
|
||||
});
|
||||
|
||||
// Step 2: Create admin user
|
||||
await installStep('Creating admin account...', async () => {
|
||||
const response = await fetch('setup.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ action: 'create_admin', ...data })
|
||||
});
|
||||
return await response.json();
|
||||
});
|
||||
|
||||
// Step 3: Finalize setup
|
||||
await installStep('Finalizing setup...', async () => {
|
||||
const response = await fetch('setup.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({ action: 'finalize', ...data })
|
||||
});
|
||||
return await response.json();
|
||||
});
|
||||
|
||||
// Success! Move to final step
|
||||
document.getElementById('step8').classList.remove('active');
|
||||
currentStep = 9;
|
||||
document.getElementById('step9').classList.add('active');
|
||||
updateProgressBar();
|
||||
|
||||
// Populate final details
|
||||
document.getElementById('finalPlatformName').textContent = data.platformName;
|
||||
document.getElementById('finalDomainName').textContent = data.domainName;
|
||||
document.getElementById('finalAdminUsername').textContent = data.adminUsername;
|
||||
|
||||
// Clear saved data
|
||||
localStorage.removeItem('easystreamSetup');
|
||||
|
||||
} catch (error) {
|
||||
document.getElementById('installStatus').textContent = 'Installation Failed';
|
||||
document.getElementById('installStep').innerHTML = `
|
||||
<div style="color: #e53e3e; padding: 20px; background: #fed7d7; border-radius: 8px;">
|
||||
<strong>Error:</strong> ${error.message}
|
||||
<br><br>
|
||||
<button onclick="location.reload()" class="btn btn-primary">Try Again</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
async function installStep(message, action) {
|
||||
document.getElementById('installStep').textContent = message;
|
||||
|
||||
// Add progress item
|
||||
const progress = document.getElementById('installProgress');
|
||||
const item = document.createElement('div');
|
||||
item.style.cssText = 'padding: 12px; background: #f7fafc; border-radius: 6px; margin-bottom: 8px; display: flex; align-items: center;';
|
||||
item.innerHTML = `<span class="spinner" style="width: 16px; height: 16px; margin-right: 12px; border-width: 2px;"></span><span>${message}</span>`;
|
||||
progress.appendChild(item);
|
||||
|
||||
try {
|
||||
const result = await action();
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Unknown error occurred');
|
||||
}
|
||||
|
||||
// Update to success
|
||||
item.innerHTML = `<span style="color: #48bb78; font-size: 20px; margin-right: 12px;">✓</span><span>${message} <strong style="color: #48bb78;">Done!</strong></span>`;
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
item.innerHTML = `<span style="color: #e53e3e; font-size: 20px; margin-right: 12px;">✗</span><span>${message} <strong style="color: #e53e3e;">Failed!</strong></span>`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function showAlert(type, message) {
|
||||
const alert = document.getElementById('alert');
|
||||
alert.className = `alert alert-${type} show`;
|
||||
alert.textContent = message;
|
||||
|
||||
// Auto-hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
alert.classList.remove('show');
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function clearAlert() {
|
||||
const alert = document.getElementById('alert');
|
||||
alert.classList.remove('show');
|
||||
}
|
||||
Reference in New Issue
Block a user