commit c7f669fc117602ea6267d24c9917f268bb2e7f2c Author: htom Date: Sun Dec 21 20:40:32 2025 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..fd16ab8 --- /dev/null +++ b/App.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { HashRouter as Router, Routes, Route } from 'react-router-dom'; +import { Navbar } from './components/Navbar'; +import { Footer } from './components/Footer'; +import { Home } from './pages/Home'; +import { Services } from './pages/Services'; +import { Products } from './pages/Products'; +import { References } from './pages/References'; +import { Contact } from './pages/Contact'; +import { Login } from './pages/auth/Login'; +import { Register } from './pages/auth/Register'; +import { ForgotPassword } from './pages/auth/ForgotPassword'; +import { ResetPassword } from './pages/auth/ResetPassword'; +import { Dashboard } from './pages/Dashboard'; +import { Admin } from './pages/Admin'; +import { Privacy } from './pages/Privacy'; +import { Terms } from './pages/Terms'; +import { FAQ } from './pages/FAQ'; +import { ProtectedRoute } from './components/ProtectedRoute'; +import { AuthProvider } from './context/AuthContext'; +import ScrollToTop from './components/ScrollToTop'; +import { AnalyticsTracker } from './components/AnalyticsTracker'; +import { ProfileCompleter } from './components/ProfileCompleter'; +import { CookieBanner } from './components/CookieBanner'; + +// Demo Pages +import { SweetCraving } from './pages/demos/SweetCraving'; +import { BlueWave } from './pages/demos/BlueWave'; +import { Steelguard } from './pages/demos/Steelguard'; + +// Helper component to scroll to top on route change +const ScrollToTopHelper = () => { + return ; +}; + +const App: React.FC = () => { + return ( + + +
+ + + + + + + {/* Main Application Routes */} +
} /> +
} /> +
} /> +
} /> +
} /> + + {/* Info Pages */} +
} /> +
} /> +
} /> + + {/* Auth Routes */} +
} /> +
} /> +
} /> +
} /> + + {/* Protected Routes */} + + <>
+ + } + /> + + + <>
+ + } + /> + + {/* Demo Routes - These have their own custom layouts/navbars */} + } /> + } /> + } /> + + +
+
+
+ ); +}; + +export default App; \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2bd8010 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +
+GHBanner +
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/drive/1-FVDkCL1ZZR8qhPZRheiLI83H5WiQpXi + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/SUPABASE_AUTO_PROFILES.sql b/SUPABASE_AUTO_PROFILES.sql new file mode 100644 index 0000000..e69de29 diff --git a/SUPABASE_SETUP.sql b/SUPABASE_SETUP.sql new file mode 100644 index 0000000..f958e34 --- /dev/null +++ b/SUPABASE_SETUP.sql @@ -0,0 +1 @@ +nb+b^JZmIv+h,z۩ʋuZm \ No newline at end of file diff --git a/components/AnalyticsTracker.tsx b/components/AnalyticsTracker.tsx new file mode 100644 index 0000000..3a37dcd --- /dev/null +++ b/components/AnalyticsTracker.tsx @@ -0,0 +1,57 @@ +import React, { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; +import { supabase, isSupabaseConfigured } from '../lib/supabaseClient'; +import { useAuth } from '../context/AuthContext'; + +export const AnalyticsTracker: React.FC = () => { + const location = useLocation(); + const { user, loading } = useAuth(); + + useEffect(() => { + // Wait for auth to initialize so we don't log "null" user then "user id" immediately after + if (loading) return; + + const trackVisit = async () => { + // 1. Check for cookie consent + // If user hasn't allowed or has explicitly denied, do not track. + const consent = localStorage.getItem('cookie_consent'); + if (consent !== 'true') { + return; + } + + // 2. Don't track if Supabase isn't configured (Demo mode) + if (!isSupabaseConfigured) return; + + // 3. Check if we already logged this session + // sessionStorage persists while the tab is open, ensuring 1 count per session + const hasLoggedSession = sessionStorage.getItem('motionweb_visit_logged'); + + if (hasLoggedSession) return; + + try { + // Mark immediately to prevent race conditions if effect fires multiple times rapidly + sessionStorage.setItem('motionweb_visit_logged', 'true'); + + await supabase.from('page_visits').insert({ + page_path: location.pathname + location.hash, + user_agent: navigator.userAgent, + user_id: user?.id || null + }); + + } catch (error) { + // Silently fail for analytics to not disrupt user experience + console.error('Analytics tracking error:', error); + } + }; + + trackVisit(); + + // Add listener for storage events to re-check tracking if consent changes mid-session + const handleStorageChange = () => trackVisit(); + window.addEventListener('storage', handleStorageChange); + + return () => window.removeEventListener('storage', handleStorageChange); + }, [location, user, loading]); + + return null; +}; \ No newline at end of file diff --git a/components/Button.tsx b/components/Button.tsx new file mode 100644 index 0000000..ce8e17e --- /dev/null +++ b/components/Button.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: 'primary' | 'secondary' | 'outline' | 'white'; + size?: 'sm' | 'md' | 'lg'; + fullWidth?: boolean; +} + +export const Button: React.FC = ({ + children, + variant = 'primary', + size = 'md', + fullWidth = false, + className = '', + ...props +}) => { + const baseStyles = "inline-flex items-center justify-center font-semibold transition-all duration-300 rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2"; + + const variants = { + primary: "bg-gradient-to-r from-primary to-secondary hover:from-primary-dark hover:to-secondary-dark text-white shadow-lg hover:shadow-xl border-transparent focus:ring-primary", + secondary: "bg-secondary hover:bg-secondary-dark text-white shadow-md focus:ring-secondary", + outline: "bg-transparent border-2 border-primary text-primary hover:bg-primary hover:text-white focus:ring-primary", + white: "bg-white text-primary hover:bg-gray-100 shadow-md focus:ring-white" + }; + + const sizes = { + sm: "px-4 py-2 text-sm", + md: "px-6 py-3 text-base", + lg: "px-8 py-4 text-lg", + }; + + const widthClass = fullWidth ? "w-full" : ""; + + return ( + + ); +}; \ No newline at end of file diff --git a/components/CookieBanner.tsx b/components/CookieBanner.tsx new file mode 100644 index 0000000..fc63af1 --- /dev/null +++ b/components/CookieBanner.tsx @@ -0,0 +1,132 @@ +import React, { useState, useEffect } from 'react'; +import { X, Check, Cookie } from 'lucide-react'; +import { Link } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; +import { supabase, isSupabaseConfigured } from '../lib/supabaseClient'; + +export const CookieBanner: React.FC = () => { + const { user } = useAuth(); + const [isVisible, setIsVisible] = useState(false); + const [hasChecked, setHasChecked] = useState(false); + + useEffect(() => { + const checkAndSyncConsent = async () => { + // 1. Check Local Storage choice + const localConsent = localStorage.getItem('cookie_consent'); + + // If a choice was already made locally, we hide the banner immediately + if (localConsent !== null) { + setIsVisible(false); + setHasChecked(true); + + // AUTO-SYNC: If user is logged in, ensure the local choice is synced to the database + if (user && isSupabaseConfigured) { + try { + const consentValue = localConsent === 'true'; + await supabase + .from('profiles') + .update({ cookie_consent: consentValue }) + .eq('id', user.id); + } catch (e) { + console.error("Error auto-syncing cookie consent to DB:", e); + } + } + return; + } + + // 2. If NO local choice but user IS logged in, try to fetch from DB + if (user && isSupabaseConfigured) { + try { + const { data, error } = await supabase + .from('profiles') + .select('cookie_consent') + .eq('id', user.id) + .maybeSingle(); + + if (!error && data && data.cookie_consent !== null) { + // Save DB choice to local storage and hide + localStorage.setItem('cookie_consent', data.cookie_consent.toString()); + setIsVisible(false); + setHasChecked(true); + return; + } + } catch (e) { + console.error("Error checking cookie consent in DB:", e); + } + } + + // 3. If NO choice found anywhere, show the banner + setIsVisible(true); + setHasChecked(true); + }; + + checkAndSyncConsent(); + }, [user]); + + const handleConsent = async (allowed: boolean) => { + // Save to local storage immediately to hide banner + localStorage.setItem('cookie_consent', allowed.toString()); + setIsVisible(false); + + // If logged in, also update the database + if (user && isSupabaseConfigured) { + try { + await supabase + .from('profiles') + .update({ cookie_consent: allowed }) + .eq('id', user.id); + } catch (e) { + console.error("Error saving cookie consent to DB manually:", e); + } + } + + // Trigger storage event for AnalyticsTracker and other listeners + window.dispatchEvent(new Event('storage')); + }; + + if (!isVisible || !hasChecked) return null; + + return ( +
+
+
+
+ +
+
+

Sütik és Adatvédelem

+

+ Az élmény fokozása érdekében sütiket használunk. Az elfogadással hozzájárul az anonim látogatottsági adatok gyűjtéséhez. +

+
+ +
+ +
+ + +
+ +
+ További információkért olvassa el az Adatkezelési tájékoztatónkat. +
+
+
+ ); +}; \ No newline at end of file diff --git a/components/FeedbackModal.tsx b/components/FeedbackModal.tsx new file mode 100644 index 0000000..f2f9df0 --- /dev/null +++ b/components/FeedbackModal.tsx @@ -0,0 +1,289 @@ +import React, { useState } from 'react'; +import { X, Info, CheckCircle, AlertTriangle, MessageSquare, Calendar, ChevronDown, ChevronUp } from 'lucide-react'; +import { Button } from './Button'; + +interface FeedbackModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (feedbackData: any) => Promise; + loading: boolean; +} + +export const FeedbackModal: React.FC = ({ isOpen, onClose, onSubmit, loading }) => { + // --- STATE --- + const [mainDecision, setMainDecision] = useState<'approved' | 'minor' | 'major' | null>(null); + + // Approval Flow + const [approvalConfirmed, setApprovalConfirmed] = useState(false); + + // Revision Flow + const [designCheckboxes, setDesignCheckboxes] = useState([]); + const [designText, setDesignText] = useState(''); + + const [contentCheckboxes, setContentCheckboxes] = useState([]); + const [contentText, setContentText] = useState(''); + + const [structureCheckboxes, setStructureCheckboxes] = useState([]); + const [structureText, setStructureText] = useState(''); + + const [funcCheckboxes, setFuncCheckboxes] = useState([]); + const [funcText, setFuncText] = useState(''); + + const [priorityText, setPriorityText] = useState(''); + + const [deadlineType, setDeadlineType] = useState<'none' | 'date' | 'discuss'>('none'); + const [deadlineDate, setDeadlineDate] = useState(''); + + const [extraNotes, setExtraNotes] = useState(''); + + const [revisionConfirmed, setRevisionConfirmed] = useState(false); + + if (!isOpen) return null; + + // --- HANDLERS --- + const handleCheckbox = ( + currentList: string[], + setList: React.Dispatch>, + value: string, + noneValue: string + ) => { + if (value === noneValue) { + if (currentList.includes(noneValue)) { + setList([]); + } else { + setList([noneValue]); + } + } else { + let newList = currentList.filter(item => item !== noneValue); + if (newList.includes(value)) { + newList = newList.filter(item => item !== value); + } else { + newList = [...newList, value]; + } + setList(newList); + } + }; + + const isRevision = mainDecision === 'minor' || mainDecision === 'major'; + + const handleSubmit = () => { + const feedbackData = { + decision: mainDecision, + submittedAt: new Date().toISOString(), + approval: mainDecision === 'approved' ? { + confirmed: approvalConfirmed + } : null, + revision: isRevision ? { + design: { selected: designCheckboxes, comment: designText }, + content: { selected: contentCheckboxes, comment: contentText }, + structure: { selected: structureCheckboxes, comment: structureText }, + functionality: { selected: funcCheckboxes, comment: funcText }, + priority: priorityText, + deadline: { type: deadlineType, date: deadlineDate }, + extraNotes: extraNotes, + confirmed: revisionConfirmed + } : null + }; + + onSubmit(feedbackData); + }; + + const commonInputStyles = "w-full p-4 border border-gray-300 rounded-xl text-sm font-medium text-black placeholder-gray-400 focus:ring-4 focus:ring-primary/10 focus:border-primary outline-none bg-white transition-all shadow-sm"; + + const renderFeedbackCategory = ( + title: string, + options: string[], + selected: string[], + setSelected: React.Dispatch>, + textValue: string, + setTextValue: React.Dispatch>, + placeholder: string + ) => { + const noneOption = options[options.length - 1]; + const showTextarea = selected.length > 0 && !selected.includes(noneOption); + + return ( +
+

{title}

+
+ {options.map(opt => ( + + ))} +
+ {showTextarea && ( + + )} +
+ ); + }; + + return ( +
+
+ + + +
+
+
+ +
+
+

Visszajelzés a Weboldalról

+

A bemutató verzió alapján kérjük jelezd, ha valamit módosítanál.

+
+
+
+ +
+
+

+ Döntés a továbblépésről * +

+
+ {[ + { id: 'approved', label: 'Igen, jóváhagyom a terveket', color: 'green', icon: '✅' }, + { id: 'minor', label: 'Alapvetően jó, de kisebb javításokat kérek', color: 'yellow', icon: '🔧' }, + { id: 'major', label: 'Nem megfelelő, jelentős módosításra van szükség', color: 'red', icon: '❌' } + ].map(opt => ( + + ))} +
+
+ + {mainDecision === 'approved' && ( +
+

+ Megerősítés +

+ +
+ )} + + {isRevision && ( +
+ + {renderFeedbackCategory( + "Dizájn módosítások", + ["Színek", "Betűtípusok", "Elrendezés", "Képek / Illusztrációk", "Nem szeretnék dizájn módosítást"], + designCheckboxes, setDesignCheckboxes, + designText, setDesignText, + "Írd le pontosan, mit változtatnál a megjelenésen..." + )} + + {renderFeedbackCategory( + "Tartalmi változtatások", + ["Szövegek stílusa", "Adatok pontosítása", "Hiányzó tartalom", "Nem szeretnék tartalmi módosítást"], + contentCheckboxes, setContentCheckboxes, + contentText, setContentText, + "Írd le a szöveges módosításokat..." + )} + +
+

Mi a legfontosabb kérésed? *

+ +
+ +
+

Egyéb észrevételek

+ +
+ +
+ +
+ +
+ )} +
+ +
+ + + {mainDecision === 'approved' && ( + + )} + + {isRevision && ( + + )} + + {!mainDecision && ( + + )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/components/Footer.tsx b/components/Footer.tsx new file mode 100644 index 0000000..4751d03 --- /dev/null +++ b/components/Footer.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { Facebook, Instagram, Code2, Mail } from 'lucide-react'; +import { Link } from 'react-router-dom'; + +export const Footer: React.FC = () => { + return ( +
+
+
+ {/* Brand Info */} +
+
+
+ +
+ MotionWeb +
+

+ Innovatív webes megoldások, amelyek üzleti eredményeket hoznak. Teljeskörű digitális partner cégek számára. +

+ +
+ + {/* Services */} +
+

Szolgáltatások

+
    +
  • Egyedi Weboldal
  • +
  • Reszponzív Design
  • +
  • SEO Optimalizálás
  • +
  • UI/UX Design
  • +
+
+ + {/* Contact Info - Modified */} +
+

Elérhetőség

+ +
+ + {/* Information (Replaced Newsletter) */} +
+

Információk

+
    +
  • Adatkezelési tájékoztató
  • +
  • Általános Szerződési Feltételek
  • +
  • Gyakori Kérdések
  • +
+
+
+ +
+

+ © {new Date().getFullYear()} Motion Web Stúdió. Minden jog fenntartva. +

+
+
+
+ ); +}; \ No newline at end of file diff --git a/components/Navbar.tsx b/components/Navbar.tsx new file mode 100644 index 0000000..21f73fc --- /dev/null +++ b/components/Navbar.tsx @@ -0,0 +1,236 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Menu, X, Code2, User, LogIn, UserPlus, LogOut, LayoutDashboard, ShieldAlert } from 'lucide-react'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; +import { Button } from './Button'; +import { useAuth } from '../context/AuthContext'; + +export const Navbar: React.FC = () => { + const [isOpen, setIsOpen] = useState(false); + const [isProfileOpen, setIsProfileOpen] = useState(false); + const [scrolled, setScrolled] = useState(false); + const location = useLocation(); + const navigate = useNavigate(); + const profileRef = useRef(null); + const { user, signOut, isAdmin } = useAuth(); + + useEffect(() => { + const handleScroll = () => { + setScrolled(window.scrollY > 20); + }; + window.addEventListener('scroll', handleScroll); + + // Close dropdown when clicking outside + const handleClickOutside = (event: MouseEvent) => { + if (profileRef.current && !profileRef.current.contains(event.target as Node)) { + setIsProfileOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + + return () => { + window.removeEventListener('scroll', handleScroll); + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + useEffect(() => { + setIsOpen(false); + setIsProfileOpen(false); + }, [location]); + + const handleLogout = async () => { + await signOut(); + navigate('/'); + setIsProfileOpen(false); + }; + + // Helper to determine if a link is active based on hash or path + const isActive = (path: string) => { + if (path === '/') return location.pathname === '/' && !location.hash; + if (path.startsWith('/#')) return location.hash === path.substring(1); + return location.pathname === path; + }; + + const navLinks = [ + { name: 'Kezdőlap', path: '/' }, + { name: 'Szolgáltatások', path: '/#services' }, + { name: 'Referenciák', path: '/#references' }, + { name: 'Csomagok', path: '/#products' }, + ]; + + const linkClass = (path: string) => `text-sm font-medium transition-colors hover:text-primary ${ + isActive(path) ? 'text-primary' : ( + !scrolled && location.pathname === '/' ? 'text-gray-100 hover:text-white' : 'text-gray-700' + ) + }`; + + return ( + + ); +}; \ No newline at end of file diff --git a/components/OrderForm.tsx b/components/OrderForm.tsx new file mode 100644 index 0000000..34593a5 --- /dev/null +++ b/components/OrderForm.tsx @@ -0,0 +1,876 @@ +import React, { useState } from 'react'; +import { + Send, CheckCircle, AlertCircle, Globe, Server, Check, + ArrowRight, ArrowLeft, User, FileText, Target, Layout, + Palette, Zap, Lightbulb, Settings, ClipboardCheck, Lock, + Cloud, Upload +} from 'lucide-react'; +import { Button } from './Button'; +import { useAuth } from '../context/AuthContext'; +import { Link } from 'react-router-dom'; +import { supabase, isSupabaseConfigured } from '../lib/supabaseClient'; + +interface Inspiration { + url: string; + comment: string; +} + +export const OrderForm: React.FC = () => { + const { user, loading } = useAuth(); + const [currentStep, setCurrentStep] = useState(1); + const [isSubmitted, setIsSubmitted] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [submitError, setSubmitError] = useState(null); + const [errors, setErrors] = useState([]); + const [privacyAccepted, setPrivacyAccepted] = useState(false); + const [aszfAccepted, setAszfAccepted] = useState(false); + const totalSteps = 9; + + const [formData, setFormData] = useState({ + // 1. Kapcsolattartás + name: '', + company: '', + email: user?.email || '', + phone: '', + package: 'Pro Web', // Default selection updated + + // 2. Bemutatkozás + description: '', + + // 3. Célok + goals: [] as string[], + goalOther: '', + successCriteria: '', + + // 4. Tartalom + content: [] as string[], + contentOther: '', + existingAssets: 'Nincs' as 'Igen' | 'Nem' | 'Részben', + contentLink: '', // New field for drive link + + // 5. Design + primaryColor: '', + secondaryColor: '', + balanceColor: '', + style: [] as string[], + targetAudience: '', + + // 6. Funkciók + features: [] as string[], + + // 7. Inspirációk + inspirations: [ + { url: '', comment: '' }, + { url: '', comment: '' }, + { url: '', comment: '' } + ] as Inspiration[], + + // 8. Extrák & Megjegyzések + extras: [] as string[], + domainName: '', + notes: '' + }); + + // Helper arrays + const colorOptions = [ + { name: 'Piros', value: '#ef4444' }, + { name: 'Kék', value: '#3b82f6' }, + { name: 'Zöld', value: '#22c55e' }, + { name: 'Lila', value: '#a855f7' }, + { name: 'Fekete', value: '#171717' }, + { name: 'Fehér', value: '#ffffff' }, + { name: 'Szürke', value: '#6b7280' }, + { name: 'Narancs', value: '#f97316' }, + { name: 'Sárga', value: '#eab308' }, + { name: 'Türkiz', value: '#14b8a6' }, + { name: 'Barna', value: '#78350f' }, + ]; + + const styleOptions = [ + 'Modern és letisztult', 'Üzleti és professzionális', 'Fiatalos és energikus', + 'Spirituális / nyugodt', 'Természetes / barátságos', 'Luxus / prémium' + ]; + + const steps = [ + { id: 1, title: 'Kapcsolat', icon: User }, + { id: 2, title: 'Bemutatkozás', icon: FileText }, + { id: 3, title: 'Célok', icon: Target }, + { id: 4, title: 'Tartalom', icon: Layout }, + { id: 5, title: 'Design', icon: Palette }, + { id: 6, title: 'Funkciók', icon: Zap }, + { id: 7, title: 'Inspirációk', icon: Lightbulb }, + { id: 8, title: 'Extrák', icon: Settings }, + { id: 9, title: 'Összegzés', icon: ClipboardCheck }, + ]; + + // Auto-fill user data (Name and Email) when user loads + React.useEffect(() => { + const prefillUserData = async () => { + if (!user) return; + + let emailToSet = user.email || ''; + let nameToSet = ''; + + // 1. Try metadata (from session - fastest) + const meta = user.user_metadata; + if (meta?.last_name && meta?.first_name) { + // Hungarian order: Lastname Firstname + nameToSet = `${meta.last_name} ${meta.first_name}`; + } + + // 2. Fallback to DB if Supabase is configured and metadata is missing name + if (!nameToSet && isSupabaseConfigured) { + try { + const { data } = await supabase + .from('profiles') + .select('first_name, last_name') + .eq('id', user.id) + .maybeSingle(); + + if (data?.first_name && data?.last_name) { + nameToSet = `${data.last_name} ${data.first_name}`; + } + } catch (error) { + console.error('Error fetching user profile for order form:', error); + } + } + + setFormData(prev => { + // Only update if fields are empty to avoid overwriting user input + if (prev.name && prev.email) return prev; + + return { + ...prev, + email: prev.email || emailToSet, + name: prev.name || nameToSet + }; + }); + }; + + prefillUserData(); + }, [user]); + + // If loading auth state, show a skeleton or loader + if (loading) { + return ( +
+
Betöltés...
+
+ ); + } + + // Auth Protection - Lock Screen + if (!user) { + return ( +
+
+
+ +
+

Jelentkezzen be a rendeléshez

+

+ Weboldal rendelés leadásához kérjük, jelentkezzen be fiókjába, vagy regisztráljon egyet ingyenesen. +

+
+
+
+ + + + + + +
+

+ A regisztráció mindössze 1 percet vesz igénybe, és segít a projekt későbbi nyomon követésében. +

+
+
+ ); + } + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + }; + + const handleCheckboxChange = (category: 'goals' | 'content' | 'style' | 'features' | 'extras', value: string) => { + setFormData(prev => { + const list = prev[category]; + if (list.includes(value)) { + return { ...prev, [category]: list.filter(item => item !== value) }; + } else { + return { ...prev, [category]: [...list, value] }; + } + }); + }; + + const handleInspirationChange = (index: number, field: 'url' | 'comment', value: string) => { + const newInspirations = [...formData.inspirations]; + newInspirations[index] = { ...newInspirations[index], [field]: value }; + setFormData(prev => ({ ...prev, inspirations: newInspirations })); + }; + + const validateStep = (step: number): boolean => { + const newErrors: string[] = []; + + if (step === 1) { + if (!formData.name) newErrors.push('A név megadása kötelező.'); + if (!formData.email) newErrors.push('Az e-mail cím megadása kötelező.'); + if (!formData.phone) newErrors.push('A telefonszám megadása kötelező.'); + } + + if (newErrors.length > 0) { + setErrors(newErrors); + return false; + } + + setErrors([]); + return true; + }; + + const nextStep = () => { + if (validateStep(currentStep)) { + if (currentStep < totalSteps) { + setCurrentStep(prev => prev + 1); + window.scrollTo({ top: document.getElementById('order-form-container')?.offsetTop || 0, behavior: 'smooth' }); + } else { + handleSubmit(); + } + } + }; + + const prevStep = () => { + if (currentStep > 1) { + setCurrentStep(prev => prev - 1); + window.scrollTo({ top: document.getElementById('order-form-container')?.offsetTop || 0, behavior: 'smooth' }); + } + }; + + const handleSubmit = async () => { + setIsSubmitting(true); + setSubmitError(null); + + if (!privacyAccepted) { + setSubmitError('A rendelés leadásához el kell fogadnia az Adatkezelési tájékoztatót.'); + setIsSubmitting(false); + return; + } + + if (!aszfAccepted) { + setSubmitError('A rendelés leadásához el kell fogadnia az Általános Szerződési Feltételeket (ÁSZF).'); + setIsSubmitting(false); + return; + } + + // Determine amount text + const amount = + formData.package === 'Landing Page' ? '190.000 Ft' : + formData.package === 'Pro Web' ? '350.000 Ft' : + 'Egyedi Árazás'; + + if (isSupabaseConfigured && user) { + try { + const { error } = await supabase.from('orders').insert({ + user_id: user.id, + customer_name: formData.name, + customer_email: formData.email, + package: formData.package, + status: 'new', + amount: amount, + details: formData + }); + + if (error) { + throw error; + } + + setIsSubmitted(true); + window.scrollTo({ top: document.getElementById('order-form-container')?.offsetTop || 0, behavior: 'smooth' }); + } catch (err: any) { + console.error('Error submitting order:', err); + setSubmitError('Hiba történt a rendelés elküldésekor: ' + err.message); + } finally { + setIsSubmitting(false); + } + } else { + // Fallback for Demo Mode + console.log('Demo Mode: Order Data:', formData); + await new Promise(resolve => setTimeout(resolve, 1500)); // Simulate delay + setIsSubmitted(true); + setIsSubmitting(false); + window.scrollTo({ top: document.getElementById('order-form-container')?.offsetTop || 0, behavior: 'smooth' }); + } + }; + + // Modern Input Style Class - MotionWeb Style + const inputClass = "w-full px-4 py-3 rounded-lg border border-gray-200 bg-white text-[#111111] placeholder-gray-400 focus:ring-4 focus:ring-[#4e6bff]/10 focus:border-[#4e6bff] outline-none transition-all shadow-sm"; + const labelClass = "text-sm font-semibold text-gray-800 block mb-2"; + const checkboxClass = "w-5 h-5 text-[#4e6bff] rounded focus:ring-[#4e6bff] border-gray-300 flex-shrink-0 transition-colors cursor-pointer"; + + const ColorPickerSection = ({ label, selected, onChange }: { label: string, selected: string, onChange: (color: string) => void }) => ( +
+ +
+ {colorOptions.map((color) => ( + + ))} +
+

{selected ? `Választott: ${selected}` : ''}

+
+ ); + + if (isSubmitted) { + return ( +
+
+ +
+

Rendelését sikeresen rögzítettük!

+

+ Átirányítjuk az előlegfizetési oldalra. Amennyiben ez nem történik meg, kollégáink hamarosan felveszik Önnel a kapcsolatot. +

+ +
+ ); + } + + return ( +
+ {/* Light Gradient Header & Step Navigation */} +
+
+

Rendelés Leadása

+

Töltse ki az űrlapot a pontos ajánlatadáshoz, és segítünk megvalósítani elképzeléseit.

+
+ + {/* Desktop Stepper Navigation */} +
+ {/* Background Line */} +
+ {/* Active Progress Line */} +
+ + {steps.map((step) => { + const isActive = step.id === currentStep; + const isCompleted = step.id < currentStep; + + return ( +
+
+ {isCompleted ? : } +
+
+ {step.title} +
+
+ ); + })} +
+ + {/* Mobile/Tablet Progress Bar */} +
+
+ Lépés {currentStep} / {totalSteps} + {steps[currentStep - 1].title} +
+
+
+
+
+
+ +
+ {errors.length > 0 && ( +
+ +
+

Kérjük, javítsa a következő hibákat:

+
    + {errors.map((err, idx) =>
  • {err}
  • )} +
+
+
+ )} + + {submitError && ( +
+ +
{submitError}
+
+ )} + + {/* Step 1: Kapcsolattartási adatok */} + {currentStep === 1 && ( +
+
+
+ +
+

Kapcsolattartási adatok

+
+ +
+
+ + +
+
+ + +
+
+ + +

A bejelentkezett e-mail címed.

+
+
+ + +
+
+ +
+ +
+ {['Landing Page', 'Pro Web', 'Enterprise'].map((pkg) => ( +
+
+ )} + + {currentStep === 2 && ( +
+
+
+ +
+

A vállalkozás rövid bemutatása

+
+ +
+ + +

💡 Tipp: Írjon le mindent, amit fontosnak tart a tevékenységével kapcsolatban.

+
+
+ )} + + {currentStep === 3 && ( +
+
+
+ +
+

Mi a weboldal fő célja?

+
+ +
+ {['Cég bemutatása', 'Termék / szolgáltatás értékesítés', 'Időpontfoglalás vagy ügyfélszerzés', 'Hírlevél / e-mail lista építése', 'Közösségépítés', 'Egyéb'].map((goal) => ( +
+ + {goal === 'Egyéb' && formData.goals.includes('Egyéb') && ( +
+ +
+ )} +
+ ))} +
+
+ + +
+
+ )} + + {currentStep === 4 && ( +
+
+
+ +
+

Tartalom és szerkezet

+
+ +
+ +
+ {['Rólunk', 'Szolgáltatások', 'Termékek', 'Referenciák / Vélemények', 'Blog / Hírek', 'Kapcsolat', 'Egyéb'].map((item) => ( +
+ + {item === 'Egyéb' && formData.content.includes('Egyéb') && ( + + )} +
+ ))} +
+
+ +
+ +
+ {['Igen', 'Nem', 'Részben'].map((opt) => ( + + ))} +
+ + {/* Content Upload / Link Section */} + {(formData.existingAssets === 'Igen' || formData.existingAssets === 'Részben') && ( +
+

+ Tartalom megosztása +

+

+ Kérjük, töltse fel a meglévő tartalmakat (képek, szövegek, logó) egy felhő tárhelyre (pl. Google Drive) és ossza meg a linket, vagy töltsön fel fájlt (max 1GB). +

+
+
+ + +
+
+ +
+ +

Kattintson a feltöltéshez vagy húzza ide a fájlt

+

(Max 1GB)

+ +
+
+
+
+ )} +
+
+ )} + + {currentStep === 5 && ( +
+
+
+ +
+

Design és stílus

+
+ +
+ setFormData(prev => ({ ...prev, primaryColor: c }))} /> + setFormData(prev => ({ ...prev, secondaryColor: c }))} /> + setFormData(prev => ({ ...prev, balanceColor: c }))} /> +
+ +
+ +
+ {styleOptions.map((style) => ( + + ))} +
+
+ +
+ + +
+
+ )} + + {currentStep === 6 && ( +
+
+
+ +
+

Funkciók és technikai igények

+
+ +
+ {['Kapcsolatfelvételi űrlap', 'Időpontfoglalás rendszer', 'Hírlevél-feliratkozás', 'Webshop / fizetési lehetőség', 'Blog / hírek', 'Többnyelvűség', 'Képgaléria / videógaléria', 'Google Térkép integráció', 'Chat gomb (Messenger / WhatsApp)'].map((feat) => ( + + ))} +
+
+ )} + + {currentStep === 7 && ( +
+
+
+ +
+

Inspirációk

+

Segítsen megérteni az ízlését!

+
+ +
+
+

Osszon meg velünk 3 weboldalt, ami tetszik, és írja le röviden, miért (pl. színek, elrendezés, hangulat).

+
+ +
+ {[0, 1, 2].map((i) => ( +
+
+ +
+ + handleInspirationChange(i, 'url', e.target.value)} + placeholder="https://pelda.hu" + className={`${inputClass} pl-9`} + /> +
+
+
+ + handleInspirationChange(i, 'comment', e.target.value)} + placeholder="Színek, elrendezés, animációk..." + className={inputClass} + /> +
+
+ ))} +
+
+ )} + + {currentStep === 8 && ( +
+
+
+ +
+

Extra szolgáltatások és Megjegyzések

+
+ +
+ +
+ {['SEO optimalizálás', 'Szövegírás', 'Domain ügyintézés', 'Tárhely ügyintézés'].map((extra) => ( + + ))} +
+ + {/* Dynamic Content */} +
+ {formData.extras.includes('Domain ügyintézés') && ( +
+ +
+ + +
+
+ )} + + {/* Domain Warning */} + {!formData.extras.includes('Domain ügyintézés') && ( +
+
+
+ Figyelem: Nem jelölte be a domain ügyintézést. Kérjük győződjön meg róla, hogy rendelkezik saját domain névvel, vagy a későbbiekben tudja biztosítani a DNS beállításokat. +
+
+ )} + + {!formData.extras.includes('Tárhely ügyintézés') && ( +
+
+
+ Figyelem: Mivel nem jelölte be a tárhely ügyintézést, a weboldalt átadjuk (forráskód), de az üzemeltetést és a szerver beállítását nem a MotionWeb végzi. +
+
+ )} +
+
+ +
+ + +
+
+ )} + + {/* Step 9: Összegzés */} + {currentStep === 9 && ( +
+
+
+ +
+

Rendelés összesítése

+

Ellenőrizze az adatokat a véglegesítés előtt.

+
+ +
+
+
+

Kapcsolattartó

+

{formData.name}

+

{formData.email}

+

{formData.phone}

+ {formData.company &&

{formData.company}

} +
+
+

Választott Csomag

+ {formData.package} +
+
+ +
+
+

Célok

+
+ {formData.goals.length > 0 ? formData.goals.map(g => {g}) : Nincs megadva} +
+
+
+

Színvilág

+
+ {formData.primaryColor &&
c.name === formData.primaryColor)?.value}} title="Főszín">
} + {formData.secondaryColor &&
c.name === formData.secondaryColor)?.value}} title="Mellékszín">
} + {formData.balanceColor &&
c.name === formData.balanceColor)?.value}} title="Kiegyensúlyozó">
} + {!formData.primaryColor && !formData.secondaryColor && !formData.balanceColor && Nincs színválasztás} +
+
+
+
+ +
+

Mi történik a rendelés leadása után?

+

+ A "Rendelés leadása" gombra kattintva átirányítjuk az előlegfizetési oldalra. A sikeres tranzakciót követően kollégáink 48 órán belül felveszik Önnel a kapcsolatot a megadott elérhetőségeken a projekt indításához. +

+
+ +
+
+
+ setPrivacyAccepted(e.target.checked)} + className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded cursor-pointer" + /> +
+
+ +
+
+ +
+
+ setAszfAccepted(e.target.checked)} + className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded cursor-pointer" + /> +
+
+ +
+
+
+
+ )} +
+ + {/* Footer Navigation */} +
+ + + {currentStep < totalSteps ? ( + + ) : ( + + )} +
+
+ ); +}; \ No newline at end of file diff --git a/components/ProcessSection.tsx b/components/ProcessSection.tsx new file mode 100644 index 0000000..d68303e --- /dev/null +++ b/components/ProcessSection.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import { + UserPlus, + FileText, + CreditCard, + Layout, + MessageSquare, + CheckCircle, + Globe, + ShieldCheck +} from 'lucide-react'; + +export const ProcessSection: React.FC = () => { + const steps = [ + { + id: 1, + title: 'Regisztráció & fiók létrehozása', + description: 'Első lépésként létrehozod a fiókodat, ahol később minden rendelést és értesítést kezelni tudsz.', + icon: UserPlus + }, + { + id: 2, + title: 'Kérdőív kitöltése', + description: 'Kitöltöd az irányított kérdőívet, amely alapján elkezdjük megtervezni a weboldalad.', + icon: FileText + }, + { + id: 3, + title: 'Előleg fizetése', + description: 'A kérdőív után kifizeted az előleget Stripe-on keresztül, ezzel indul a fejlesztési folyamat.', + icon: CreditCard + }, + { + id: 4, + title: 'Első verzió elkészítése', + description: 'Elkészítjük a bemutató weboldal első verzióját placeholder tartalmakkal.', + icon: Layout + }, + { + id: 5, + title: 'Visszajelzés megadása', + description: 'Egy gyors űrlapon jelzed, hogy tetszik-e az irány, vagy milyen módosításokat szeretnél.', + icon: MessageSquare + }, + { + id: 6, + title: 'Végleges rendelés & teljes fizetés', + description: 'A jóváhagyás után kifizeted a teljes árat, és elkészítjük a végleges, teljes funkcionalitású weboldalt.', + icon: CheckCircle + }, + { + id: 7, + title: 'Domain csatlakoztatása', + description: 'A weboldal rákerül a megadott domainedre, és élesben is elérhetővé válik.', + icon: Globe + }, + { + id: 8, + title: 'Fenntartás & támogatás', + description: 'A továbbiakban csak a fenntartási díjat kell fizetned, mi pedig gondoskodunk a stabil működésről.', + icon: ShieldCheck + } + ]; + + return ( +
+
+
+

Hogyan készül el a weboldalad?

+

+ Átlátható folyamat, gyors és professzionális kivitelezés – pontosan tudni fogod, mire számíthatsz. +

+
+ +
+ {steps.map((step) => ( +
+
+
+ +
+
+ {step.id} +
+
+

{step.title}

+

{step.description}

+
+ ))} +
+
+
+ ); +}; \ No newline at end of file diff --git a/components/ProfileCompleter.tsx b/components/ProfileCompleter.tsx new file mode 100644 index 0000000..18b8847 --- /dev/null +++ b/components/ProfileCompleter.tsx @@ -0,0 +1,250 @@ +import React, { useEffect, useState } from 'react'; +import { useAuth } from '../context/AuthContext'; +import { supabase, isSupabaseConfigured } from '../lib/supabaseClient'; +import { Button } from './Button'; +import { User, Save, AlertCircle, Calendar } from 'lucide-react'; + +export const ProfileCompleter: React.FC = () => { + const { user, refreshDemoUser } = useAuth(); + const [isOpen, setIsOpen] = useState(false); + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [dateOfBirth, setDateOfBirth] = useState(''); + const [loading, setLoading] = useState(false); + const [checking, setChecking] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (!user) { + setIsOpen(false); + return; + } + + const checkProfile = async () => { + setChecking(true); + + // --- DEMO MODE --- + if (!isSupabaseConfigured) { + const meta = user.user_metadata || {}; + if (!meta.first_name || !meta.last_name || !meta.date_of_birth) { + setIsOpen(true); + if (meta.first_name) setFirstName(meta.first_name); + if (meta.last_name) setLastName(meta.last_name); + if (meta.date_of_birth) setDateOfBirth(meta.date_of_birth); + } + setChecking(false); + return; + } + // ----------------- + + try { + const { data, error } = await supabase + .from('profiles') + .select('first_name, last_name, date_of_birth') + .eq('id', user.id) + .maybeSingle(); + + if (error) { + console.error('Error checking profile:', error.message || error); + // If DB check fails, fallback to metadata to see if we should block the user. + // This prevents locking the user out if the DB is temporarily unavailable or misconfigured, + // provided they have the data in their auth metadata. + const meta = user.user_metadata || {}; + const hasMetadata = meta.first_name && meta.last_name && meta.date_of_birth; + + if (!hasMetadata) { + setIsOpen(true); + if (meta.first_name) setFirstName(meta.first_name); + if (meta.last_name) setLastName(meta.last_name); + if (meta.date_of_birth) setDateOfBirth(meta.date_of_birth); + } + return; + } + + // If no profile exists, or names/dob are missing + if (!data || !data.first_name || !data.last_name || !data.date_of_birth) { + setIsOpen(true); + // Pre-fill if partial data exists + if (data?.first_name) setFirstName(data.first_name); + else if (user.user_metadata?.first_name) setFirstName(user.user_metadata.first_name); + + if (data?.last_name) setLastName(data.last_name); + else if (user.user_metadata?.last_name) setLastName(user.user_metadata.last_name); + + if (data?.date_of_birth) setDateOfBirth(data.date_of_birth); + else if (user.user_metadata?.date_of_birth) setDateOfBirth(user.user_metadata.date_of_birth); + } + } catch (err: any) { + console.error('Unexpected error in ProfileCompleter:', err); + } finally { + setChecking(false); + } + }; + + checkProfile(); + }, [user]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + setLoading(true); + + if (!firstName.trim() || !lastName.trim() || !dateOfBirth) { + setError('Minden mező kitöltése kötelező.'); + setLoading(false); + return; + } + + try { + // --- DEMO MODE UPDATE --- + if (!isSupabaseConfigured && user) { + await new Promise(resolve => setTimeout(resolve, 800)); // Fake delay + + // Update local storage session + const storedSession = localStorage.getItem('demo_user_session'); + if (storedSession) { + const parsed = JSON.parse(storedSession); + parsed.user.user_metadata = { + ...parsed.user.user_metadata, + first_name: firstName, + last_name: lastName, + date_of_birth: dateOfBirth + }; + localStorage.setItem('demo_user_session', JSON.stringify(parsed)); + refreshDemoUser(); // Refresh context + } + setIsOpen(false); + return; + } + // ------------------------ + + if (user) { + // 1. Update Profile Table + const { error: dbError } = await supabase + .from('profiles') + .upsert({ + id: user.id, + email: user.email, + first_name: firstName, + last_name: lastName, + date_of_birth: dateOfBirth, + updated_at: new Date().toISOString() + }); + + if (dbError) throw dbError; + + // 2. Update Auth Metadata (optional, but good for consistency) + await supabase.auth.updateUser({ + data: { + first_name: firstName, + last_name: lastName, + date_of_birth: dateOfBirth + } + }); + + setIsOpen(false); + // We do not reload here to avoid infinite loops if checks fail on reload. + // The state close is enough. + } + } catch (err: any) { + console.error('Error updating profile:', err); + setError('Hiba történt a mentés során: ' + (err.message || 'Ismeretlen hiba')); + } finally { + setLoading(false); + } + }; + + if (!isOpen) return null; + + return ( +
+
+
+
+ +
+

Hiányzó Adatok

+

+ Kérjük, a folytatáshoz adja meg a hiányzó adatait. +

+
+ +
+
+ {error && ( +
+ + {error} +
+ )} + +
+
+ + setLastName(e.target.value)} + className="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900" + placeholder="Kovács" + required + /> +
+ +
+ + setFirstName(e.target.value)} + className="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900" + placeholder="János" + required + /> +
+ +
+ +
+ setDateOfBirth(e.target.value)} + className="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900" + required + /> +
+ +
+
+
+
+ +
+ +
+ +

+ Ezekre az adatokra a számlázáshoz és a kapcsolattartáshoz van szükségünk. +

+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/components/ProtectedRoute.tsx b/components/ProtectedRoute.tsx new file mode 100644 index 0000000..4f4ce70 --- /dev/null +++ b/components/ProtectedRoute.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; + +export const ProtectedRoute: React.FC = ({ children }) => { + const { user, loading } = useAuth(); + + if (loading) { + return
Betöltés...
; + } + + if (!user) { + return ; + } + + return <>{children}; +}; \ No newline at end of file diff --git a/components/ScrollToTop.tsx b/components/ScrollToTop.tsx new file mode 100644 index 0000000..88d7589 --- /dev/null +++ b/components/ScrollToTop.tsx @@ -0,0 +1,22 @@ +import { useEffect } from "react"; +import { useLocation } from "react-router-dom"; + +export default function ScrollToTop() { + const { pathname, hash } = useLocation(); + + useEffect(() => { + if (hash) { + // Use setTimeout to allow DOM to render if navigating from another page + setTimeout(() => { + const element = document.getElementById(hash.replace('#', '')); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } + }, 100); + } else { + window.scrollTo(0, 0); + } + }, [pathname, hash]); + + return null; +} \ No newline at end of file diff --git a/components/ServiceCard.tsx b/components/ServiceCard.tsx new file mode 100644 index 0000000..7c78446 --- /dev/null +++ b/components/ServiceCard.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { LucideIcon } from 'lucide-react'; + +interface ServiceCardProps { + title: string; + description: string; + Icon: LucideIcon; +} + +export const ServiceCard: React.FC = ({ title, description, Icon }) => { + return ( +
+
+ +
+

{title}

+

{description}

+
+ ); +}; \ No newline at end of file diff --git a/components/SettingsModal.tsx b/components/SettingsModal.tsx new file mode 100644 index 0000000..be1f1c0 --- /dev/null +++ b/components/SettingsModal.tsx @@ -0,0 +1,188 @@ +import React, { useState, useEffect } from 'react'; +import { X, Save, Lock, User, CheckCircle, AlertCircle, Info } from 'lucide-react'; +import { Button } from './Button'; +import { supabase, isSupabaseConfigured } from '../lib/supabaseClient'; +import { useAuth } from '../context/AuthContext'; + +interface UserProfile { + id: string; + email: string; + first_name?: string; + last_name?: string; + date_of_birth: string; +} + +interface SettingsModalProps { + isOpen: boolean; + onClose: () => void; + userProfile: UserProfile | null; + onUpdate: () => void; +} + +export const SettingsModal: React.FC = ({ isOpen, onClose, userProfile, onUpdate }) => { + const { user, refreshDemoUser } = useAuth(); + const [activeTab, setActiveTab] = useState<'profile' | 'security'>('profile'); + + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [dateOfBirth, setDateOfBirth] = useState(''); + + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState<{type: 'success' | 'error', text: string} | null>(null); + + useEffect(() => { + if (isOpen && userProfile) { + setFirstName(userProfile.first_name || ''); + setLastName(userProfile.last_name || ''); + setDateOfBirth(userProfile.date_of_birth || ''); + setNewPassword(''); + setConfirmPassword(''); + setMessage(null); + setActiveTab('profile'); + } + }, [isOpen, userProfile]); + + const commonInputStyles = "w-full px-4 py-3 rounded-xl border border-gray-300 focus:ring-4 focus:ring-primary/10 focus:border-primary outline-none transition-all bg-white text-black font-medium shadow-sm"; + + const handleUpdateProfile = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setMessage(null); + if (!user) return; + + try { + if (!isSupabaseConfigured) { + await new Promise(resolve => setTimeout(resolve, 800)); + setMessage({ type: 'success', text: 'Profil frissítve (Demo Mód).' }); + setTimeout(() => onUpdate(), 1000); + return; + } + const { error: dbError } = await supabase.from('profiles').update({ first_name: firstName, last_name: lastName, date_of_birth: dateOfBirth, updated_at: new Date().toISOString() }).eq('id', user.id); + if (dbError) throw dbError; + await supabase.auth.updateUser({ data: { first_name: firstName, last_name: lastName, date_of_birth: dateOfBirth } }); + setMessage({ type: 'success', text: 'Adatok sikeresen mentve.' }); + setTimeout(() => onUpdate(), 1000); + } catch (err: any) { + setMessage({ type: 'error', text: 'Hiba: ' + err.message }); + } finally { + setLoading(false); + } + }; + + const handleChangePassword = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setMessage(null); + if (newPassword.length < 6) { setMessage({ type: 'error', text: 'Túl rövid jelszó.' }); setLoading(false); return; } + if (newPassword !== confirmPassword) { setMessage({ type: 'error', text: 'A jelszavak nem egyeznek.' }); setLoading(false); return; } + + try { + if (!isSupabaseConfigured) { + await new Promise(resolve => setTimeout(resolve, 800)); + setMessage({ type: 'success', text: 'Jelszó frissítve (Demo Mód).' }); + return; + } + const { error } = await supabase.auth.updateUser({ password: newPassword }); + if (error) throw error; + setMessage({ type: 'success', text: 'Jelszó sikeresen módosítva.' }); + setNewPassword(''); setConfirmPassword(''); + } catch (err: any) { + setMessage({ type: 'error', text: 'Hiba: ' + err.message }); + } finally { + setLoading(false); + } + }; + + if (!isOpen) return null; + + return ( +
+
+ +
+

Fiók Beállítások

+ +
+ +
+ + +
+ +
+ {message && ( +
+ {message.type === 'success' ? : } + {message.text} +
+ )} + + {activeTab === 'profile' && ( +
+
+
+ + setLastName(e.target.value)} className={commonInputStyles} /> +
+
+ + setFirstName(e.target.value)} className={commonInputStyles} /> +
+
+ +
+ + setDateOfBirth(e.target.value)} className={commonInputStyles} /> +
+ +
+ + +
+ +
+ +
+
+ )} + + {activeTab === 'security' && ( +
+
+ + setNewPassword(e.target.value)} placeholder="Minimum 6 karakter" className={commonInputStyles} /> +
+ +
+ + setConfirmPassword(e.target.value)} placeholder="Jelszó újra" className={commonInputStyles} /> +
+ +
+ +

Biztonsági okokból a jelszó megváltoztatása után javasolt az újra-bejelentkezés minden eszközön.

+
+ +
+ +
+
+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx new file mode 100644 index 0000000..067fb89 --- /dev/null +++ b/context/AuthContext.tsx @@ -0,0 +1,150 @@ + +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { supabase, isSupabaseConfigured } from '../lib/supabaseClient'; +import { Session, User } from '@supabase/supabase-js'; + +interface AuthContextType { + session: Session | null; + user: User | null; + loading: boolean; + isAdmin: boolean; + signOut: () => Promise; + refreshDemoUser: () => void; +} + +const AuthContext = createContext({ + session: null, + user: null, + loading: true, + isAdmin: false, + signOut: async () => {}, + refreshDemoUser: () => {}, +}); + +export const AuthProvider: React.FC = ({ children }) => { + const [session, setSession] = useState(null); + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [isAdmin, setIsAdmin] = useState(false); + + const loadDemoUser = () => { + try { + const stored = localStorage.getItem('demo_user_session'); + if (stored) { + const parsed = JSON.parse(stored); + setSession(parsed); + setUser(parsed.user); + setIsAdmin(parsed.user.email === 'motionstudiohq@gmail.com'); + } else { + setSession(null); + setUser(null); + setIsAdmin(false); + } + } catch (e) { + console.error('Error loading demo user', e); + } + }; + + const checkAdminStatus = async (currentUser: User | null) => { + if (!currentUser) { + setIsAdmin(false); + return; + } + + // 1. Hardcoded Super Admin Check (Ez mindig működik, RLS-től függetlenül) + if (currentUser.email === 'motionstudiohq@gmail.com') { + setIsAdmin(true); + return; + } + + // 2. Database Role Check (Hibatűrő módon) + if (isSupabaseConfigured) { + try { + const { data, error } = await supabase + .from('roles') + .select('role') + .eq('id', currentUser.id) + .maybeSingle(); + + if (error) { + console.warn('RLS Policy Error in AuthContext (recursion?):', error.message); + return; // Ha hiba van, hagyatkozunk az email alapú ellenőrzésre + } + + if (data?.role === 'admin') { + setIsAdmin(true); + return; + } + } catch (err) { + console.error('Error checking admin role:', err); + } + } + + setIsAdmin(false); + }; + + useEffect(() => { + const initAuth = async () => { + if (!isSupabaseConfigured) { + loadDemoUser(); + setLoading(false); + return; + } + + try { + const { data, error } = await supabase.auth.getSession(); + if (error) throw error; + + const currentSession = data.session; + setSession(currentSession); + setUser(currentSession?.user ?? null); + if (currentSession?.user) await checkAdminStatus(currentSession.user); + } catch (err) { + console.error('Auth initialization error:', err); + } finally { + setLoading(false); + } + + const { data: listener } = supabase.auth.onAuthStateChange(async (_event, session) => { + setSession(session); + setUser(session?.user ?? null); + if (session?.user) await checkAdminStatus(session.user); + setLoading(false); + }); + + return () => { + listener.subscription.unsubscribe(); + }; + }; + + initAuth(); + }, []); + + const signOut = async () => { + if (!isSupabaseConfigured) { + localStorage.removeItem('demo_user_session'); + setSession(null); + setUser(null); + setIsAdmin(false); + return; + } + try { + await supabase.auth.signOut(); + setIsAdmin(false); + } catch (error) { + console.error('Error signing out:', error); + } + }; + + const refreshDemoUser = () => { + if (!isSupabaseConfigured) loadDemoUser(); + }; + + return ( + + {!loading && children} + + ); +}; + +export const useAuth = () => useContext(AuthContext); diff --git a/db_fix.sql b/db_fix.sql new file mode 100644 index 0000000..624e0cd --- /dev/null +++ b/db_fix.sql @@ -0,0 +1,2 @@ + +zZ^rب$i٢Z,y˫z\ \ No newline at end of file diff --git a/db_schema.sql b/db_schema.sql new file mode 100644 index 0000000..a7ff1e4 Binary files /dev/null and b/db_schema.sql differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..9d38f93 --- /dev/null +++ b/index.html @@ -0,0 +1,77 @@ + + + + + + + Motion Web Stúdió - Professzionális Webfejlesztés + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..6ca5361 --- /dev/null +++ b/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +const rootElement = document.getElementById('root'); +if (!rootElement) { + throw new Error("Could not find root element to mount to"); +} + +const root = ReactDOM.createRoot(rootElement); +root.render( + + + +); \ No newline at end of file diff --git a/lib/defaultPlans.ts b/lib/defaultPlans.ts new file mode 100644 index 0000000..95a0d44 --- /dev/null +++ b/lib/defaultPlans.ts @@ -0,0 +1,54 @@ +import { ProductPackage } from '../types'; + +export const defaultPlans: ProductPackage[] = [ + { + id: 'start', + name: "Landing Page", + price: "190.000 Ft-tól", + desc: "Ideális induló vállalkozásoknak és személyes brandeknek.", + features: [ + "Egyoldalas Landing Page", + "Reszponzív (mobilbarát) design", + "Kapcsolati űrlap", + "Alapvető SEO beállítások", + "Social Media integráció", + "2 hét ingyenes támogatás" + ], + cta: "Megrendelem", + isPopular: false + }, + { + id: 'pro', + name: "Pro Web", + price: "350.000 Ft-tól", + desc: "A legnépszerűbb választás kis- és középvállalkozások számára.", + features: [ + "Max 5 aloldal (Rólunk, Szolgáltatások...)", + "Blog funkció", + "Google Térkép integráció", + "Bővített SEO optimalizálás", + "Gyorsított betöltés", + "Google Analytics bekötés", + "1 hónap ingyenes támogatás" + ], + isPopular: true, + cta: "Ezt választom" + }, + { + id: 'enterprise', + name: "Enterprise", + price: "Egyedi árazás", + desc: "Komplex rendszerek és webáruházak nagyvállalati igényekre.", + features: [ + "Korlátlan aloldalak", + "Webshop funkcionalitás", + "Bankkártyás fizetés", + "CRM/ERP rendszer integráció", + "Egyedi fejlesztésű funkciók", + "Prémium support", + "Karbantartási szerződés" + ], + cta: "Ajánlatot kérek", + isPopular: false + } +]; \ No newline at end of file diff --git a/lib/promptGenerator.ts b/lib/promptGenerator.ts new file mode 100644 index 0000000..639e793 --- /dev/null +++ b/lib/promptGenerator.ts @@ -0,0 +1,296 @@ +import { ProductPackage } from '../types'; + +export interface PromptResult { + title: string; + content: string; + tags: string[]; +} + +export const generateOrderPrompts = (order: any): PromptResult[] => { + const pkg = order.package || 'Pro Web'; + const details = order.details || {}; + + // Helper to safely get data + const get = (val: any, fallback = 'Not specified') => val || fallback; + const getArr = (val: any[]) => (val && val.length > 0 ? val.join(', ') : 'None selected'); + + // Common Context for all prompts + const commonContext = ` +PROJECT CONTEXT: +- Client Name: ${get(details.name)} +- Company: ${get(details.company)} +- Business Description: ${get(details.description)} +- Target Audience: ${get(details.targetAudience)} +- Primary Color: ${get(details.primaryColor)} +- Secondary Color: ${get(details.secondaryColor)} +- Desired Style: ${getArr(details.style)} +- Goals: ${getArr(details.goals)} +- Domain: ${get(details.domainName, 'TBD')} + +TECHNICAL STANDARDS: +- Language: Hungarian (Magyar) +- Framework: React with Next.js (App Router) +- Styling: Tailwind CSS +- Icons: Lucide React +- Code Style: Clean, component-based, exportable code. +- Backend: Supabase (if needed for features). +`; + + // 1. SIMPLE LANDING PAGE STRATEGY + if (pkg === 'Landing Page' || pkg === 'Start') { + return [{ + title: 'Landing Page Website Generator Prompt', + tags: ['Single Page', 'Conversion', 'UI/UX'], + content: ` +Act as a world-class Frontend Engineer and UI/UX Designer. +Your task is to create a high-conversion, single-page Landing Website in Hungarian. + +${commonContext} + +INSTRUCTIONS: +1. Create a single-file React component (or a clean component structure) for a Landing Page. +2. Structure: + - Hero Section: High impact, using the business description. + - About/Introduction: Based on the client's description. + - Services/Features: Highlight the features requested. + - Call to Action (CTA): Strong focus on the client's goals (e.g., ${getArr(details.goals)}). + - Contact Section: Include a functional looking form and placeholders for Phone/Email (${get(details.email)}). +3. Design: + - Use the primary color (${get(details.primaryColor)}) for main actions and accents. + - Use the requested style (${getArr(details.style)}) to determine font choices and spacing. +4. Content: + - Write all visible text in Hungarian. + - Use persuasive, marketing-oriented copy. + +OUTPUT: +Provide the full React code using Tailwind CSS. +` + }]; + } + + // 2. COMPLEX SITE STRATEGY (Pro Business / Enterprise) + const prompts: PromptResult[] = []; + + // Prompt 1: Main Structure + prompts.push({ + title: 'Phase 1: Core Structure & Homepage', + tags: ['Architecture', 'Layout', 'Homepage'], + content: ` +Act as a Senior Full-Stack Developer. +We are building a Multi-Page Website in Hungarian using Next.js App Router and Tailwind CSS. + +${commonContext} + +TASK: +Generate the core application structure and the Homepage. + +REQUIREMENTS: +1. **Layout & Navigation**: + - Create a responsive Navbar including links for requested pages: ${getArr(details.content)}. + - Create a professional Footer with company info and links. + - Define \`app/layout.tsx\` with font setup (Inter/Roboto). +2. **Homepage Design**: + - Create a modern, section-based homepage reflecting the brand identity. + - Hero Section: Engaging headline based on "${get(details.description)}". + - Key Value Proposition sections. + - Use the primary color (${get(details.primaryColor)}) effectively. +3. **Routing**: + - Setup the directory structure for Next.js App Router. + +OUTPUT: +- \`app/layout.tsx\` +- \`components/Navbar.tsx\` +- \`components/Footer.tsx\` +- \`app/page.tsx\` (Homepage content) +` + }); + + // Prompt 2: Subpages + if (details.content && details.content.length > 0) { + prompts.push({ + title: 'Phase 2: Standard Subpages', + tags: ['Content', 'Static Pages'], + content: ` +Act as a Frontend Specialist. +Continuing from the previous website structure, generate the specific subpages requested by the client. + +${commonContext} + +PAGES TO GENERATE: +${getArr(details.content)} + +INSTRUCTIONS: +1. **Content Generation**: For each page, generate realistic Hungarian content based on the business description: "${get(details.description)}". +2. **Design**: Ensure consistent styling with the Homepage (Tailwind CSS). +3. **Specifics**: + - If "Rólunk" (About): Tell the company story. + - If "Szolgáltatások" (Services): Grid layout of offerings. + - If "Kapcsolat" (Contact): Contact details and map placeholder. + +OUTPUT: +Provide the code for the page components (e.g., \`app/rolunk/page.tsx\`, \`app/szolgaltatasok/page.tsx\`). +` + }); + } + + // Prompts 3+: Individual Features (One prompt per feature) + if (details.features && details.features.length > 0) { + details.features.forEach((feature: string, index: number) => { + let featurePromptContent = ''; + let featureTitle = `Phase 3.${index + 1}: Feature - ${feature}`; + let featureTags = ['Feature', 'Logic']; + + switch (feature) { + case 'Kapcsolatfelvételi űrlap': + featurePromptContent = ` +TASK: Implement a fully functional **Contact Form**. +REQUIREMENTS: +1. Create a \`ContactForm\` component with Name, Email, Phone, Message fields. +2. Form Validation (Zod or simple HTML5). +3. Backend: Define a Supabase table \`messages\` to store submissions. +4. Logic: Create a server action or API route to handle the submission to Supabase. +`; + break; + + case 'Időpontfoglalás rendszer': + featurePromptContent = ` +TASK: Implement an **Appointment Booking System**. +REQUIREMENTS: +1. Database: Define a Supabase schema for \`appointments\` and \`available_slots\`. +2. UI: Create a calendar or slot picker component. +3. Logic: Allow users to select a time and book it. Handle availability checks. +`; + break; + + case 'Hírlevél-feliratkozás': + featurePromptContent = ` +TASK: Implement a **Newsletter Subscription** feature. +REQUIREMENTS: +1. UI: A clean input field and subscribe button (Footer or dedicated section). +2. Backend: Define a Supabase table \`subscribers\`. +3. Logic: Handle the submission and prevent duplicate emails. +`; + break; + + case 'Webshop / fizetési lehetőség': + featurePromptContent = ` +TASK: Implement core **E-commerce / Webshop** functionality. +REQUIREMENTS: +1. Database: Define tables for \`products\` and \`orders\`. +2. UI: Create a Product Listing component and a Product Detail page. +3. State: Implement a simple Cart context. +4. Checkout: Create a basic checkout form structure (mock payment integration is fine for now, or setup Stripe logic). +`; + featureTags.push('E-commerce'); + break; + + case 'Blog / hírek': + featurePromptContent = ` +TASK: Implement a **Blog / News** section. +REQUIREMENTS: +1. Database: Define a \`posts\` table (title, slug, content, image, date). +2. UI: Create a Blog Index page (grid of cards) and a Blog Post layout. +3. Logic: Fetch data from Supabase to render posts dynamically. +`; + featureTags.push('CMS'); + break; + + case 'Többnyelvűség': + featurePromptContent = ` +TASK: Implement **Multi-language Support (i18n)**. +REQUIREMENTS: +1. Setup a localization strategy (e.g., using URL path prefixes /en, /hu or a context provider). +2. Create a dictionary file for translations. +3. Add a language switcher component to the Navbar. +`; + break; + + case 'Képgaléria / videógaléria': + featurePromptContent = ` +TASK: Implement a **Media Gallery**. +REQUIREMENTS: +1. UI: Create a responsive masonry or grid layout for images/videos. +2. Functionality: Implement a Lightbox (modal) view when clicking an image. +3. Data: Define a data structure (array) for the gallery items. +`; + break; + + case 'Google Térkép integráció': + featurePromptContent = ` +TASK: Implement **Google Maps Integration**. +REQUIREMENTS: +1. Create a \`MapComponent\`. +2. Integrate an embed iframe or use a React Google Maps library. +3. Place a marker at the company's location. +`; + break; + + case 'Chat gomb (Messenger / WhatsApp)': + featurePromptContent = ` +TASK: Implement a **Floating Chat Widget**. +REQUIREMENTS: +1. UI: A fixed button in the bottom-right corner. +2. Interaction: On click, either open a modal or redirect to WhatsApp/Messenger API links. +3. Styling: Make it unobtrusive but visible. +`; + break; + + default: + featurePromptContent = ` +TASK: Implement the requested feature: **${feature}**. +REQUIREMENTS: +1. Analyze the requirement based on the project context. +2. Create necessary UI components. +3. Define backend logic or database schema if data persistence is needed. +`; + break; + } + + prompts.push({ + title: featureTitle, + tags: featureTags, + content: ` +Act as a Full-Stack Developer. +You are adding a specific feature to the existing Next.js + Supabase project. + +${commonContext} + +${featurePromptContent} + +OUTPUT: +- React Components +- Supabase SQL Definitions (if applicable) +- Integration instructions +` + }); + }); + } + + return prompts; +}; + +export const generateDownloadContent = (order: any, prompts: PromptResult[]): string => { + const header = ` +============================================================================== +MOTIONWEB - AI PROMPT PACKAGE +============================================================================== +Order ID: ${order.displayId} +Client: ${order.customer} +Date: ${new Date().toLocaleDateString()} +Package: ${order.package} +============================================================================== + +`; + + const body = prompts.map((p, i) => ` +------------------------------------------------------------------------------ +PROMPT ${i + 1}: ${p.title.toUpperCase()} +Tags: [${p.tags.join(', ')}] +------------------------------------------------------------------------------ + +${p.content.trim()} + +`).join('\n\n'); + + return header + body; +}; diff --git a/lib/supabaseClient.ts b/lib/supabaseClient.ts new file mode 100644 index 0000000..e93b85e --- /dev/null +++ b/lib/supabaseClient.ts @@ -0,0 +1,27 @@ +import { createClient } from '@supabase/supabase-js'; + +// Configuration +// You can switch these to import.meta.env.VITE_SUPABASE_URL if you use a .env file later. +const supabaseUrl = 'https://fhgxhqagvjnjamsyilmh.supabase.co'; +const supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZoZ3hocWFndmpuamFtc3lpbG1oIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ3NDg2OTMsImV4cCI6MjA4MDMyNDY5M30.t4RhJlqM5f_N7PK3Qx58KhvTl9zZB6KYxLGr_gc_3Ok'; + +// Validation to ensure keys are not empty or default placeholders +const isPlaceholder = + !supabaseUrl || + !supabaseAnonKey || + supabaseUrl.includes('placeholder') || + supabaseAnonKey.includes('placeholder'); + +if (isPlaceholder) { + console.warn( + "Missing or invalid Supabase environment variables. Auth features will run in DEMO MODE." + ); +} + +// Initialize the Supabase client +export const supabase = createClient( + supabaseUrl || 'https://placeholder.supabase.co', + supabaseAnonKey || 'placeholder-key' +); + +export const isSupabaseConfigured = !isPlaceholder; \ No newline at end of file diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..9b94a7d --- /dev/null +++ b/metadata.json @@ -0,0 +1,5 @@ +{ + "name": "Motion Web Stúdió", + "description": "Professzionális webfejlesztés, webshop készítés és digitális megoldások cégek számára.", + "requestFramePermissions": [] +} \ No newline at end of file diff --git a/motion-web-studio-0.1-login-order-status.zip b/motion-web-studio-0.1-login-order-status.zip new file mode 100644 index 0000000..4dea16d Binary files /dev/null and b/motion-web-studio-0.1-login-order-status.zip differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..d30e329 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "motion-web-stúdió", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-router-dom": "6.22.3", + "lucide-react": "0.344.0", + "@supabase/supabase-js": "2.39.7" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.0", + "typescript": "~5.8.2", + "vite": "^6.2.0" + } +} diff --git a/pages/About.tsx b/pages/About.tsx new file mode 100644 index 0000000..245394c --- /dev/null +++ b/pages/About.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { Target, Users, Zap } from 'lucide-react'; + +export const About: React.FC = () => { + return ( +
+ {/* Hero */} +
+
+ Office team +
+
+

+ Rólunk +

+

+ A Motion Web Stúdió (Balogh Bence Benedek) egy fiatalos, dinamikus csapat, akik elkötelezettek a minőségi webfejlesztés mellett. +

+
+
+ + {/* Content */} +
+
+
+
+

A küldetésünk

+

+ Hiszünk abban, hogy egy weboldal több, mint digitális névjegykártya. Egy jól felépített oldal értékesítő gépezet, márkaépítő eszköz és ügyfélszolgálati csatorna egyben. +

+

+ Célunk, hogy a magyar kis- és középvállalkozásokat olyan digitális eszközökkel lássuk el, amelyekkel versenyképesek maradhatnak a nemzetközi piacon is. Minden projektbe szívünket-lelkünket beletesszük. +

+
+
+ Working + Coding +
+
+ +
+
+
+ +
+

Eredményorientáltság

+

Nem csak szépet alkotunk, hanem olyat, ami működik. A konverzió és a ROI a legfontosabb mérőszámunk.

+
+
+
+ +
+

Ügyfélközpontúság

+

Folyamatos kommunikáció és átlátható folyamatok. Ön mindig tudja, hol tart a projektje.

+
+
+
+ +
+

Modern Technológia

+

React, TypeScript, Tailwind. A legmodernebb eszközöket használjuk a gyors és biztonságos működésért.

+
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Admin.tsx b/pages/Admin.tsx new file mode 100644 index 0000000..390c57c --- /dev/null +++ b/pages/Admin.tsx @@ -0,0 +1,588 @@ +import React, { useState, useEffect } from 'react'; +import { useAuth } from '../context/AuthContext'; +import { useNavigate } from 'react-router-dom'; +import { + ChevronLeft, RefreshCw, BarChart2, Users, ShoppingCart, Package, + FileText, Globe, Sparkles, MessageSquare, AlertCircle, CheckCircle, + XCircle, Clock, Activity, Save, Mail, Rocket, Edit3, Bell, + AlertTriangle, Archive, Send, Layout, History +} from 'lucide-react'; +import { Button } from '../components/Button'; +import { supabase, isSupabaseConfigured } from '../lib/supabaseClient'; +import { ProductPackage } from '../types'; +import { defaultPlans } from '../lib/defaultPlans'; +import { generateOrderPrompts, PromptResult } from '../lib/promptGenerator'; + +interface AdminUser { + id: string; + email: string; + first_name?: string; + last_name?: string; + created_at: string; + role: string; +} + +interface OrderHistoryEntry { + id: string; + status: string; + changed_at: string; +} + +interface EmailLogEntry { + id: string; + email_type: string; + sent_at: string; +} + +interface AdminOrder { + id: string; + user_id: string; + displayId: string; + customer: string; + email: string; + package: string; + status: string; + date: string; + amount: string; + details?: any; + history?: OrderHistoryEntry[]; + emailLogs?: EmailLogEntry[]; +} + +export const Admin: React.FC = () => { + const { user, isAdmin, loading } = useAuth(); + const navigate = useNavigate(); + const [activeTab, setActiveTab] = useState<'overview' | 'users' | 'orders' | 'plans'>('overview'); + const [users, setUsers] = useState([]); + const [orders, setOrders] = useState([]); + const [plans, setPlans] = useState([]); + const [loadingData, setLoadingData] = useState(false); + const [visitorStats, setVisitorStats] = useState({ month: 0, week: 0 }); + const [statusUpdating, setStatusUpdating] = useState(null); + const [planSaving, setPlanSaving] = useState(null); + const [errorMsg, setErrorMsg] = useState(null); + + const [viewOrder, setViewOrder] = useState(null); + const [generatedPrompts, setGeneratedPrompts] = useState([]); + const [copiedIndex, setCopiedIndex] = useState(null); + const [demoUrlInput, setDemoUrlInput] = useState(''); + const [savingDemoUrl, setSavingDemoUrl] = useState(false); + const [emailSending, setEmailSending] = useState(null); + + useEffect(() => { + if (!loading && !isAdmin) { + navigate('/dashboard'); + } + }, [isAdmin, loading, navigate]); + + useEffect(() => { + if (isAdmin) fetchAdminData(); + }, [isAdmin]); + + const fetchAdminData = async () => { + setLoadingData(true); + setErrorMsg(null); + + if (!isSupabaseConfigured) { + setPlans(defaultPlans); + setLoadingData(false); + return; + } + + try { + const { data: profiles, error: pErr } = await supabase.from('profiles').select('*'); + if (pErr) throw pErr; + + let rolesData: any[] = []; + try { + const { data: roles } = await supabase.from('roles').select('*'); + rolesData = roles || []; + } catch (e) { + console.warn("Could not fetch roles table."); + } + + setUsers(profiles.map(p => ({ + id: p.id, + email: p.email || 'N/A', + first_name: p.first_name, + last_name: p.last_name, + created_at: p.created_at, + role: rolesData.find(r => r.id === p.id)?.role || (p.email === 'motionstudiohq@gmail.com' ? 'admin' : 'user') + }))); + + const { data: oData, error: oErr } = await supabase.from('orders').select('*').order('created_at', { ascending: false }); + if (oErr) throw oErr; + setOrders(oData.map(o => ({ + ...o, + displayId: o.id.substring(0, 8).toUpperCase(), + customer: o.customer_name, + email: o.customer_email + }))); + + const { data: plData } = await supabase.from('plans').select('*').order('price', { ascending: true }); + setPlans(plData && plData.length > 0 ? plData : defaultPlans); + + const { count: mCount } = await supabase.from('page_visits').select('*', { count: 'exact', head: true }); + setVisitorStats({ month: mCount || 0, week: Math.floor((mCount || 0) / 4) }); + + } catch (err: any) { + setErrorMsg(err.message || "Hiba történt az adatok lekérésekor."); + } finally { + setLoadingData(false); + } + }; + + const fetchOrderHistory = async (orderId: string) => { + if (!isSupabaseConfigured) return []; + try { + const { data } = await supabase + .from('order_status_history') + .select('*') + .eq('order_id', orderId) + .order('changed_at', { ascending: false }); + return data || []; + } catch (e) { + return []; + } + }; + + const fetchEmailLogs = async (orderId: string) => { + if (!isSupabaseConfigured) return []; + try { + const { data } = await supabase + .from('email_log') + .select('*') + .eq('order_id', orderId) + .order('sent_at', { ascending: false }); + return data || []; + } catch (e) { + console.warn("Could not fetch email_log table."); + return []; + } + }; + + const handleStatusChange = async (orderId: string, newStatus: string) => { + if (statusUpdating) return; + setStatusUpdating(orderId); + + try { + if (isSupabaseConfigured) { + const { error } = await supabase.from('orders').update({ status: newStatus }).eq('id', orderId); + if (error) throw error; + + await supabase.from('order_status_history').insert({ order_id: orderId, status: newStatus }); + const newHist = await fetchOrderHistory(orderId); + + setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: newStatus } : o)); + if (viewOrder && viewOrder.id === orderId) { + setViewOrder({ ...viewOrder, status: newStatus, history: newHist }); + } + } else { + setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: newStatus } : o)); + if (viewOrder && viewOrder.id === orderId) { + setViewOrder({ ...viewOrder, status: newStatus }); + } + } + } catch (e: any) { + alert("Hiba: " + e.message); + } finally { + setStatusUpdating(null); + } + }; + + const handleUpdateDemoUrl = async () => { + if (!viewOrder || savingDemoUrl) return; + setSavingDemoUrl(true); + + const orderId = viewOrder.id; + const targetStatus = 'pending_feedback'; + const updatedDetails = { ...(viewOrder.details || {}), demoUrl: demoUrlInput }; + + try { + if (isSupabaseConfigured) { + const { error } = await supabase.from('orders').update({ + details: updatedDetails, + status: targetStatus + }).eq('id', orderId); + + if (error) throw error; + + await supabase.from('order_status_history').insert({ order_id: orderId, status: targetStatus }); + const newHist = await fetchOrderHistory(orderId); + + setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: targetStatus, details: updatedDetails } : o)); + setViewOrder({ ...viewOrder, status: targetStatus, details: updatedDetails, history: newHist }); + + alert(`Sikeres mentés és visszajelzés kérése!`); + } else { + setViewOrder({ ...viewOrder, details: updatedDetails }); + alert("Demo URL mentve."); + } + } catch (e: any) { + alert("Hiba: " + e.message); + } finally { + setSavingDemoUrl(false); + } + }; + + const handleViewDetails = async (order: AdminOrder) => { + setLoadingData(true); + const history = await fetchOrderHistory(order.id); + const emailLogs = await fetchEmailLogs(order.id); + setViewOrder({ ...order, history, emailLogs }); + setDemoUrlInput(order.details?.demoUrl || ''); + setGeneratedPrompts([]); + setLoadingData(false); + }; + + const handleSendEmail = async (emailType: string) => { + if (!viewOrder || emailSending) return; + setEmailSending(emailType); + + try { + console.log(`Email notification trigger: ${emailType}`); + + if (isSupabaseConfigured) { + const { error } = await supabase.from('email_log').insert({ + order_id: viewOrder.id, + email_type: emailType + }); + + if (error) throw error; + + const newLogs = await fetchEmailLogs(viewOrder.id); + setViewOrder({ ...viewOrder, emailLogs: newLogs }); + + alert(`E-mail ("${emailType}") naplózva és kiküldve (szimulált).`); + } else { + alert(`E-mail értesítő előkészítve: "${emailType}"\n\nJelenleg ez a funkció csak placeholder.`); + } + } catch (e: any) { + alert("Hiba az e-mail naplózásakor: " + e.message); + } finally { + setEmailSending(null); + } + }; + + const StatusBadge = ({ status }: { status: string }) => { + const configs: any = { + new: { label: 'Új', color: 'bg-blue-100 text-blue-800' }, + in_progress: { label: 'Folyamatban', color: 'bg-yellow-100 text-yellow-800' }, + pending_feedback: { label: 'Visszajelzés', color: 'bg-purple-100 text-purple-800' }, + completed: { label: 'Kész', color: 'bg-green-100 text-green-800' }, + cancelled: { label: 'Törölve', color: 'bg-gray-100 text-gray-800' } + }; + const c = configs[status] || configs.new; + return {c.label}; + }; + + if (loading) return
Admin felület...
; + if (!isAdmin) return null; + + return ( +
+
+
+
+
+ + Admin Access +
+

Vezérlőpult

+
+ +
+ +
+ {[ + { id: 'overview', label: 'Statisztika', icon: BarChart2 }, + { id: 'users', label: 'Felhasználók', icon: Users }, + { id: 'orders', label: 'Rendelések', icon: ShoppingCart }, + { id: 'plans', label: 'Csomagok', icon: Package } + ].map((tab) => ( + + ))} +
+ +
+ {activeTab === 'overview' && ( +
+
+

Látogatók

+

{visitorStats.month}

+
+
+

Rendelések

+

{orders.length}

+
+
+

Regisztrációk

+

{users.length}

+
+
+ )} + + {activeTab === 'users' && ( +
+ + + + + + + + + + + {users.map(u => ( + + + + + + + ))} + +
Felhasználó NeveE-mailSzintRegisztráció
+ {u.last_name || ''} {u.first_name || ''} + {(!u.last_name && !u.first_name) && Nincs megadva} + + {u.email} + + {u.role} + {new Date(u.created_at).toLocaleDateString('hu-HU')}
+
+ )} + + {activeTab === 'orders' && ( +
+ + + + + + + + + + + {orders.map(o => ( + + + + + + + ))} + +
ÜgyfélCsomagStátuszMűvelet
+

{o.customer}

+

{o.email}

+
{o.package} + +
+
+ )} + + {activeTab === 'plans' && ( +
+ {plans.map((p, i) => ( +
+ { const n = [...plans]; n[i].name = e.target.value; setPlans(n); }} className="text-xl font-black border-b border-gray-200 focus:border-primary outline-none w-full pb-1 text-black bg-white mb-6" /> + +
+ +
+ +
+ + )} +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx new file mode 100644 index 0000000..178c878 --- /dev/null +++ b/pages/Dashboard.tsx @@ -0,0 +1,313 @@ +import React, { useEffect, useState } from 'react'; +import { useAuth } from '../context/AuthContext'; +import { Button } from '../components/Button'; +import { LogOut, User, Settings as SettingsIcon, CreditCard, Layout, Cake, ShieldAlert, Clock, Activity, CheckCircle, XCircle, MessageSquare, ArrowRight, Edit2, Download, FileText, ExternalLink, History, RefreshCw } from 'lucide-react'; +import { Link, useNavigate } from 'react-router-dom'; +import { supabase, isSupabaseConfigured } from '../lib/supabaseClient'; +import { SettingsModal } from '../components/SettingsModal'; +import { FeedbackModal } from '../components/FeedbackModal'; +import { Invoice } from '../types'; + +interface UserProfile { + id: string; + email: string; + first_name?: string; + last_name?: string; + date_of_birth: string; +} + +interface OrderHistoryEntry { + id: string; + status: string; + changed_at: string; +} + +interface UserOrder { + id: string; + created_at: string; + package: string; + status: string; + amount: string; + displayId?: string; + details?: any; + history?: OrderHistoryEntry[]; +} + +export const Dashboard: React.FC = () => { + const { user, signOut, isAdmin } = useAuth(); + const navigate = useNavigate(); + const [profile, setProfile] = useState(null); + const [orders, setOrders] = useState([]); + const [invoices, setInvoices] = useState([]); + const [loadingOrders, setLoadingOrders] = useState(true); + const [showSettings, setShowSettings] = useState(false); + + const [feedbackModalOpen, setFeedbackModalOpen] = useState(false); + const [selectedOrderId, setSelectedOrderId] = useState(null); + const [feedbackLoading, setFeedbackLoading] = useState(false); + + const fetchOrderHistory = async (orderId: string) => { + if (!isSupabaseConfigured) return []; + try { + const { data } = await supabase + .from('order_status_history') + .select('*') + .eq('order_id', orderId) + .order('changed_at', { ascending: false }); + return data || []; + } catch (e) { + return []; + } + }; + + const fetchData = async () => { + if (!user) return; + setLoadingOrders(true); + + if (!isSupabaseConfigured) { + const meta = user.user_metadata || {}; + setProfile({ id: user.id, email: user.email || '', first_name: meta.first_name || '', last_name: meta.last_name || '', date_of_birth: meta.date_of_birth || '1990-01-01' }); + setOrders([{ id: 'demo-order-1', created_at: new Date().toISOString(), package: 'Pro Web', status: 'pending_feedback', amount: '350.000 Ft', displayId: 'DEMO-123', details: { demoUrl: 'https://example.com' }, history: [{ id: 'h1', status: 'new', changed_at: new Date().toISOString() }] }]); + setLoadingOrders(false); + return; + } + + try { + const { data: profileData } = await supabase.from('profiles').select('*').eq('id', user.id).maybeSingle(); + if (profileData) setProfile(profileData); + + const { data: orderData } = await supabase.from('orders').select('*').eq('user_id', user.id).order('created_at', { ascending: false }); + + if (orderData) { + const enrichedOrders = await Promise.all(orderData.map(async (o) => { + const history = await fetchOrderHistory(o.id); + return { + ...o, + displayId: o.id.substring(0, 8).toUpperCase(), + history: history + }; + })); + setOrders(enrichedOrders as UserOrder[]); + } + + const { data: invoiceData } = await supabase.from('invoices').select('*').eq('user_id', user.id).order('created_at', { ascending: false }); + if (invoiceData) setInvoices(invoiceData as Invoice[]); + + } catch (err) { + console.error("Unexpected error fetching data:", err); + } finally { + setLoadingOrders(false); + } + }; + + useEffect(() => { + fetchData(); + }, [user]); + + const handleLogout = async () => { + await signOut(); + navigate('/'); + }; + + const openFeedbackModal = (orderId: string) => { + setSelectedOrderId(orderId); + setFeedbackModalOpen(true); + }; + + const handleSubmitFeedback = async (feedbackData: any) => { + if (!selectedOrderId) return; + setFeedbackLoading(true); + + try { + if (!isSupabaseConfigured) { + await new Promise(r => setTimeout(r, 1000)); + setOrders(prev => prev.map(o => o.id === selectedOrderId ? { ...o, status: 'in_progress' } : o)); + setFeedbackModalOpen(false); + setFeedbackLoading(false); + return; + } + + const { data: currentOrder } = await supabase.from('orders').select('details').eq('id', selectedOrderId).single(); + const updatedDetails = { ...(currentOrder?.details || {}), latestFeedback: feedbackData, feedbackDate: new Date().toISOString() }; + + // Approved status also goes to in_progress now, admin will set to completed later + const { error: updateError } = await supabase.from('orders').update({ + status: 'in_progress', + details: updatedDetails + }).eq('id', selectedOrderId); + + if (updateError) throw updateError; + + // Log history + await supabase.from('order_status_history').insert({ order_id: selectedOrderId, status: 'in_progress' }); + + await fetchData(); + setFeedbackModalOpen(false); + } catch (err: any) { + alert("Hiba: " + err.message); + } finally { + setFeedbackLoading(false); + } + }; + + const getFullName = () => profile?.last_name && profile?.first_name ? `${profile.last_name} ${profile.first_name}` : user?.email?.split('@')[0] || 'Felhasználó'; + + const getStatusConfig = (status: string) => { + switch (status) { + case 'new': return { label: 'Beérkezett', color: 'bg-blue-100 text-blue-800', icon: Clock }; + case 'in_progress': + case 'progress': return { label: 'Fejlesztés', color: 'bg-yellow-100 text-yellow-800', icon: Activity }; + case 'pending_feedback': + case 'waiting_feedback': + case 'feedback': return { label: 'Visszajelzés', color: 'bg-purple-100 text-purple-800', icon: MessageSquare }; + case 'completed': return { label: 'Kész', color: 'bg-green-100 text-green-800', icon: CheckCircle }; + case 'cancelled': return { label: 'Törölve', color: 'bg-gray-100 text-gray-500', icon: XCircle }; + default: return { label: 'Beérkezett', color: 'bg-gray-100 text-gray-800', icon: Clock }; + } + }; + + return ( +
+ setShowSettings(false)} userProfile={profile} onUpdate={fetchData} /> + setFeedbackModalOpen(false)} onSubmit={handleSubmitFeedback} loading={feedbackLoading} /> + +
+
+
+
+

Fiók Áttekintése

+

Üdvözöljük, {getFullName()}!

+
+
+ +
+
+ +
+
+ {/* ORDERS COLUMN */} +
+
+

+ Aktív Projektjeim +

+
+ + {loadingOrders ? ( +
+ ) : orders.length > 0 ? ( +
+ {orders.map(order => { + const statusConfig = getStatusConfig(order.status); + const needsFeedback = ['pending_feedback', 'waiting_feedback', 'feedback'].includes(order.status); + + return ( +
+
+
+

{order.package}

+

ID: {order.displayId}

+
+
+ {needsFeedback && ( +
+ + Demó oldal megnyitása + + +
+ )} + {!needsFeedback && ( + + {statusConfig.label.toUpperCase()} + + )} +
+
+ + {/* PROJECT TIMELINE */} +
+
+ Projekt Történet +
+
+ {order.history && order.history.length > 0 ? ( + order.history.map((h, idx) => ( +
+
+
+ + {getStatusConfig(h.status).label} + + + {new Date(h.changed_at).toLocaleString('hu-HU')} + +
+ {idx === 0 && Aktuális} +
+ )) + ) : ( +

Nincs korábbi bejegyzés.

+ )} +
+
+
+ ); + })} +
+ ) : ( +
+

Még nincsenek leadott rendelései.

+ +
+ )} +
+ + {/* SETTINGS & INVOICES COLUMN */} +
+
+
+

Beállítások

+ +
+
+
+

E-mail

+

{user?.email}

+
+ +
+
+ +
+

Számlák

+ {invoices.length > 0 ? ( +
+ {invoices.map(inv => ( +
+
+ + {inv.invoice_number} +
+ +
+ ))} +
+ ) :

Nincs számlázott tétel.

} +
+
+
+ +
+ +
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/FAQ.tsx b/pages/FAQ.tsx new file mode 100644 index 0000000..1d97b63 --- /dev/null +++ b/pages/FAQ.tsx @@ -0,0 +1,84 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ArrowLeft, HelpCircle, ChevronDown, ChevronUp } from 'lucide-react'; +import { Button } from '../components/Button'; + +interface FaqItemProps { + question: string; + answer: string; +} + +const FaqItem: React.FC = ({ question, answer }) => { + const [isOpen, setIsOpen] = useState(false); + return ( +
+ +
+

{answer}

+
+
+ ); +}; + +export const FAQ: React.FC = () => { + const navigate = useNavigate(); + + const faqs = [ + { + question: "Mennyi idő alatt készül el egy weboldal?", + answer: "Ez a projekt összetettségétől függ. Egy egyszerűbb bemutatkozó oldal (Landing Page) általában 1-2 hét alatt elkészül, míg egy komplexebb webshop vagy egyedi fejlesztés 4-8 hetet is igénybe vehet. A pontos határidőt mindig az árajánlatban rögzítjük." + }, + { + question: "Kell saját domain névvel és tárhellyel rendelkeznem?", + answer: "Nem feltétlenül. Ha még nincs, szívesen segítünk a beszerzésében és a beállításokban. Ha már van, akkor azokat használjuk. A 'Domain ügyintézés' és 'Tárhely ügyintézés' opciókat a megrendelésnél is kiválaszthatja." + }, + { + question: "Vannak rejtett költségek vagy havidíjak?", + answer: "Nincsenek rejtett költségek. A fejlesztési díj egyszeri. Éves költségként a domain és tárhely díj merülhet fel (ha rajtunk keresztül intézi, akkor mi számlázzuk évente), illetve opcionálisan választható karbantartási csomagunk a folyamatos frissítésekért." + }, + { + question: "Kapok számlát a szolgáltatásról?", + answer: "Természetesen. Minden szolgáltatásunkról hivatalos számlát állítunk ki, amelyet e-mailben küldünk el Önnek. Mivel alanyi adómentes (AAM) vállalkozás vagyunk, a számlán nem szerepel felszámított ÁFA, így a feltüntetett árak a teljes fizetendő összeget jelentik." + }, + { + question: "Tudom majd egyedül szerkeszteni az oldalt?", + answer: "Igen! Olyan rendszereket építünk, amelyekhez kérés esetén adminisztrációs felületet biztosítunk (pl. blogbejegyzések írásához, termékek feltöltéséhez), és az átadáskor megmutatjuk a használatát." + }, + { + question: "Mi történik, ha elakadok vagy hibát találok?", + answer: "Elakadás vagy technikai probléma esetén örömmel segítünk, kérjük, jelezze ezt felénk e-mailben. Amennyiben a hiba a mi oldalunkon merül fel, azt természetesen javítjuk. Ha azonban a probléma tőlünk független, vagy utólagos módosítást (pl. design változtatást) szeretne, abban is szívesen állunk rendelkezésére, de ezek a fejlesztések már nem ingyenesek." + } + ]; + + return ( +
+
+ + +
+
+ +
+

Gyakori Kérdések

+

+ Összeszedtük a leggyakrabban felmerülő kérdéseket, hogy segítsünk a döntésben. +

+
+ +
+ {faqs.map((faq, index) => ( + + ))} +
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Home.tsx b/pages/Home.tsx new file mode 100644 index 0000000..cf1c4bd --- /dev/null +++ b/pages/Home.tsx @@ -0,0 +1,316 @@ +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { ArrowRight, Monitor, Search, ExternalLink, Check, Star, Smartphone, PenTool } from 'lucide-react'; +import { Button } from '../components/Button'; +import { ServiceCard } from '../components/ServiceCard'; +import { OrderForm } from '../components/OrderForm'; +import { ProcessSection } from '../components/ProcessSection'; +import { supabase } from '../lib/supabaseClient'; +import { ProductPackage } from '../types'; +import { defaultPlans } from '../lib/defaultPlans'; + +export const Home: React.FC = () => { + const [packages, setPackages] = useState(defaultPlans); + + useEffect(() => { + const fetchPlans = async () => { + try { + const { data, error } = await supabase + .from('plans') + .select('*') + .order('price', { ascending: true }); // Simple text ordering, ideally numerical but works for default prices somewhat + + if (!error && data && data.length > 0) { + setPackages(data); + } + } catch (e) { + console.error("Error fetching plans:", e); + } + }; + fetchPlans(); + }, []); + + return ( +
+ {/* Hero Section */} +
+ {/* Background Elements */} +
+
+ {/* Subtle texture overlay */} +
+ {/* Animated glow effects */} +
+
+
+ +
+
+ Innovatív Megoldások Neked +
+ +

+ Motion Web Stúdió +

+ +

+ Vigye vállalkozását a következő szintre +

+ +

+ A MotionWeb csapata prémium minőségű weboldalakat és webáruházakat fejleszt, amelyek nemcsak szépek, de vevőket is hoznak. +

+ +
+ + + + + + +
+
+
+ + {/* Services Section */} +
+
+
+

Szolgáltatásaink

+

+ Minden, ami a sikeres online jelenléthez kell +

+

+ Teljeskörű digitális kivitelezés a tervezéstől az üzemeltetésig. +

+
+ +
+ + + + +
+
+
+ + {/* References Section */} +
+
+
+

Referenciák

+

+ Tekintsd meg, milyen modern és reszponzív weboldalakat készítünk ügyfeleink számára. +

+
+ +
+ {/* Card 1: SweetCraving */} +
+
+ SweetCraving Desszertműhely +
+
+

SweetCraving Desszertműhely

+

Kézműves desszertek – Landing Page csomag

+

+ A SweetCraving számára egy fiatalos, barátságos hangulatú bemutatkozó oldalt hoztunk létre. +

+ +
+ Színek: +
+
+
+
+ +
+ + + +
+
+
+ + {/* Card 2: BlueWave */} +
+
+ BlueWave Solar Kft. +
+
+

BlueWave Solar Kft.

+

Zöld energia megoldások – Pro Web csomag

+

+ A BlueWave Solar számára modern, üzleti stílusú, reszponzív weboldalt készítettünk. +

+ +
+ Színek: +
+
+
+
+ +
+ + + +
+
+
+ + {/* Card 3: Steelguard */} +
+
+ Steelguard Biztonságtechnika +
+
+

Steelguard Biztonságtechnika

+

Biztonságtechnika és IT rendszerek – Enterprise csomag

+

+ A Steelguard számára technológiai, sötét tónusú weboldalt készítettünk. +

+ +
+ Színek: +
+
+
+
+ +
+ + + +
+
+
+
+
+
+ + {/* Products/Packages Section */} +
+
+
+

Csomagajánlataink

+

+ Átlátható árazás, rejtett költségek nélkül. Válassza az Ön céljaihoz leginkább illeszkedő csomagot. +

+
+ +
+ {packages.map((pkg, index) => ( +
+ {pkg.isPopular && ( +
+ Legnépszerűbb +
+ )} + +
+

{pkg.name}

+

{pkg.desc}

+
+ +
+ {pkg.price} + {pkg.price.includes('Ft') && + ÁFA} +
+ +
    + {pkg.features.map((feature, i) => ( +
  • + + {feature} +
  • + ))} +
+ +
+ + + +
+
+ ))} +
+
+
+ + {/* Process Section */} + + + {/* Order Form Section */} +
+
+ +
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Privacy.tsx b/pages/Privacy.tsx new file mode 100644 index 0000000..fbb5a9f --- /dev/null +++ b/pages/Privacy.tsx @@ -0,0 +1,342 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ArrowLeft, Shield, Mail, Globe, Database, Cookie, UserCheck, Scale, Lock } from 'lucide-react'; +import { Button } from '../components/Button'; + +export const Privacy: React.FC = () => { + const navigate = useNavigate(); + + return ( +
+
+ + +
+ {/* Header Section */} +
+
+
+ +
+
+

Adatkezelési Tájékoztató

+
+ + MotionWeb + + + Hatályos: 2025. január 1-től + +
+
+
+
+ + {/* Content Sections */} +
+
+ + {/* Section 1 */} +
+
+

+ 1 + Az adatkezelő adatai +

+
+
+
+

Adatkezelő neve

+

Balogh Bence Benedek egyéni vállalkozó

+
+
+

Projekt / márkanév

+

MotionWeb

+
+
+
+
+ +
+

Weboldal

+ https://motionweb.hu +
+
+
+ +
+

Kapcsolattartási e-mail

+ motionstudiohq@gmail.com +
+
+
+
+

+ A MotionWeb elnevezés projektnév, nem külön jogi személy. Az adatkezelést Balogh Bence Benedek egyéni vállalkozó végzi. +

+
+
+
+ + {/* Section 2 */} +
+

+ 2 + Az adatkezelés jogalapja +

+
+

Az adatkezelés a GDPR 6. cikk (1) bekezdése alapján történik, az alábbi jogalapokon:

+
    + {[ + 'szerződés teljesítése, valamint', + 'szerződés megkötését megelőző lépések,', + 'jogi kötelezettség teljesítése (számlázás),', + 'jogos érdek (a szolgáltatás működtetése és biztonsága),', + 'érintetti hozzájárulás (cookie-k alkalmazása).' + ].map((item, i) => ( +
  • + + {item} +
  • + ))} +
+

+ A MotionWeb szolgáltatásai fix csomagárakon érhetők el. A megrendelés leadásával és az előleg megfizetésével a felek között szerződés jön létre. +

+

A MotionWeb nem végez marketing célú adatkezelést.

+
+
+ + {/* Section 3 */} +
+

+ 3 + Kezelt személyes adatok köre +

+
+
+

+ Kapcsolatfelvétel és rendelés +

+
    + {['név', 'cégnév (amennyiben megadásra kerül)', 'e-mail cím', 'telefonszám', 'számlázási adatok', 'projektleírás, megjegyzések'].map((item, i) => ( +
  • +
    {item} +
  • + ))} +
+
+
+

+ Felhasználói fiók esetén +

+
    + {['e-mail cím', 'jelszó (titkosított formában)', 'fiók státusz', 'rendelésekhez kapcsolódó adatok', 'technikai aktivitási adatok'].map((item, i) => ( +
  • +
    {item} +
  • + ))} +
+
+
+

+ Fizetés +

+

+ A MotionWeb nem kezel bankkártya-adatokat. A fizetés a Stripe rendszerén keresztül történik, amelyhez az érintett átirányításra kerül. +

+
+
+
+ + {/* Section 4 */} +
+

+ 4 + Adattárolás és adatfeldolgozók +

+
+
+

Adattárolás helye

+
    +
  • Supabase – adatbázis és felhasználói adatok (EU)
  • +
  • VPS szerver – weboldalak kiszolgálása (EU)
  • +
+
+
+

E-mail küldés

+
    +
  • Resend – tranzakciós e-mailek
  • +
  • Supabase – fiókregisztráció és jelszó-visszaállítás
  • +
+
+
+

Fizetési szolgáltató

+
    +
  • Stripe Payments Europe Ltd.
  • +
+
+
+

+ Az adatfeldolgozók kiválasztása során az adatkezelő kizárólag a GDPR-nak megfelelő szolgáltatókat veszi igénybe. +

+
+ + {/* Section 5 */} +
+

+ 5 + Analitikai adatok +

+
+

A MotionWeb kizárólag alapvető statisztikai adatokat gyűjt Supabase segítségével, így különösen:

+
    + {['weboldal-látogatások száma', 'regisztrált felhasználók száma', 'leadott rendelések száma'].map((item, i) => ( +
  • + {item} +
  • + ))} +
+

Az analitikai adatok nem alkalmasak az érintettek azonosítására, és nem szolgálnak marketing vagy profilalkotási célokat.

+
+
+ + {/* Section 6 */} +
+

+ 6 + Cookie-k (sütik) +

+
+
+ +
+
+

A weboldal cookie-kat alkalmaz a megfelelő működés, a felhasználói élmény és a biztonság érdekében.

+

A cookie-k használatához az érintett hozzájárulása szükséges, amelyet a weboldalon megjelenő cookie banner biztosít.

+

Marketing célú cookie-k alkalmazására nem kerül sor.

+
+
+
+ + {/* Section 7 */} +
+

+ 7 + Adatmegőrzés időtartama +

+
+ {[ + { label: 'aktív ügyféladatok', val: 'a szolgáltatás fennállásának ideje alatt' }, + { label: 'szerződés megszűnését követően', val: '1 évig' }, + { label: 'számlázási adatok', val: 'jogszabályi kötelezettség szerint' }, + { label: 'felhasználói fiókadatok', val: 'határozatlan ideig, vagy az érintett törlési kérelméig' } + ].map((item, i) => ( +
+

{item.label}

+

{item.val}

+
+ ))} +
+
+ + {/* Section 8 */} +
+

+ 8 + Az érintettek jogai +

+
+

Az érintett jogosult:

+
+ {[ + 'tájékoztatást kérni személyes adatai kezeléséről', + 'hozzáférést kérni a kezelt adatokhoz', + 'adatainak helyesbítését kérni', + 'adatainak törlését kérni', + 'az adatkezelés korlátozását kérni', + 'tiltakozni az adatkezelés ellen', + 'adathordozhatóságot kérni' + ].map((item, i) => ( +
+
+ {item} +
+ ))} +
+

+ A kérelmek benyújthatók az alábbi e-mail címen:
+ motionstudiohq@gmail.com +

+
+
+ + {/* Section 9 */} +
+

+ 9 + Jogorvoslati lehetőségek +

+
+
+ +
+
+

Amennyiben az érintett megítélése szerint személyes adatainak kezelése jogsértő, jogosult panaszt tenni:

+

Nemzeti Adatvédelmi és Információszabadság Hatóság (NAIH)

+

Weboldal: https://www.naih.hu

+
+
+
+ + {/* Section 10 */} +
+

+ 10 + Adatbiztonság +

+
+
+ +
+

+ Az adatkezelő megfelelő technikai és szervezési intézkedéseket alkalmaz a személyes adatok biztonságának megőrzése érdekében, különös tekintettel a jogosulatlan hozzáférés, módosítás vagy törlés megelőzésére. +

+
+
+ + {/* Section 11 */} +
+

+ 11 + Záró rendelkezések +

+
+

Az adatkezelő fenntartja a jogot jelen adatkezelési tájékoztató módosítására.

+

A módosítás a weboldalon történő közzététellel lép hatályba.

+
+
+ +
+
+ + {/* Footer Info */} +
+

+ Utolsó frissítés: 2025. január +

+

+ MotionWeb © {new Date().getFullYear()} - Minden jog fenntartva. +

+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Products.tsx b/pages/Products.tsx new file mode 100644 index 0000000..e44cccc --- /dev/null +++ b/pages/Products.tsx @@ -0,0 +1,99 @@ +import React, { useState, useEffect } from 'react'; +import { Check, Star } from 'lucide-react'; +import { Button } from '../components/Button'; +import { Link } from 'react-router-dom'; +import { supabase } from '../lib/supabaseClient'; +import { ProductPackage } from '../types'; +import { defaultPlans } from '../lib/defaultPlans'; + +export const Products: React.FC = () => { + const [packages, setPackages] = useState(defaultPlans); + + useEffect(() => { + const fetchPlans = async () => { + try { + const { data, error } = await supabase + .from('plans') + .select('*') + .order('price', { ascending: true }); + + if (!error && data && data.length > 0) { + setPackages(data); + } + } catch (e) { + console.error("Error fetching plans:", e); + } + }; + fetchPlans(); + }, []); + + return ( +
+
+
+

Csomagajánlataink

+

+ Átlátható árazás, rejtett költségek nélkül. Válassza az Ön céljaihoz leginkább illeszkedő csomagot. +

+
+ +
+ {packages.map((pkg, index) => ( +
+ {pkg.isPopular && ( +
+ Legnépszerűbb +
+ )} + +
+

{pkg.name}

+

{pkg.desc}

+
+ +
+ {pkg.price} + {pkg.price.includes('Ft') && + ÁFA} +
+ +
    + {pkg.features.map((feature, i) => ( +
  • + + {feature} +
  • + ))} +
+ +
+ + + +
+
+ ))} +
+ +
+
+

Egyedi igényei vannak?

+

+ Nem találja a megfelelő csomagot? Készítünk Önnek egy teljesen személyre szabott ajánlatot. +

+
+ + + +
+
+
+ ); +}; diff --git a/pages/References.tsx b/pages/References.tsx new file mode 100644 index 0000000..9429387 --- /dev/null +++ b/pages/References.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { ExternalLink } from 'lucide-react'; +import { Link } from 'react-router-dom'; + +export const References: React.FC = () => { + // Demo projects that now have real pages + const projects = [ + { + id: 1, + title: "SweetCraving Desszertműhely", + category: "Landing Page", + image: "https://images.unsplash.com/photo-1563729784474-d77dbb933a9e?ixlib=rb-4.0.3&auto=format&fit=crop&w=1073&q=80", + description: "Fiatalos, kézműves desszertműhely bemutatkozó oldala.", + link: "/demos/sweetcraving" + }, + { + id: 2, + title: "BlueWave Solar Kft.", + category: "Pro Web + Aloldalak", + image: "https://images.unsplash.com/photo-1509391366360-2e959784a276?ixlib=rb-4.0.3&auto=format&fit=crop&w=1074&q=80", + description: "Megújuló energiaforrások vállalati weboldala kalkulátorral.", + link: "/demos/bluewave" + }, + { + id: 3, + title: "Steelguard Biztonságtechnika", + category: "Enterprise Rendszer", + image: "https://images.unsplash.com/photo-1518770660439-4636190af475?ixlib=rb-4.0.3&auto=format&fit=crop&w=1170&q=80", + description: "High-tech biztonságtechnikai portál és ügyfélkapu.", + link: "/demos/steelguard" + }, + { + id: 4, + title: "EcoHome Ingatlan", + category: "Ingatlan weboldal", + image: "https://images.unsplash.com/photo-1560518883-ce09059eeffa?ixlib=rb-4.0.3&auto=format&fit=crop&w=1073&q=80", + description: "Modern ingatlanértékesítő platform kereső funkcióval.", + link: null + }, + { + id: 5, + title: "Glamour Beauty", + category: "Webshop", + image: "https://images.unsplash.com/photo-1483985988355-763728e1935b?ixlib=rb-4.0.3&auto=format&fit=crop&w=1170&q=80", + description: "Kozmetikai webáruház bankkártyás fizetéssel.", + link: null + }, + { + id: 6, + title: "Law Firm", + category: "Bemutatkozó oldal", + image: "https://images.unsplash.com/photo-1589829085413-56de8ae18c73?ixlib=rb-4.0.3&auto=format&fit=crop&w=1212&q=80", + description: "Ügyvédi iroda letisztult, bizalomépítő oldala.", + link: null + } + ]; + + return ( +
+
+
+

Referenciáink

+

+ Büszkék vagyunk rá, hogy ügyfeleink álmait digitális valósággá formálhatjuk. Nézze meg korábbi munkáinkat! +

+
+ +
+ {projects.map((project) => ( +
+ {project.title} + +
+ {project.category} +

{project.title}

+

{project.description}

+ + {project.link ? ( + + Megtekintés + + ) : ( + Hamarosan elérhető + )} +
+
+ ))} +
+ +
+

+ "A képek illusztrációk. A valódi referenciamunkákat az ügyfél kérésére e-mailben küldött portfólió tartalmazza." +

+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/SUPABASE_SETUP.sql b/pages/SUPABASE_SETUP.sql new file mode 100644 index 0000000..928e689 --- /dev/null +++ b/pages/SUPABASE_SETUP.sql @@ -0,0 +1 @@ +nb$ |ڱ,Gb \ No newline at end of file diff --git a/pages/Services.tsx b/pages/Services.tsx new file mode 100644 index 0000000..f92f4a3 --- /dev/null +++ b/pages/Services.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { Monitor, Smartphone, Search, PenTool, Database, Server } from 'lucide-react'; +import { Button } from '../components/Button'; +import { Link } from 'react-router-dom'; + +export const Services: React.FC = () => { + const services = [ + { + icon: Monitor, + title: "Egyedi Webfejlesztés", + desc: "Nem sablonmegoldásokat kínálunk. Minden weboldalt a nulláról, az Ön igényeire szabva fejlesztünk React és modern technológiák használatával. Gyors, biztonságos és skálázható." + }, + { + icon: Smartphone, + title: "Reszponzív Design", + desc: "Mobile-first szemléletünk garantálja, hogy oldala minden eszközön – telefonon, tableten és asztali gépen – tökéletesen jelenjen meg és működjön." + }, + { + icon: Search, + title: "SEO Optimalizálás", + desc: "A Google találati lista élére segítjük. Technikai SEO, kulcsszókutatás és tartalomoptimalizálás, hogy a potenciális ügyfelek Önre találjanak." + }, + { + icon: PenTool, + title: "UI/UX Design", + desc: "Felhasználóbarát felületek tervezése. A látogatói élmény (UX) maximalizálása növeli a konverziót és a vásárlási hajlandóságot." + }, + { + icon: Database, + title: "Webshop Rendszerek", + desc: "Teljeskörű e-kereskedelmi megoldások. Termékkezelés, raktárkészlet, automatikus számlázás és bankkártyás fizetés integrációja." + }, + { + icon: Server, + title: "Hoszting & Domain", + desc: "Segítünk a megfelelő domain név kiválasztásában és a gyors, megbízható tárhely beállításában. Teljeskörű üzemeltetést is vállalunk." + } + ]; + + return ( +
+
+
+

Szolgáltatásaink

+

+ A Motion Web Stúdió a digitális megoldások széles spektrumát kínálja, hogy vállalkozása sikeres legyen az online térben. +

+
+
+ +
+
+ {services.map((service, index) => ( +
+
+ +
+

{service.title}

+

+ {service.desc} +

+
+ ))} +
+
+ + {/* Process Section */} +
+
+

A Munkafolyamatunk

+
+ {[ + { step: "01", title: "Konzultáció", desc: "Felmérjük igényeit és céljait." }, + { step: "02", title: "Tervezés", desc: "Drótvázak és design tervek készítése." }, + { step: "03", title: "Fejlesztés", desc: "Programozás és tartalomfeltöltés." }, + { step: "04", title: "Átadás", desc: "Tesztelés, élesítés és oktatás." } + ].map((item, i) => ( +
+
{item.step}
+
+

{item.title}

+

{item.desc}

+
+
+ ))} +
+
+ + + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Terms.tsx b/pages/Terms.tsx new file mode 100644 index 0000000..2f3351b --- /dev/null +++ b/pages/Terms.tsx @@ -0,0 +1,320 @@ + +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +// Add XCircle to the import list +import { ArrowLeft, FileText, Globe, Mail, Shield, XCircle } from 'lucide-react'; +import { Button } from '../components/Button'; + +export const Terms: React.FC = () => { + const navigate = useNavigate(); + + return ( +
+
+ + +
+ {/* Header Section */} +
+
+
+ +
+
+

Általános Szerződési Feltételek

+
+ + MotionWeb + + + Hatályos: 2025. január 1-től + +
+
+
+
+ + {/* Content Sections */} +
+
+ + {/* Section 1 */} +
+
+

+ 1 + A szolgáltató adatai +

+
+
+
+

Szolgáltató neve

+

Balogh Bence Benedek egyéni vállalkozó

+
+
+

Projekt / márkanév

+

MotionWeb

+
+
+
+
+ +
+

Weboldal

+ https://motionweb.hu +
+
+
+ +
+

Kapcsolattartási e-mail

+ motionstudiohq@gmail.com +
+
+
+
+

+ A MotionWeb elnevezés projektnév, nem önálló jogi személy. A szolgáltatásokat Balogh Bence Benedek egyéni vállalkozó nyújtja. +

+
+
+
+ + {/* Section 2 */} +
+

+ 2 + Az ÁSZF hatálya +

+
+

Jelen Általános Szerződési Feltételek (ÁSZF) a MotionWeb weboldalon keresztül megrendelt valamennyi szolgáltatásra vonatkoznak.

+

+ A megrendelő a rendelés leadásával és az előleg megfizetésével jelen ÁSZF rendelkezéseit elfogadja, és a felek között szerződés jön létre. +

+
+
+ + {/* Section 3 */} +
+

+ 3 + A szolgáltatás jellege +

+
+

A MotionWeb fix csomagárakon kínál weboldalkészítési szolgáltatásokat.

+

A szolgáltatás digitális szolgáltatásnak minősül, amely egy bemutató (demó) weboldal elkészítésével kezdődik, majd a megrendelő jóváhagyása esetén a végleges weboldal fejlesztésével folytatódik.

+

A MotionWeb nem nyújt egyedi ajánlattételt.

+
+
+ + {/* Section 4 */} +
+

+ 4 + Szerződés létrejötte +

+

A szerződés az alábbi lépések teljesülésével jön létre:

+
    + {['a megrendelő kiválasztja a szolgáltatási csomagot,', 'kitölti a rendelési űrlapot,', 'megfizeti az előleget.'].map((step, i) => ( +
  • + {i+1} + {step} +
  • + ))} +
+

A szerződés létrejöttével a MotionWeb megkezdi a demó weboldal elkészítését.

+
+ + {/* Section 5 */} +
+

+ 5 + Előleg +

+
+

Az előleg a szolgáltatás megkezdésének feltétele.

+

Az előleg nem visszatérítendő, mivel az a demó weboldal elkészítéséhez kapcsolódó munkavégzést és ráfordítást fedezi.

+

Amennyiben a megrendelő a demó weboldalt nem kívánja jóváhagyni, a fennmaradó díjat nem köteles megfizetni, azonban az előleg visszatérítésére ilyen esetben sem jogosult.

+
+
+ + {/* Section 6 */} +
+

+ 6 + Demó weboldal és visszajelzés +

+
+

A MotionWeb a demó weboldal elkészültéről a megrendelőt e-mailben értesíti.

+

A megrendelő a demó weboldalt a motionweb.hu oldalon, bejelentkezést követően, a „Rendeléseim” menüpont alatt tekintheti meg és adhat visszajelzést.

+

A demó weboldal visszajelzésére összesen 1 hónap áll rendelkezésre.

+

A MotionWeb a visszajelzés hiánya esetén:

+
+ {['az elkészülést követően,', '1 hét elteltével,', '2 hét elteltével'].map((time, i) => ( + {time} + ))} +
+

emlékeztető e-mailt küld.

+

+ Amennyiben a visszajelzési határidő eredménytelenül telik el, a MotionWeb a projektet kommunikáció hiányában lezártnak tekinti, és a projektfájlokat e-mailben megküldi. +

+
+
+ + {/* Section 7 */} +
+

+ 7 + Módosítási körök +

+
+

A csomagár legfeljebb két (2) módosítási kört tartalmaz.

+

Amennyiben a két módosítási kör után a demó weboldal nem kerül jóváhagyásra, a MotionWeb jogosult a projektet lezárni.

+
+
+ + {/* Section 8 */} +
+

+ 8 + Jóváhagyás és végfizetés +

+
+

A demó weboldal jóváhagyását követően a megrendelő köteles megfizetni a fennmaradó díjat.

+

A végfizetés beérkezését követően a MotionWeb megkezdi a weboldal végleges, funkciókkal ellátott verziójának fejlesztését és élesítését.

+

A végfizetés elmaradása esetén a végleges weboldal fejlesztése és élesítése nem történik meg.

+
+
+ + {/* Section 9 */} +
+

+ 9 + Határidők +

+
+

A MotionWeb nem vállal konkrét teljesítési határidőt.

+

A weboldalon vagy kommunikáció során feltüntetett időtartamok kizárólag tájékoztató jellegű becslések.

+

+ A megrendelő tudomásul veszi, hogy határidő túllépésből eredő igényt nem érvényesíthet. +

+
+
+ + {/* Section 10 */} +
+

+ 10 + Admin felület és utólagos módosítások +

+
+

Az admin felület kizárólag azokat a funkciókat tartalmazza, amelyek a megrendelés során előzetesen egyeztetésre kerültek.

+

A megrendelő kizárólag az admin felületen elérhető funkciók körében jogosult önálló módosításokra.

+

Egyszerű, technikailag nem összetett kérések (például szövegcsere) esetén a megrendelő e-mailben jelezheti igényét, amelynek teljesítésében a MotionWeb eseti alapon segítséget nyújthat.

+

Dizájnmódosítás, új funkció, új aloldal vagy admin bővítés külön megrendelésnek minősül.

+

A külön megrendelések egyedi árazás alapján, előzetes befizetést követően valósulnak meg.

+

A MotionWeb nem nyújt folyamatos technikai támogatási vagy karbantartási szolgáltatást.

+
+
+ + {/* Section 11 */} +
+

+ 11 + Tulajdonjog és referenciahasználat +

+
+

A weboldal forráskódja és tartalma a teljes díj megfizetéséig a MotionWeb tulajdonában marad.

+

A végfizetést követően a megrendelő jogosult a weboldalt használni.

+

A MotionWeb jogosult az elkészült weboldalt referenciaként, anonim módon, a megrendelő nevének feltüntetése nélkül bemutatni.

+
+
+ + {/* Section 12 */} +
+

+ 12 + Felelősségkorlátozás +

+
+

A MotionWeb nem vállal felelősséget:

+
    + {['elmaradt haszonért,', 'közvetett károkért,', 'üzleti veszteségekért.'].map((item, i) => ( +
  • + {item} +
  • + ))} +
+

A szolgáltatás nem garantál üzleti eredményt.

+
+
+ + {/* Section 13 */} +
+

+ 13 + Elállási jog kizárása +

+
+

A szolgáltatás digitális szolgáltatásnek minősül.

+

A megrendelő a rendelés leadásával és az előleg megfizetésével kifejezetten tudomásul veszi, hogy a teljesítés megkezdésével elállási joga megszűnik.

+
+
+ + {/* Section 14 */} +
+

+ 14 + Ügyfélkör +

+

A MotionWeb szolgáltatásait magánszemélyek és vállalkozások egyaránt igénybe vehetik.

+
+ + {/* Section 15 */} +
+

+ 15 + Jogvita és irányadó jog +

+
+

Jelen ÁSZF-re a magyar jog az irányadó.

+

Jogvita esetén a szolgáltató székhelye szerinti bíróság rendelkezik illetékességgel.

+
+
+ + {/* Section 16 */} +
+

+ 16 + Záró rendelkezések +

+
+

A MotionWeb fenntartja a jogot jelen ÁSZF módosítására.

+

A módosítás a weboldalon történő közzététellel lép hatályba.

+
+
+ +
+
+ + {/* Footer Info */} +
+
+ + Biztonságos Adatkezelés & Jogi Megfelelőség +
+

+ MotionWeb © {new Date().getFullYear()} - Minden jog fenntartva. +

+
+
+
+
+ ); +}; diff --git a/pages/auth/ForgotPassword.tsx b/pages/auth/ForgotPassword.tsx new file mode 100644 index 0000000..3b94a41 --- /dev/null +++ b/pages/auth/ForgotPassword.tsx @@ -0,0 +1,112 @@ +import React, { useState } from 'react'; +import { Link } from 'react-router-dom'; +import { supabase, isSupabaseConfigured } from '../../lib/supabaseClient'; +import { Button } from '../../components/Button'; +import { Mail, ArrowLeft, AlertCircle, CheckCircle, RefreshCw, Send } from 'lucide-react'; + +export const ForgotPassword: React.FC = () => { + const [email, setEmail] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(null); + setSuccess(false); + + try { + if (!isSupabaseConfigured) { + await new Promise(resolve => setTimeout(resolve, 1000)); + setSuccess(true); + return; + } + + const redirectUrl = `${window.location.origin}/#/auth/reset-password`; + + const { error } = await supabase.auth.resetPasswordForEmail(email, { + redirectTo: redirectUrl, + }); + + if (error) { + setError(error.message); + } else { + setSuccess(true); + } + } catch (err: any) { + setError('Váratlan hiba történt.'); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+

Jelszó visszaállítása

+

Küldünk egy linket a jelszava megváltoztatásához.

+
+ +
+ {success ? ( +
+
+ +
+

E-mail elküldve!

+

+ Ellenőrizze a(z) {email} postaládáját a visszaállításhoz szükséges linkért. +

+ + + +
+ ) : ( +
+ {error && ( +
+ +

{error}

+
+ )} + +
+ +
+ + setEmail(e.target.value)} + className="appearance-none block w-full pl-10 pr-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" + placeholder="pelda@email.hu" + /> +
+
+ +
+ +
+ +
+ + Vissza a bejelentkezéshez + +
+
+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/auth/Login.tsx b/pages/auth/Login.tsx new file mode 100644 index 0000000..6a37641 --- /dev/null +++ b/pages/auth/Login.tsx @@ -0,0 +1,171 @@ +import React, { useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { supabase, isSupabaseConfigured } from '../../lib/supabaseClient'; +import { Button } from '../../components/Button'; +import { useAuth } from '../../context/AuthContext'; +import { LogIn, AlertCircle, ArrowLeft, RefreshCw, Mail, ShieldCheck } from 'lucide-react'; + +export const Login: React.FC = () => { + const navigate = useNavigate(); + const { refreshDemoUser } = useAuth(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [needsConfirmation, setNeedsConfirmation] = useState(false); + const [resendLoading, setResendLoading] = useState(false); + const [resendSuccess, setResendSuccess] = useState(false); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(null); + setNeedsConfirmation(false); + + try { + if (!isSupabaseConfigured) { + await new Promise(resolve => setTimeout(resolve, 1000)); + const demoUser = { + user: { + id: 'demo-user-123', + email: email, + user_metadata: { date_of_birth: '1990-01-01' } + }, + access_token: 'demo-token', + }; + localStorage.setItem('demo_user_session', JSON.stringify(demoUser)); + refreshDemoUser(); + navigate('/dashboard'); + return; + } + + const { error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + + if (error) { + console.error('Login error:', error); + if (error.message.includes('Invalid login credentials')) { + setError('Helytelen e-mail cím vagy jelszó. Kérjük, győződjön meg róla, hogy megerősítette az e-mail címét!'); + setNeedsConfirmation(true); + } else if (error.message.includes('Email not confirmed')) { + setError('Az e-mail cím még nincs megerősítve.'); + setNeedsConfirmation(true); + } else { + setError('Hiba történt a bejelentkezés során: ' + error.message); + } + } else { + navigate('/dashboard'); + } + } catch (err: any) { + setError('Váratlan hiba történt.'); + } finally { + setLoading(false); + } + }; + + const handleResendConfirmation = async () => { + if (!email) { + setError('Kérjük, először adja meg az e-mail címét!'); + return; + } + setResendLoading(true); + setResendSuccess(false); + setError(null); + + try { + const redirectUrl = `${window.location.origin}/#/auth/login`; + const { error } = await supabase.auth.resend({ + type: 'signup', + email: email, + options: { emailRedirectTo: redirectUrl } + }); + + if (error) { + setError('Hiba az újraküldéskor: ' + error.message); + } else { + setResendSuccess(true); + } + } catch (err: any) { + setError('Hiba történt.'); + } finally { + setResendLoading(false); + } + }; + + return ( +
+
+
+

Bejelentkezés

+

Jelentkezzen be fiókjába a folytatáshoz.

+
+ +
+
+ {error && ( +
+
+
+

{error}

+
+ {needsConfirmation && ( +
+ +
+ )} +
+ )} + + {resendSuccess && ( +
+
+

A megerősítő e-mailt újraküldtük!

+
+ )} + +
+ +
+ setEmail(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm bg-white text-gray-900" /> +
+
+ +
+
+ + Elfelejtette? +
+
+ setPassword(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm bg-white text-gray-900" /> +
+
+ +
+ +
+
+ +
+
Nincs még fiókod?
+
+ Regisztrálj ingyenesen +
+
+
+ +
+ + Vissza a főoldalra + +
+
+
+ ); +} \ No newline at end of file diff --git a/pages/auth/Register.tsx b/pages/auth/Register.tsx new file mode 100644 index 0000000..fa8a315 --- /dev/null +++ b/pages/auth/Register.tsx @@ -0,0 +1,249 @@ +import React, { useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { supabase, isSupabaseConfigured } from '../../lib/supabaseClient'; +import { Button } from '../../components/Button'; +import { useAuth } from '../../context/AuthContext'; +import { UserPlus, AlertCircle, ArrowLeft, CheckCircle, Mail, Info, Send, ShieldCheck } from 'lucide-react'; + +export const Register: React.FC = () => { + const navigate = useNavigate(); + const { refreshDemoUser } = useAuth(); + const [lastName, setLastName] = useState(''); + const [firstName, setFirstName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [dateOfBirth, setDateOfBirth] = useState(''); + const [privacyAccepted, setPrivacyAccepted] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + const handleRegister = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(null); + + // Alapvető validációk + if (!lastName || !firstName) { + setError('A név megadása kötelező.'); + setLoading(false); + return; + } + + if (password !== confirmPassword) { + setError('A jelszavak nem egyeznek.'); + setLoading(false); + return; + } + + if (password.length < 6) { + setError('A jelszónak legalább 6 karakter hosszúnak kell lennie.'); + setLoading(false); + return; + } + + if (!dateOfBirth) { + setError('A születési dátum megadása kötelező.'); + setLoading(false); + return; + } + + if (!privacyAccepted) { + setError('A regisztrációhoz el kell fogadnia az Adatkezelési tájékoztatót.'); + setLoading(false); + return; + } + + try { + if (!isSupabaseConfigured) { + // Demo mód szimuláció + await new Promise(resolve => setTimeout(resolve, 1000)); + const demoUser = { + user: { + id: 'demo-user-123', + email: email, + user_metadata: { + first_name: firstName, + last_name: lastName, + date_of_birth: dateOfBirth + } + }, + access_token: 'demo-token', + }; + localStorage.setItem('demo_user_session', JSON.stringify(demoUser)); + refreshDemoUser(); + navigate('/dashboard'); + return; + } + + // HashRouter esetén a redirect URL-nek pontosnak kell lennie. + // A window.location.origin a base URL (pl. http://localhost:5173) + const redirectUrl = `${window.location.origin}/#/auth/login`; + + console.log('Attempting signup with redirect:', redirectUrl); + + const { data, error: signUpError } = await supabase.auth.signUp({ + email, + password, + options: { + emailRedirectTo: redirectUrl, + data: { + first_name: firstName, + last_name: lastName, + date_of_birth: dateOfBirth, + } + } + }); + + if (signUpError) { + console.error('Detailed registration error:', signUpError); + + // Specifikus hibaüzenetek kezelése + if (signUpError.message.includes('Error sending confirmation email')) { + setError('A rendszer nem tudta kiküldeni a megerősítő e-mailt a Resend szerverén keresztül. Kérjük, ellenőrizze, hogy a Supabase Dashboard-on a "Custom SMTP" beállításoknál a Sender Email megegyezik-e a Resend-ben hitelesített domainnel!'); + } else if (signUpError.message.includes('User already registered') || signUpError.status === 422) { + setError('Ezzel az e-mail címmel már regisztráltak. Kérjük, próbáljon meg bejelentkezni.'); + } else if (signUpError.status === 429) { + setError('Túl sok regisztrációs kísérlet. Kérjük, várjon néhány percet!'); + } else { + setError(`Hiba történt a regisztráció során: ${signUpError.message}`); + } + setLoading(false); + return; + } + + if (data.user) { + // Ha azonnal van session, akkor nincs e-mail megerősítés (ritka egyedi SMTP-nél) + if (data.session) { + navigate('/dashboard'); + } else { + setSuccess(true); + } + } + } catch (err: any) { + setError('Váratlan hiba történt a regisztráció során. Kérjük, próbálja meg később.'); + console.error('Unexpected catch error:', err); + } finally { + setLoading(false); + } + }; + + if (success) { + return ( +
+
+
+ +
+

E-mail kiküldve!

+

+ Küldtünk egy megerősítő linket a(z) {email} e-mail címre a Resend szolgáltatón keresztül. A belépéshez kérjük, kattintson a levélben található linkre. +

+ +
+
+
+

Nem találja a levelet?

+

+ Nézze meg a Spam vagy a Promóciók mappát is. Ha 5 percen belül nem érkezik meg, próbálja meg újra a bejelentkezési oldalon az újraküldést. +

+
+
+ +
+ + + +
+
+
+ ); + } + + return ( +
+
+
+

Regisztráció

+

+ Csatlakozzon a MotionWeb közösségéhez. +

+
+ +
+
+ {error && ( +
+
+
+
+ {error} +
+
+ )} + +
+
+ + setLastName(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" placeholder="Kovács" /> +
+
+ + setFirstName(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" placeholder="János" /> +
+
+ +
+ + setEmail(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" placeholder="pelda@email.hu" /> +
+ +
+ + setDateOfBirth(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" /> +
+ +
+
+ + setPassword(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" placeholder="••••••••" /> +
+
+ + setConfirmPassword(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" placeholder="••••••••" /> +
+
+ +
+ setPrivacyAccepted(e.target.checked)} className="h-5 w-5 text-primary focus:ring-primary border-gray-300 rounded-lg cursor-pointer mt-0.5" /> +
+ +
+
+ +
+ +
+
+ +
+ Már van fiókja? + Jelentkezzen be +
+
+ +
+ + Vissza a főoldalra + +
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/auth/ResetPassword.tsx b/pages/auth/ResetPassword.tsx new file mode 100644 index 0000000..d0b953c --- /dev/null +++ b/pages/auth/ResetPassword.tsx @@ -0,0 +1,128 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { supabase, isSupabaseConfigured } from '../../lib/supabaseClient'; +import { Button } from '../../components/Button'; +import { Lock, AlertCircle, CheckCircle, RefreshCw, Save } from 'lucide-react'; + +export const ResetPassword: React.FC = () => { + const navigate = useNavigate(); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + useEffect(() => { + // Supabase sets the session automatically when clicking the recovery link + const checkSession = async () => { + if (isSupabaseConfigured) { + const { data } = await supabase.auth.getSession(); + if (!data.session) { + setError("A link lejárt vagy érvénytelen. Kérjük, igényeljen újat!"); + } + } + }; + checkSession(); + }, []); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(null); + + if (password.length < 6) { + setError('A jelszónak legalább 6 karakternek kell lennie.'); + setLoading(false); + return; + } + + if (password !== confirmPassword) { + setError('A jelszavak nem egyeznek.'); + setLoading(false); + return; + } + + try { + if (!isSupabaseConfigured) { + await new Promise(resolve => setTimeout(resolve, 1000)); + setSuccess(true); + return; + } + + const { error } = await supabase.auth.updateUser({ + password: password + }); + + if (error) { + setError(error.message); + } else { + setSuccess(true); + } + } catch (err: any) { + setError('Váratlan hiba történt.'); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+

Új jelszó megadása

+

Kérjük, adja meg az új biztonságos jelszavát.

+
+ +
+ {success ? ( +
+
+ +
+

Jelszó sikeresen módosítva!

+

Most már bejelentkezhet az új jelszavával.

+ +
+ ) : ( +
+ {error && ( +
+ +

{error}

+
+ )} + +
+
+ +
+ + setPassword(e.target.value)} className="appearance-none block w-full pl-10 pr-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" /> +
+
+ +
+ +
+ + setConfirmPassword(e.target.value)} className="appearance-none block w-full pl-10 pr-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" /> +
+
+
+ +
+ +
+
+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/demos/BlueWave.tsx b/pages/demos/BlueWave.tsx new file mode 100644 index 0000000..82e40e7 --- /dev/null +++ b/pages/demos/BlueWave.tsx @@ -0,0 +1,474 @@ +import React, { useState, useEffect } from 'react'; +import { ArrowLeft, Sun, Battery, Zap, ChevronRight, Menu, X, Phone, Mail, CheckCircle, Home, Info, Briefcase, Calculator, ArrowRight } from 'lucide-react'; +import { Link } from 'react-router-dom'; + +export const BlueWave: React.FC = () => { + const [activePage, setActivePage] = useState<'home' | 'services' | 'about' | 'contact'>('home'); + const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + const [scrolled, setScrolled] = useState(false); + + // Smooth scroll to top when "changing pages" in this SPA simulation + useEffect(() => { + window.scrollTo({ top: 0, behavior: 'smooth' }); + }, [activePage]); + + // Navbar scroll effect + useEffect(() => { + const handleScroll = () => setScrolled(window.scrollY > 20); + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + const navItems = [ + { id: 'home', label: 'Főoldal', icon: Home }, + { id: 'services', label: 'Megoldások', icon: Zap }, + { id: 'about', label: 'Cégünkről', icon: Info }, + { id: 'contact', label: 'Kapcsolat', icon: Phone }, + ]; + + return ( +
+ {/* Return to Main Site */} +
+ + + +
+ + {/* Top Bar */} +
+
+
+ +36 30 999 8888 + info@bluewave-solar.demo +
+ +
+
+ + {/* Navbar */} + + + {/* Main Content Area - Simulating Routing with smooth fade */} +
+
+ + {/* HOME PAGE */} + {activePage === 'home' && ( + <> +
+
+ Solar Panels +
+
+
+
+
+ Hivatalos állami kivitelező partner +
+

+ Váltson tiszta energiára
+ a jövőjéért +

+

+ Csökkentse rezsijét akár 100%-kal prémium napelem rendszereinkkel. Teljeskörű ügyintézés a tervezéstől a kulcsrakész átadásig, 25 év garanciával. +

+
+ + +
+ +
+
+
1000+ elégedett ügyfél +
+
+
Országos lefedettség +
+
+
+
+
+ +
+
+
+ {[ + { icon: Sun, color: 'text-[#0284c7]', bg: 'bg-sky-50', title: 'Prémium Panelek', desc: 'Tier-1 kategóriás napelemek 25 év teljesítménygaranciával és kiemelkedő hatásfokkal.' }, + { icon: Battery, color: 'text-emerald-600', bg: 'bg-emerald-50', title: 'Energiatárolás', desc: 'Hibrid rendszerek és akkumulátoros megoldások a teljes függetlenségért.' }, + { icon: Briefcase, color: 'text-orange-600', bg: 'bg-orange-50', title: 'Kulcsrakész', desc: 'Minden papírmunkát, pályázatot és engedélyeztetést elintézünk Ön helyett.' } + ].map((item, i) => ( +
+
+ +
+

{item.title}

+

{item.desc}

+
+ ))} +
+
+ +
+
+

Miért válassza a BlueWave-et?

+

Szakértelem, minőség és megbízhatóság egy helyen.

+
+ +
+ Engineer +
+ {[ + { title: 'Szakértő Mérnökcsapat', text: 'Saját, magasan képzett mérnökeink tervezik meg rendszerét a maximális hatékonyság érdekében.' }, + { title: 'Gyors Kivitelezés', text: 'A szerződéskötéstől számított 30 napon belül telepítjük rendszerét.' }, + { title: 'Folyamatos Támogatás', text: 'Telepítés után sem engedjük el a kezét, távfelügyeletet és karbantartást biztosítunk.' } + ].map((feat, i) => ( +
+
+ {i + 1} +
+
+

{feat.title}

+

{feat.text}

+
+
+ ))} + +
+
+
+
+ + )} + + {/* SERVICES PAGE */} + {activePage === 'services' && ( +
+
+
+ Megoldásaink +

Minden igényre van válaszunk

+

Legyen szó kis családi házról vagy hatalmas ipari létesítményről, mi megtaláljuk az optimális rendszert.

+
+ +
+ {/* Residential */} +
+
+
+ Residential +
+ Lakossági +

Családi Házak

+
+
+
+

+ Csökkentse otthona energiafüggőségét és növelje ingatlana értékét. Rendszereink diszkrétek, esztétikusak és okosotthon-kompatibilisek. +

+
+ {['Rezsi nullázása', 'Hibrid Inverterek', 'Okos monitorozás', 'Elektromos autó töltés'].map((item, i) => ( +
+ {item} +
+ ))} +
+ +
+
+ + {/* Industrial */} +
+
+
+ Commercial +
+ Ipari +

Vállalatok & Üzemek

+
+
+
+

+ Optimalizálja vállalkozása működési költségeit. Nagy teljesítményű rendszerek, amelyek fedezik a gépek és üzemcsarnokok energiaigényét. +

+
+ {['Gyors megtérülés', '50kW - 5MW teljesítmény', 'ESG megfelelőség', 'Saját transzformátor'].map((item, i) => ( +
+ {item} +
+ ))} +
+ +
+
+
+
+
+ )} + + {/* ABOUT PAGE */} + {activePage === 'about' && ( +
+
+
+ +
+

A BlueWave Küldetése

+

+ A BlueWave Solar Kft. 2015-ben azzal a céllal alakult, hogy elérhetővé tegye a napenergiát mindenki számára. Hiszünk abban, hogy a fenntarthatóság nem luxus, hanem a jövő záloga. Csapatunk okleveles mérnökökből, pályázati szakértőkből és tapasztalt kivitelezőkből áll. +

+ +
+
+
+
500+
+
Telepített rendszer
+
+
+
15MW
+
Össz teljesítmény
+
+
+
100%
+
Ügyfél elégedettség
+
+
+ {/* Background decorations */} +
+
+ +
+
+

Minőség

+

Kizárólag minősített, prémium gyártók termékeit használjuk, hogy rendszere évtizedekig működjön.

+
+
+

Innováció

+

Folyamatos követjük a technológia fejlődését, hogy a legmodernebb megoldásokat kínálhassuk.

+
+
+

Garancia

+

A kivitelezésre és a termékekre is kiemelkedő garanciális feltételeket biztosítunk.

+
+
+
+
+ )} + + {/* CONTACT PAGE */} + {activePage === 'contact' && ( +
+
+
+ {/* Left Panel */} +
+
+

Lépjen velünk kapcsolatba

+

+ Töltse ki az űrlapot egy ingyenes, kötelezettségmentes előzetes kalkulációért. Mérnök kollégánk 24 órán belül keresni fogja. +

+
+
+ + +36 30 999 8888 +
+
+ + info@bluewave-solar.demo +
+
+ + 1117 Budapest, Napelem u. 1. +
+
+
+
+

+ Nyitvatartás: H-P 8:00 - 17:00 +

+
+ {/* Circle decorations */} +
+
+
+ + {/* Right Panel - Form */} +
+

Ingyenes Ajánlatkérés

+
+
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+ Gyors Kalkulátor Adatok +
+
+
+ + +
+
+ + +
+
+
+ +
+ +

+ Az ajánlatkérés nem jár kötelezettséggel. Adatvédelmi irányelveinket elfogadom. +

+
+
+
+
+
+
+ )} + +
+
+ + {/* Footer */} +
+
+
+
+
+
+ +
+ BlueWaveSolar +
+

+ Magyarország vezető napenergia szolgáltatója. Célunk, hogy ügyfeleink számára energiafüggetlenséget és fenntartható jövőt biztosítsunk. +

+
+
+

Navigáció

+
    +
  • setActivePage('home')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Főoldal
  • +
  • setActivePage('services')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Megoldások
  • +
  • setActivePage('about')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Rólunk
  • +
  • setActivePage('contact')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Kapcsolat
  • +
+
+
+

Kapcsolat

+
+

1117 Budapest, Napelem u. 1.

+

+36 30 999 8888

+

info@bluewave-solar.demo

+
+
+
+
+

© 2024 BlueWave Solar Kft. Minden jog fenntartva.

+

Demonstrációs weboldal - MotionWeb Pro csomag

+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/demos/Steelguard.tsx b/pages/demos/Steelguard.tsx new file mode 100644 index 0000000..829160a --- /dev/null +++ b/pages/demos/Steelguard.tsx @@ -0,0 +1,428 @@ +import React, { useEffect, useState } from 'react'; +import { ArrowLeft, Shield, Lock, Eye, Server, Cpu, Globe, ChevronDown, PlayCircle, Fingerprint, Scan, Activity, Radio, Terminal, Box, Building2, Users, MapPin } from 'lucide-react'; +import { Link } from 'react-router-dom'; + +export const Steelguard: React.FC = () => { + const [scrolled, setScrolled] = useState(false); + const [activeTab, setActiveTab] = useState<'home' | 'solutions' | 'hardware' | 'sectors' | 'company'>('home'); + + useEffect(() => { + const handleScroll = () => setScrolled(window.scrollY > 20); + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + // Reset scroll when changing tabs + useEffect(() => { + window.scrollTo({ top: 0, behavior: 'smooth' }); + }, [activeTab]); + + return ( +
+ + {/* INJECTED STYLES FOR ADVANCED ANIMATIONS */} + + + {/* Return to Main Site */} +
+ + + +
+ + {/* Enterprise Navbar */} + + + {/* Main Content Router Simulation */} +
+ + {/* HOME VIEW */} + {activeTab === 'home' && ( + <> +
+
+
+
+
+
+
+
+
+ +
+
+
+ + ESTABLISHING SECURE CONNECTION... +
+

+ TOTAL
+ DEFENSE +

+

+ Advanced AI-driven security architecture for enterprise infrastructure. Zero-trust protocols and predictive threat neutralization active. +

+
+ + +
+
+ +
+
+
+
+
+ Tech Visual +
+
+
+
+ +
+

Status

+

OPTIMAL_FLOW

+
+
+
+
+
+
+ +
+
+
+ {[ + { icon: Eye, title: 'Neural Surveillance', desc: 'Real-time object recognition and behavioral prediction algorithms.' }, + { icon: Scan, title: 'Retinal Access', desc: '99.999% accuracy biometric entry points with liveness detection.' }, + { icon: Server, title: 'Core Defense', desc: 'Hardened physical and logical shielding for data centers.' }, + { icon: Globe, title: 'Global Ops', desc: '24/7 localized threat monitoring from orbit-synced satellites.' }, + { icon: Cpu, title: 'IoT Mesh', desc: 'Thousands of interconnected sensors forming an unbreakable web.' }, + { icon: Shield, title: 'Hybrid Firewall', desc: 'Simultaneous protection against kinetic and digital intrusion.' }, + ].map((item, i) => ( +
+
+
+ +
+

{item.title}

+

{item.desc}

+
+
+ ))} +
+
+
+ + )} + + {/* SOLUTIONS VIEW */} + {activeTab === 'solutions' && ( +
+
+
+
+

Strategic Solutions

+
+ +
+
+

+ We offer a multi-layered security stack designed to neutralize threats before they reach your perimeter. +

+
+ {[ + { title: "Zero Trust Architecture", desc: "Never trust, always verify. Every node is isolated and encrypted." }, + { title: "Predictive AI Shield", desc: "Our neural networks predict intrusion patterns with 99.8% accuracy." }, + { title: "Cloud Integration", desc: "Seamless security across AWS, Azure, and private data centers." } + ].map((sol, i) => ( +
+

{sol.title}

+

{sol.desc}

+
+ ))} +
+
+
+
+ Solution +
+
+
+
+ )} + + {/* HARDWARE VIEW */} + {activeTab === 'hardware' && ( +
+
+
+
+

Hardened Hardware

+
+ +
+ {[ + { icon: Box, name: "Node V4 Unit", img: "https://images.unsplash.com/photo-1518770660439-4636190af475?auto=format&fit=crop&w=800&q=80" }, + { icon: Cpu, name: "Neural Processor", img: "https://images.unsplash.com/photo-1591799264318-7e6ef8ddb7ea?auto=format&fit=crop&w=800&q=80" }, + { icon: Shield, name: "Quantum Enclosure", img: "https://images.unsplash.com/photo-1544197150-b99a580bb7a8?auto=format&fit=crop&w=800&q=80" } + ].map((hw, i) => ( +
+ {hw.name} +
+
+ + {hw.name} +
+ +
+
+ ))} +
+
+
+ )} + + {/* SECTORS VIEW */} + {activeTab === 'sectors' && ( +
+
+

Strategic Sectors

+

Steelguard protects the infrastructure that power nations.

+ +
+ {[ + { icon: Building2, label: "Enterprise", count: "400+ Nodes" }, + { icon: Globe, label: "Government", count: "12 Nations" }, + { icon: Shield, label: "Defense", count: "Active Mesh" } + ].map((sector, i) => ( +
+
+ +
+

{sector.label}

+ {sector.count} +
+ ))} +
+
+
+ )} + + {/* COMPANY VIEW */} + {activeTab === 'company' && ( +
+
+
+
+
+ Office +
+
+ Origins +

Engineering
Secured Future

+

+ Founded in 2042, Steelguard Defense Systems has evolved from a small hardware research lab into a global leader in AI-driven security architecture. Our mission is absolute protection through innovation. +

+
+
+

15

+

Global Hubs

+
+
+

850+

+

Architects

+
+
+
+
+
+
+ )} + + {/* MAP INTEGRATION */} + {activeTab === 'home' && ( +
+
+
+ +

Command Center HQ

+
+
+
+
+ +
+
+
+
+ )} + +
+ + {/* Footer / CTA */} +
+
+ {[ + { val: '500+', label: 'NODES ACTIVE' }, + { val: '0.01ms', label: 'LATENCY' }, + { val: '100%', label: 'UPTIME' }, + { val: 'SECURE', label: 'STATUS' } + ].map((stat, i) => ( +
+
{stat.val}
+
{stat.label}
+
+ ))} +
+ +
+
+ +
+

+ SECURE YOUR FUTURE +

+

+ Initiate contact with our security architects. Design a defense strategy leveraging next-gen technologies. +

+
+ + +
+
+
© 2024 STEELGUARD DEFENSE SYSTEMS.
+
SYSTEM_ID: MW-ENT-8842
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/demos/SweetCraving.tsx b/pages/demos/SweetCraving.tsx new file mode 100644 index 0000000..a551e48 --- /dev/null +++ b/pages/demos/SweetCraving.tsx @@ -0,0 +1,266 @@ +import React, { useEffect, useState } from 'react'; +import { ArrowLeft, Instagram, Facebook, MapPin, Phone, Mail, Clock, Heart, Star, ChevronDown } from 'lucide-react'; +import { Link } from 'react-router-dom'; + +export const SweetCraving: React.FC = () => { + const [scrolled, setScrolled] = useState(false); + + useEffect(() => { + const handleScroll = () => { + setScrolled(window.scrollY > 50); + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return ( +
+ {/* Return to Main Site Button */} +
+ + + +
+ + {/* Dynamic Navigation */} + + + {/* Hero Section */} +
+
+ Desszertműhely Hero +
+
+ +
+
+ + Kézműves Desszertműhely + +
+ +

+ Édes Álmok
+ + Süteménybe Zárva + +

+ +

+ Természetes alapanyagokból, szívvel-lélekkel készült desszertek minden alkalomra. +
Mert minden nap megérdemel egy kis kényeztetést. +

+ + +
+ + {/* Scroll Indicator */} +
+ +
+
+ + {/* About Section */} +
+
+
+
+
+ Pék és Cukrászmunka + {/* Floating Badge */} +
+
+

5+

+

Év Tapasztalat

+
+
+
+ +
+

+ A szenvedélyünk
+ a minőség +

+

+ 2018 óta készítjük desszertjeinket Budapest szívében. Hiszünk abban, hogy egy sütemény nemcsak étel, hanem élmény. Ezért kizárólag prémium belga csokoládét, termelői tejet és friss gyümölcsöket használunk. +

+ +
+ {[ + { text: '100% Természetes alapanyagok', icon: Heart }, + { text: 'Glutén- és laktózmentes opciók', icon: Star }, + { text: 'Egyedi tortarendelés 48 órán belül', icon: Clock } + ].map((item, i) => ( +
+
+ +
+ {item.text} +
+ ))} +
+
+
+
+
+ + {/* Menu Grid */} + + + {/* Testimonials (Simple) */} +
+
+ +

Vendégeink mondták

+
+ "A legfinomabb macaron, amit valaha ettem! A pisztáciás egyszerűen mennyei. Mindenkinek csak ajánlani tudom a helyet, igazi ékszerdoboz." +
+ — Kovács Anna, Budapest +
+
+ + {/* Contact & Footer */} +
+ {/* Background Pattern */} +
+ +
+

Látogass el hozzánk

+ +
+
+
+ +
+

Címünk

+

1052 Budapest,
Petőfi Sándor utca 12.

+
+
+
+ +
+

Nyitvatartás

+

H-P: 08:00 - 19:00
Szo-V: 09:00 - 16:00

+
+
+
+ +
+

Kapcsolat

+

+36 1 234 5678
hello@sweetcraving.hu

+
+
+ +
+ + + +
+ +
+ © 2024 SweetCraving. Ez egy demonstrációs weboldal a MotionWeb referenciáihoz. +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..e69de29 diff --git a/supabase_schema.sql b/supabase_schema.sql new file mode 100644 index 0000000..faae59a --- /dev/null +++ b/supabase_schema.sql @@ -0,0 +1,2 @@ + +zZ^rب$ \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2c6eed5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2022", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "types": [ + "node" + ], + "moduleResolution": "bundler", + "isolatedModules": true, + "moduleDetection": "force", + "allowJs": true, + "jsx": "react-jsx", + "paths": { + "@/*": [ + "./*" + ] + }, + "allowImportingTsExtensions": true, + "noEmit": true + } +} \ No newline at end of file diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..2b52392 --- /dev/null +++ b/types.ts @@ -0,0 +1,44 @@ + +export interface Service { + id: string; + title: string; + description: string; + icon: string; +} + +export interface ProductPackage { + id: string; + name: string; + price: string; + desc: string; // Description + features: string[]; + isPopular?: boolean; + cta: string; // Call to Action button text +} + +export interface Reference { + id: string; + title: string; + category: string; + imageUrl: string; +} + +export interface BlogPost { + id: string; + title: string; + excerpt: string; + date: string; + imageUrl: string; +} + +export interface Invoice { + id: string; + user_id: string; + order_id?: string; + invoice_type?: 'advance' | 'final' | null; + invoice_number: string; + amount: string; + created_at: string; + status: 'paid' | 'pending' | 'overdue'; + pdf_url?: string; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ee5fb8d --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,23 @@ +import path from 'path'; +import { defineConfig, loadEnv } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, '.', ''); + return { + server: { + port: 3000, + host: '0.0.0.0', + }, + plugins: [react()], + define: { + 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY), + 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY) + }, + resolve: { + alias: { + '@': path.resolve(__dirname, '.'), + } + } + }; +});