Files
MyClub/DOCS/PREMIUM_QUICK_START.md
T
Tomas Dvorak d5b4faea61 dev day #81
2025-11-03 19:54:39 +01:00

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

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>
  );
};

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

  1. Train Admins: Show how to toggle premium mode in settings
  2. Monitor Performance: Track Lighthouse scores, load times
  3. Collect Feedback: Survey users on premium vs standard
  4. Plan Enhancements: Additional premium pages, more templates
  5. 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! 🚀