22 KiB
Premium Version - Quick Start Guide
🚀 Implementation in 9 Days
This guide provides step-by-step instructions for implementing the premium version toggle system.
Day 1: Setup & Backend (8 hours)
Morning (4h): Analysis & Configuration
1. Review Premium Assets (1h)
cd /home/tdvorak/Desktop/PROG+HTML/Fotbal/fotbal-club/pro
# Count assets
ls css/ | wc -l # 45 CSS files
ls js/ | wc -l # 41 JS files
# Review key files
cat index.html | head -100
cat blog.html | head -100
cat 404.html
2. Update Environment Files (30min)
# Edit .env.example
cat >> .env.example << 'EOF'
# Premium Mode Configuration
PREMIUM_MODE=false
PREMIUM_HOMEPAGE=false
PREMIUM_BLOG=false
PREMIUM_404=false
PREMIUM_DISABLE_MYUIBRIX=false
EOF
# Copy to .env
cp .env.example .env
3. Update Backend Config (1h)
File: internal/config/config.go
type Config struct {
// ... existing fields
PremiumMode bool `env:"PREMIUM_MODE" envDefault:"false"`
PremiumHomepage bool `env:"PREMIUM_HOMEPAGE" envDefault:"false"`
PremiumBlog bool `env:"PREMIUM_BLOG" envDefault:"false"`
Premium404 bool `env:"PREMIUM_404" envDefault:"false"`
DisableMyUIbrix bool `env:"PREMIUM_DISABLE_MYUIBRIX" envDefault:"false"`
}
4. Create Database Migration (1.5h)
cd database/migrations
# Create migration files
cat > 000XXX_add_premium_settings.up.sql << 'EOF'
-- Add premium mode settings to settings table
ALTER TABLE settings ADD COLUMN IF NOT EXISTS premium_mode_active BOOLEAN DEFAULT FALSE;
ALTER TABLE settings ADD COLUMN IF NOT EXISTS premium_features TEXT;
ALTER TABLE settings ADD COLUMN IF NOT EXISTS premium_theme_variant VARCHAR(50) DEFAULT 'default';
-- Create index for faster queries
CREATE INDEX IF NOT EXISTS idx_settings_premium_mode ON settings(premium_mode_active);
-- Add comment
COMMENT ON COLUMN settings.premium_mode_active IS 'Whether premium/pro theme is active';
COMMENT ON COLUMN settings.premium_features IS 'JSON array of enabled premium features';
EOF
cat > 000XXX_add_premium_settings.down.sql << 'EOF'
-- Rollback premium settings
DROP INDEX IF EXISTS idx_settings_premium_mode;
ALTER TABLE settings DROP COLUMN IF EXISTS premium_theme_variant;
ALTER TABLE settings DROP COLUMN IF EXISTS premium_features;
ALTER TABLE settings DROP COLUMN IF EXISTS premium_mode_active;
EOF
# Run migration
make migrate-up
Afternoon (4h): Middleware & Routes
5. Create Premium Middleware (2h)
File: internal/middleware/premium_mode.go
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/yourusername/fotbal-club/internal/config"
"github.com/sirupsen/logrus"
)
// PremiumModeMiddleware adds premium mode context to requests
func PremiumModeMiddleware(cfg *config.Config) gin.HandlerFunc {
return func(c *gin.Context) {
if cfg.PremiumMode {
c.Set("premium_mode", true)
c.Set("disable_myuibrix", cfg.DisableMyUIbrix)
premiumFeatures := map[string]bool{
"homepage": cfg.PremiumHomepage,
"blog": cfg.PremiumBlog,
"404": cfg.Premium404,
}
c.Set("premium_features", premiumFeatures)
logrus.WithFields(logrus.Fields{
"path": c.Request.URL.Path,
"premium_features": premiumFeatures,
}).Debug("Premium mode active")
}
c.Next()
}
}
Register middleware in internal/routes/routes.go:
func SetupRoutes(router *gin.Engine, cfg *config.Config) {
// ... existing middleware
// Premium mode middleware
router.Use(middleware.PremiumModeMiddleware(cfg))
// ... rest of routes
}
6. Add Static Asset Routes (1h)
File: internal/routes/routes.go
// Serve premium static assets
router.Static("/premium/css", "./pro/css")
router.Static("/premium/js", "./pro/js")
router.Static("/premium/img", "./pro/img")
// Add cache headers for premium assets
router.Use(func(c *gin.Context) {
if strings.HasPrefix(c.Request.URL.Path, "/premium/") {
c.Header("Cache-Control", "public, max-age=31536000")
}
c.Next()
})
7. Extend Settings Controller (1h)
File: internal/controllers/base_controller.go
func (ctrl *BaseController) GetPublicSettings(c *gin.Context) {
settings, err := ctrl.DB.GetSettings()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
cfg := config.GetConfig()
// Add premium mode info
response := gin.H{
"settings": settings,
"premium": gin.H{
"enabled": cfg.PremiumMode,
"disable_myuibrix": cfg.DisableMyUIbrix,
},
}
if cfg.PremiumMode {
response["premium"].(gin.H)["features"] = gin.H{
"homepage": cfg.PremiumHomepage,
"blog": cfg.PremiumBlog,
"404": cfg.Premium404,
}
}
c.JSON(http.StatusOK, response)
}
Test Backend:
# Restart backend
make docker-restart-backend
# Test settings endpoint
curl http://localhost:8080/api/v1/settings/public | jq '.premium'
# Test static routes
curl -I http://localhost:8080/premium/css/bizoni.css
Day 2-3: Frontend Structure (16 hours)
Day 2 Morning (4h): Asset Utilities
8. Create Premium Asset Loader (2h)
File: frontend/src/utils/premiumAssets.ts
// Copy the full implementation from PREMIUM_ARCHITECTURE.md
// Includes: loadCSS(), loadJS(), loadPremiumAssets(), cleanupPremiumAssets()
9. Create Premium Theme Hook (2h)
File: frontend/src/hooks/usePremiumTheme.ts
// Copy implementation from PREMIUM_ARCHITECTURE.md
Day 2 Afternoon (4h): Premium Layout
10. Create Premium Layout Component (4h)
File: frontend/src/layouts/PremiumLayout.tsx
// Full implementation with asset loading, theme injection
// See PREMIUM_ARCHITECTURE.md for complete code
Test Layout:
cd frontend
npm run dev
# Visit http://localhost:3000
# Check browser console for asset loading logs
Day 3 Morning (4h): Navigation & Footer
11. Create Premium Navigation (2h)
File: frontend/src/components/premium/PremiumNav.tsx
import React from 'react';
import { Link } from 'react-router-dom';
import { useSettings } from '../../hooks/useSettings';
import { assetUrl } from '../../utils/url';
export const PremiumNav: React.FC = () => {
const { settings } = useSettings();
return (
<div id="lte-nav-wrapper" className="lte-layout-transparent-full lte-nav-color-white">
<nav className="lte-navbar affix" data-spy="affix" data-offset-top="0">
<div className="container">
<div className="lte-navbar-logo">
<Link to="/" className="lte-logo">
<img src={settings?.club_logo_url || '/img/logo.png'} alt={settings?.club_name} />
</Link>
</div>
<div className="lte-navbar-items navbar-collapse" id="navbar">
<ul className="lte-ul-nav">
<li><Link to="/"><span>Domů</span></Link></li>
<li><Link to="/o-nas"><span>O nás</span></Link></li>
<li><Link to="/blog"><span>Blog</span></Link></li>
<li><Link to="/kontakt"><span>Kontakt</span></Link></li>
<li><a href={settings?.gallery_url} target="_blank"><span>Fotogalerie</span></a></li>
</ul>
</div>
<button type="button" className="lte-navbar-toggle" id="open-button">
<span className="icon-bar top-bar"></span>
<span className="icon-bar middle-bar"></span>
<span className="icon-bar bottom-bar"></span>
</button>
</div>
</nav>
</div>
);
};
12. Create Premium Footer (2h)
File: frontend/src/components/premium/PremiumFooter.tsx
// Similar structure to footer in pro/index.html
// Inject dynamic data from settings
Day 3 Afternoon (4h): Zoom Slider
13. Create Zoom Slider Component (4h)
File: frontend/src/components/premium/ZoomSlider.tsx
This is the most complex component!
import React, { useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import { useArticles } from '../../hooks/useArticles';
import { useSettings } from '../../hooks/useSettings';
interface Slide {
image: string;
title: string;
subtitle?: string;
link: string;
}
export const ZoomSlider: React.FC = () => {
const { settings } = useSettings();
const { articles } = useArticles({ limit: 5, featured: true });
const sliderRef = useRef<HTMLDivElement>(null);
const slides: Slide[] = [
{
image: '/premium/img/2025.jpg',
title: `${settings?.club_name || 'FC'} 2025/2026`,
link: '/',
},
...articles.map(article => ({
image: article.image_url,
title: article.title,
subtitle: article.category?.name,
link: `/blog/${article.slug}`,
}))
];
useEffect(() => {
if (!sliderRef.current || !window.jQuery) return;
// Initialize zoom slider
const $slider = window.jQuery(sliderRef.current);
$slider.zoomSlider({
src: slides.map(s => s.image),
speed: 20000,
interval: 4500,
switchSpeed: 7000,
bullets: 'bottom',
overlay: 'black',
});
return () => {
// Cleanup
if ($slider.data('zoomSlider')) {
$slider.data('zoomSlider').destroy();
}
};
}, [slides]);
return (
<div
ref={sliderRef}
className="lte-slider-zoom zoom-default lte-zs-overlay-black bullets-bottom"
>
<div className="container lte-zs-slider-wrapper">
{slides.map((slide, index) => (
<div
key={index}
className={`lte-zs-slider-inner lte-zs-slide-${index}`}
data-index={index}
>
<div className="elementor elementor-36123">
<section className="elementor-section">
<div className="elementor-container">
<div className="elementor-column">
<div className="elementor-widget-wrap">
<h2 className="lte-header">
{slide.title} {slide.subtitle && <span>{slide.subtitle}</span>}
</h2>
<div className="lte-btn-wrap">
<Link to={slide.link} className="lte-btn btn-lg color-hover-white">
<span className="lte-btn-inner">
<span className="lte-btn-before"></span>
Zjistit více
</span>
</Link>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
))}
</div>
</div>
);
};
// TypeScript declarations
declare global {
interface Window {
jQuery: any;
}
interface JQuery {
zoomSlider(options?: any): JQuery;
}
}
Day 4-5: Premium Pages (16 hours)
Day 4: Premium Homepage (8h)
14. Create Premium Homepage (8h)
File: frontend/src/pages/PremiumHomePage.tsx
import React from 'react';
import { PremiumLayout } from '../layouts/PremiumLayout';
import { PremiumNav } from '../components/premium/PremiumNav';
import { PremiumFooter } from '../components/premium/PremiumFooter';
import { ZoomSlider } from '../components/premium/ZoomSlider';
import { usePremiumTheme } from '../hooks/usePremiumTheme';
// Import other sections as needed
export const PremiumHomePage: React.FC = () => {
const { settings } = usePremiumTheme();
return (
<PremiumLayout pageType="home">
<div className="lte-header-wrapper">
<PremiumNav />
</div>
{/* Hero Section */}
<section className="elementor-section">
<ZoomSlider />
</section>
{/* Next Match Section */}
<section className="next-match-section">
{/* Use existing NextMatch component */}
</section>
{/* News Section */}
<section className="news-section">
{/* Use existing NewsList component */}
</section>
{/* Other sections... */}
<PremiumFooter />
</PremiumLayout>
);
};
Day 5: Blog & 404 Pages (8h)
15. Create Premium Blog Page (6h)
File: frontend/src/pages/PremiumBlogPage.tsx
import React from 'react';
import { useParams } from 'react-router-dom';
import { PremiumLayout } from '../layouts/PremiumLayout';
import { PremiumNav } from '../components/premium/PremiumNav';
import { PremiumFooter } from '../components/premium/PremiumFooter';
import { useArticle } from '../hooks/useArticles';
export const PremiumBlogPage: React.FC = () => {
const { slug } = useParams<{ slug: string }>();
const { article, isLoading } = useArticle(slug);
if (isLoading) return <div>Loading...</div>;
if (!article) return <PremiumNotFoundPage />;
return (
<PremiumLayout pageType="blog">
<div className="lte-content-wrapper">
<PremiumNav />
<header className="lte-page-header lte-parallax-yes">
<div className="container">
<h1 className="lte-header long">{article.title}</h1>
</div>
</header>
<div className="container main-wrapper">
<div className="inner-page margin-post">
<div className="row row-center">
<div className="col-xl-8">
<section className="blog-post">
<article>
{article.image_url && (
<div className="image">
<img src={article.image_url} alt={article.title} />
</div>
)}
<div className="lte-description">
<div
className="text lte-text-page clearfix"
dangerouslySetInnerHTML={{ __html: article.content }}
/>
</div>
</article>
</section>
</div>
</div>
</div>
</div>
<PremiumFooter />
</div>
</PremiumLayout>
);
};
16. Create Premium 404 Page (2h)
File: frontend/src/pages/PremiumNotFoundPage.tsx
// Simple 404 page with premium styling
// See pro/404.html for structure
Day 6-7: Integration & Testing (16 hours)
Day 6: Router Integration (8h)
17. Update App Router (4h)
File: frontend/src/App.tsx
import { useSettings } from './hooks/useSettings';
import { PremiumHomePage } from './pages/PremiumHomePage';
import { PremiumBlogPage } from './pages/PremiumBlogPage';
import { PremiumNotFoundPage } from './pages/PremiumNotFoundPage';
const App: React.FC = () => {
const { settings, isLoading } = useSettings();
if (isLoading) return <LoadingScreen />;
const isPremium = settings?.premium_mode_active;
return (
<BrowserRouter>
<Routes>
<Route path="/" element={isPremium ? <PremiumHomePage /> : <HomePage />} />
<Route path="/blog/:slug" element={isPremium ? <PremiumBlogPage /> : <BlogPage />} />
<Route path="*" element={isPremium ? <PremiumNotFoundPage /> : <NotFoundPage />} />
{/* Other routes stay standard */}
<Route path="/hraci" element={<PlayersPage />} />
<Route path="/kontakt" element={<ContactPage />} />
{/* ... */}
</Routes>
</BrowserRouter>
);
};
18. Test Mode Switching (4h)
# Test 1: Standard Mode
cd /path/to/project
echo "PREMIUM_MODE=false" >> .env
make docker-restart
# Visit http://localhost:3000 - should see standard site
# Test 2: Premium Mode
echo "PREMIUM_MODE=true" >> .env
make docker-restart
# Visit http://localhost:3000 - should see premium site
# Test 3: Hybrid Mode
echo "PREMIUM_MODE=true" >> .env
echo "PREMIUM_HOMEPAGE=true" >> .env
echo "PREMIUM_BLOG=false" >> .env
make docker-restart
# Homepage premium, blog standard
Day 7: Performance Testing (8h)
19. Run Performance Audits (4h)
# Lighthouse audit
npm install -g lighthouse
lighthouse http://localhost:3000 --view
# Bundle analysis
cd frontend
npx webpack-bundle-analyzer stats.json
# Load time testing
curl -w "@curl-format.txt" -o /dev/null -s http://localhost:3000
20. Cross-Browser Testing (4h)
- Test on Chrome, Firefox, Safari, Edge
- Test mobile responsive (360px, 768px, 1024px)
- Verify animations work
- Check console for errors
Day 8-9: Documentation & Polish (16 hours)
Day 8: Documentation (8h)
21. Write User Guide (4h)
- How to enable premium mode
- Feature comparison
- Troubleshooting guide
22. Write Developer Guide (4h)
- Architecture overview
- How to add new premium pages
- Customization guide
Day 9: Final Polish (8h)
23. Code Review & Cleanup (4h)
- Remove console.logs
- Add TypeScript types
- Fix linter warnings
- Optimize imports
24. Deployment Preparation (4h)
# Update .env.example
cat >> .env.example << 'EOF'
# Premium Mode (Production)
PREMIUM_MODE=true
PREMIUM_HOMEPAGE=true
PREMIUM_BLOG=true
PREMIUM_404=true
PREMIUM_DISABLE_MYUIBRIX=true
EOF
# Create deployment checklist
cat > DEPLOYMENT_CHECKLIST.md << 'EOF'
- [ ] Run database migration
- [ ] Copy premium assets to /var/www/premium
- [ ] Update Nginx config
- [ ] Test in staging
- [ ] Backup current database
- [ ] Deploy to production
- [ ] Test premium mode
- [ ] Monitor logs
- [ ] Rollback plan ready
EOF
🎯 Quick Commands
Enable Premium Mode
# Set environment
export PREMIUM_MODE=true
export PREMIUM_HOMEPAGE=true
export PREMIUM_BLOG=true
# Or edit .env file
echo "PREMIUM_MODE=true" >> .env
# Restart
make docker-restart
Disable Premium Mode
# Edit .env
sed -i 's/PREMIUM_MODE=true/PREMIUM_MODE=false/' .env
# Restart
make docker-restart
Test Premium Assets
# Test CSS loading
curl http://localhost:8080/premium/css/bizoni.css | head
# Test JS loading
curl http://localhost:8080/premium/js/jquery.min.js | head
# Test images
curl -I http://localhost:8080/premium/img/logo.png
Debug Premium Mode
# Check settings API
curl http://localhost:8080/api/v1/settings/public | jq '.premium'
# Check backend logs
docker logs fotbal-club-backend | grep premium
# Check frontend console
# Open browser DevTools → Console → Filter "premium"
✅ Verification Checklist
Backend
- Environment variables added
- Database migration successful
- Static routes working (
/premium/css/*,/premium/js/*) - Settings API returns premium config
- Middleware adds context correctly
Frontend
- Premium components created
- Asset loader working
- Theme hook injecting colors
- Navigation working
- Zoom slider animating
- Blog pages rendering
- 404 page showing
Integration
- Router switches based on
premium_mode_active - MyUIbrix disabled when premium active
- Standard mode still works
- Can toggle between modes
- No console errors
Performance
- Page load < 3s
- Lighthouse score > 85
- No memory leaks
- Assets cached properly
Cross-Browser
- Chrome works
- Firefox works
- Safari works
- Mobile responsive
🚨 Common Issues & Solutions
Issue: Premium CSS not loading
Solution:
# Check static route
curl -I http://localhost:8080/premium/css/bizoni.css
# Check file exists
ls pro/css/bizoni.css
# Check Nginx config (production)
sudo nano /etc/nginx/sites-available/fotbal-club
Issue: Zoom slider not working
Solution:
// Check jQuery loaded first
useEffect(() => {
console.log('jQuery:', typeof window.jQuery);
console.log('zoomSlider:', typeof window.jQuery?.fn?.zoomSlider);
}, []);
// Load jQuery before zoom slider
const jsFiles = [
'/premium/js/jquery.min.js', // FIRST
'/premium/js/jquery.zoomslider.js', // AFTER jQuery
];
Issue: Colors not matching club theme
Solution:
// Check settings loaded
const { settings } = useSettings();
console.log('Club colors:', settings?.primary_color);
// Check CSS variables applied
const root = document.documentElement;
console.log('CSS var:', getComputedStyle(root).getPropertyValue('--lte-main-color'));
Issue: Router not switching
Solution:
// Debug in App.tsx
console.log('Settings:', settings);
console.log('Premium active:', settings?.premium_mode_active);
console.log('Is premium:', isPremium);
// Check API response
fetch('/api/v1/settings/public')
.then(r => r.json())
.then(data => console.log('API response:', data));
📚 Next Steps After Implementation
- Train Admins: Show how to toggle premium mode in settings
- Monitor Performance: Track Lighthouse scores, load times
- Collect Feedback: Survey users on premium vs standard
- Plan Enhancements: Additional premium pages, more templates
- Document Customizations: How to modify premium templates
🎉 Success Criteria
✅ Backend: Environment toggle works, assets served correctly
✅ Frontend: Premium pages render, animations work, responsive
✅ Integration: Can switch modes without errors
✅ Performance: Load time < 3s, Lighthouse > 85
✅ Documentation: User guide, developer guide, troubleshooting
Congratulations! Premium mode is live! 🚀