diff --git a/.gitignore b/.gitignore index 9621c0a..f75175a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ html/ stats.html # Secrets -.env +.env.* diff --git a/package.json b/package.json index a20beb2..27db7a6 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,11 @@ "@ant-design/colors" : "^7.1.0", "@ant-design/icons" : "^5.5.1", "@contentful/rich-text-html-renderer" : "^17.0.0", - "antd" : "^5.21.6", + "antd" : "^5.22.1", "contentful" : "^11.2.1", "i18next" : "^23.16.5", "i18next-browser-languagedetector" : "^8.0.0", + "mdb-react-ui-kit" : "^9.0.0", "react" : "^18.3.1", "react-dom" : "^18.3.1", "react-i18next" : "^15.1.1", @@ -44,7 +45,7 @@ "@types/node" : "^22.9.0", "@types/react" : "^18.3.12", "@types/react-dom" : "^18.3.1", - "@typescript-eslint/parser" : "^8.13.0", + "@typescript-eslint/parser" : "^8.14.0", "@vitejs/plugin-react-swc" : "^3.7.1", "eslint" : "^9.14.0", "eslint-plugin-import" : "^2.31.0", @@ -56,8 +57,8 @@ "prop-types" : "^15.8.1", "rollup-plugin-visualizer" : "^5.12.0", "typescript" : "^5.6.3", - "typescript-eslint" : "^8.13.0", - "vite" : "^5.4.10" + "typescript-eslint" : "^8.14.0", + "vite" : "^5.4.11" }, "engines" : { "node" : ">=20", diff --git a/public/kutan-ural-MZPwImQUDM0-unsplash.jpg b/public/kutan-ural-MZPwImQUDM0-unsplash.jpg new file mode 100644 index 0000000..083aaaf Binary files /dev/null and b/public/kutan-ural-MZPwImQUDM0-unsplash.jpg differ diff --git a/public/margo-brodowicz-nzAZxPyhZ2g-unsplash.jpg b/public/margo-brodowicz-nzAZxPyhZ2g-unsplash.jpg new file mode 100644 index 0000000..481b5c9 Binary files /dev/null and b/public/margo-brodowicz-nzAZxPyhZ2g-unsplash.jpg differ diff --git a/public/matt-jones-xpDHTc-pkog-unsplash.jpg b/public/matt-jones-xpDHTc-pkog-unsplash.jpg new file mode 100644 index 0000000..44767d2 Binary files /dev/null and b/public/matt-jones-xpDHTc-pkog-unsplash.jpg differ diff --git a/public/possessed-photography-zbLW0FG8XU8-unsplash.jpg b/public/possessed-photography-zbLW0FG8XU8-unsplash.jpg new file mode 100644 index 0000000..6a9e0b6 Binary files /dev/null and b/public/possessed-photography-zbLW0FG8XU8-unsplash.jpg differ diff --git a/src/App.tsx b/src/App.tsx index b774650..d1986d9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,16 @@ -import { createBrowserRouter, RouterProvider, } from 'react-router-dom'; +import { createHashRouter, RouterProvider, } from 'react-router-dom'; -import { AgreementPage, HomePage, } from './pages'; +import { AgreementPage, HomePage, SecureZone, } from './pages'; -const router = createBrowserRouter([ +const router = createHashRouter([ { path : '/', Component : HomePage, }, + { + path : '/products/securezone', + Component : SecureZone, + }, { path : '/agreements', children : [ @@ -17,4 +21,5 @@ const router = createBrowserRouter([ ], }, ]); + export const App = () => ; diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 0896e34..cac1ddc 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -24,11 +24,11 @@ export const Footer = () => { return ( diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 45bef08..e5dbb7f 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,5 +1,5 @@ import { Anchor, Button, Layout, Typography, } from 'antd'; -import { useEffect, useState, } from 'react'; +import { type MouseEvent, type ReactNode, useEffect, useState, } from 'react'; import { useNavigate, } from 'react-router-dom'; import { useTranslation, } from 'react-i18next'; import { useMediaQuery, } from 'usehooks-ts'; @@ -19,16 +19,21 @@ export const Header = ({ const { t, } = useTranslation(); - const handleScroll = () => { - if (window.scrollY > 0) { - setIsTop(false); - } else { - setIsTop(true); - } - }; + const handleScroll = () => setIsTop(window.scrollY === 0); const handleClick = () => navigate('/'); + const handleHashLinkClick = (e : MouseEvent, link : { + title : ReactNode, + href : string, + }) => { + e.preventDefault(); + + document.getElementById(link.href)!.scrollIntoView({ + behavior : 'smooth', + }); + }; + useEffect(() => { window.addEventListener('scroll', handleScroll); @@ -80,7 +85,7 @@ export const Header = ({ 'solutions', ].map(key => ({ key, - href : `#${key}`, + href : `${key}`, title : ( ), - }))} /> + }))} + onClick={handleHashLinkClick} /> )} diff --git a/src/components/InfoText/InfoText.tsx b/src/components/InfoText/InfoText.tsx new file mode 100644 index 0000000..d0ac6e0 --- /dev/null +++ b/src/components/InfoText/InfoText.tsx @@ -0,0 +1,28 @@ +import { Tooltip, Typography, } from 'antd'; +import type { FC, } from 'react'; + +import { InfoTextProps, } from './InfoText.types'; + +const InfoText : FC = ({ + title, + children, + ...props +}) => { + const { style, ...rest } = props; + + return ( + + + {children} + + + ); +}; + +export default InfoText; diff --git a/src/components/InfoText/InfoText.types.ts b/src/components/InfoText/InfoText.types.ts new file mode 100644 index 0000000..7204144 --- /dev/null +++ b/src/components/InfoText/InfoText.types.ts @@ -0,0 +1,7 @@ +import type { ReactNode, } from 'react'; + +export interface InfoTextProps { + title? : string, + children? : ReactNode, + [ key : string ] : any, +} diff --git a/src/components/InfoText/index.ts b/src/components/InfoText/index.ts new file mode 100644 index 0000000..0dab212 --- /dev/null +++ b/src/components/InfoText/index.ts @@ -0,0 +1 @@ +export { default as InfoText, } from './InfoText'; diff --git a/src/components/index.ts b/src/components/index.ts index f3730ac..663a759 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,2 +1,3 @@ export { Footer, } from './Footer'; export { Header, } from './Header'; +export { InfoText, } from './InfoText'; diff --git a/src/i18n/en.json b/src/i18n/en.json index fcae33e..d0ca42e 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1,32 +1,72 @@ { - "app.name" : "GeekyLifeHacks", - "menu.home" : "Home", - "menu.about" : "About", - "menu.solutions" : "Solutions", - "sections.home.title" : "Everyday Solutions\nExtraordinary Savings", - "sections.home.subtitle" : "At GeekyLifeHacks, we offer affordable, practical and cost-effective solutions, From our easy-to-follow guides and expert advice to professional-grade apps, we simplify your life.", - "sections.about.title" : "We've got what you need!", - "sections.about.subtitle" : "From weather alerts to AI-powered security features and automated software installation scripts, our aim is to provide accessible and practical solutions to meet your unique needs.", - "sections.solutions.title" : "At your service", - "sections.solutions.0.caption" : "Innovative solutions", - "sections.solutions.0.description" : "Leading the future with groundbreaking technologies", - "sections.solutions.1.caption" : "Inspiring ideas", - "sections.solutions.1.description" : "Igniting creativity and fueling innovation", - "sections.solutions.2.caption" : "Honest approach", - "sections.solutions.2.description" : "Building trust through transparency and integrity", - "sections.solutions.3.caption" : "Passionate team", - "sections.solutions.3.description" : "Driven by a deep love for what we do", - "sections.products.0.caption" : "SecureZone", - "sections.products.0.description" : "A powerful security solution that protects your home", - "sections.products.1.caption" : "WeatherWatch", - "sections.products.1.description" : "Stay informed with real-time weather updates", - "sections.products.2.caption" : "MacBrewer", - "sections.products.2.description" : "The easiest way to install software on your Mac", - "footer.copyright" : "Copyright © {{year}} {{appName}}", - "footer.terms_and_conditions" : "Terms and Conditions", - "footer.privacy_policy" : "Privacy Policy", - "footer.acceptable_use_policy" : "Acceptable Use Policy", - "footer.disclaimer" : "Disclaimer", - "footer.return_policy" : "Return Policy", - "tags.coming_soon" : "Coming Soon" + "app.name" : "GeekyLifeHacks", + "menu.home" : "Home", + "menu.about" : "About", + "menu.solutions" : "Solutions", + "sections.home.title" : "Everyday Solutions\nExtraordinary Savings", + "sections.home.subtitle" : "At GeekyLifeHacks, we offer affordable, practical and cost-effective solutions, From our easy-to-follow guides and expert advice to professional-grade apps, we simplify your life.", + "sections.about.title" : "We've got what you need!", + "sections.about.subtitle" : "From weather alerts to AI-powered security features and automated software installation scripts, our aim is to provide accessible and practical solutions to meet your unique needs.", + "sections.solutions.title" : "At your service", + "sections.solutions.0.caption" : "Innovative solutions", + "sections.solutions.0.description" : "Leading the future with groundbreaking technologies", + "sections.solutions.1.caption" : "Inspiring ideas", + "sections.solutions.1.description" : "Igniting creativity and fueling innovation", + "sections.solutions.2.caption" : "Honest approach", + "sections.solutions.2.description" : "Building trust through transparency and integrity", + "sections.solutions.3.caption" : "Passionate team", + "sections.solutions.3.description" : "Driven by a deep love for what we do", + "sections.products.0.caption" : "SecureZone", + "sections.products.0.description" : "A powerful security solution that protects your home", + "sections.products.0.title" : "Protect you home. Your way.", + "sections.products.0.subtitle" : "Advanced intrusion detection solution that prioritises privacy by processing video locally, without sending data to the cloud.", + "sections.products.0.tagline.0" : "Your Privacy, Protected.", + "sections.products.0.summary.0" : "Safeguard your home without compromising your privacy. Our AI-powered security solution operates locally, ensuring your data remains secure on your home network.", + "sections.products.0.tagline.1" : "Big Security, Small Price Tag.", + "sections.products.0.summary.1" : "Experience professional-grade security without the premium cost. Our solution offers robust protection for your home at an affordable price.", + "sections.products.0.tagline.2" : "Smart Security, Simple Setup.", + "sections.products.0.summary.2" : "Easy to install and manage, our intelligent security solution offers powerful protection without the hassle.", + "sections.products.1.caption" : "WeatherWatch", + "sections.products.1.description" : "Stay informed with real-time weather updates", + "sections.products.2.caption" : "MacBrewer", + "sections.products.2.description" : "The easiest way to install software on your Mac", + "label.upgrade.title" : "Compare features across plans", + "label.upgrade.plans" : [ + "Basic", + "Standard", + "Premier" + ], + "label.upgrade.plans.0.description" : "A Basic Plan subscription\nallows 24/7 monitoring of\nup to 2 IP camera video streams", + "label.upgrade.plans.1.description" : "A Standard Plan subscription\nallows 24/7 monitoring of\nup to 5 IP camera video\nstreams and support rich notifications", + "label.upgrade.plans.2.description" : "A Premier Plan subscription\nallows 24/7 monitoring of\nup to 15 IP camera video\nstreams, support rich notifications\nand record videos even before\na detection is triggered", + "label.upgrade.plans.price" : "Monthly subscription", + "label.upgrade.plans.0.price" : "£2.99", + "label.upgrade.plans.1.price" : "£5.49", + "label.upgrade.plans.2.price" : "£12.49", + "label.upgrade.running_monitors" : "Running monitors", + "label.upgrade.intrusion_trigger" : "24/7 moving object intrusion trigger", + "label.upgrade.synology_integration" : "Integrated with Synology NAS", + "label.upgrade.snapshot_capture" : "Snapshot capture", + "label.upgrade.snapshot_capture_tooltip" : "See an image snapshot what was happening during moving object recognition events", + "label.upgrade.video_capture" : "Video capture", + "label.upgrade.video_capture_tooltip" : "See a video of what was happening during moving object recognition events", + "label.upgrade.video_capture_option_1" : "10 seconds", + "label.upgrade.video_capture_option_2" : "60 seconds\n(with 30 seconds before\nand after the event)", + "label.upgrade.rich_notifications" : "Rich notifications", + "label.upgrade.rich_notifications_tooltip" : "Rich notifications include a photo preview that let you see exactly what triggered an event", + "label.upgrade.customer_support" : "Customer support", + "label.upgrade.customer_support_options" : [ + "Community forum", + "Community forum", + "Live chat" + ], + "footer.copyright" : "Copyright © {{year}} {{appName}}", + "footer.terms_and_conditions" : "Terms and Conditions", + "footer.privacy_policy" : "Privacy Policy", + "footer.acceptable_use_policy" : "Acceptable Use Policy", + "footer.disclaimer" : "Disclaimer", + "footer.return_policy" : "Return Policy", + "tags.coming_soon" : "Coming Soon", + "actions.find_out_more" : "Find out more", + "actions.download" : "Download" } diff --git a/src/pages/Agreement.tsx b/src/pages/Agreement.tsx index ce365a9..d289027 100644 --- a/src/pages/Agreement.tsx +++ b/src/pages/Agreement.tsx @@ -2,7 +2,7 @@ import { documentToHtmlString, } from '@contentful/rich-text-html-renderer'; import { Layout, Typography, } from 'antd'; import { createClient, } from 'contentful'; import { useEffect, useState, } from 'react'; -import { useParams, } from 'react-router-dom'; +import { ScrollRestoration, useParams, } from 'react-router-dom'; import { useMediaQuery, } from 'usehooks-ts'; import { Footer, Header, } from '../components'; @@ -45,6 +45,7 @@ export const AgreementPage = () => {
+
{ const isDesktop = useMediaQuery('(min-width: 1000px)'); - const { t, } = useTranslation(); + const navigate = useNavigate(); + + const handleAboutClick = () => document.getElementById('about')!.scrollIntoView({ + behavior : 'smooth', + }); - const handleOpenMacBrewer = () => window.open('https://macbrewer.geekylifehacks.com', '_blank'); + const handleSolutionsClick = () => document.getElementById('solutions')!.scrollIntoView({ + behavior : 'smooth', + }); + + const { t, } = useTranslation(); return (
+
{ backgroundPosition : 'center', backgroundRepeat : 'no-repeat', backgroundAttachment : 'scroll', - } }> + }}>
{ size='large' type='primary' shape='round' - onClick={() => document.getElementById('about')!.scrollIntoView({ - behavior : 'smooth', - })}> - Find Out More + onClick={handleAboutClick}> + {t('actions.find_out_more')}
@@ -240,9 +248,7 @@ export const HomePage = () => { }} size='large' shape='round' - onClick={() => document.getElementById('solutions')!.scrollIntoView({ - behavior : 'smooth', - })}> + onClick={handleSolutionsClick}> Get Started @@ -319,41 +325,58 @@ export const HomePage = () => { marginBottom : 150, }}> - {[ ...Array(3).keys(), ].map(index => ( - - {t('sections.products.0.caption')} { + const handleClick = () => { + switch (index) { + case 0: + navigate('/products/securezone'); + break; + + case 2: + window.open('https://macbrewer.geekylifehacks.com', '_blank'); + break; + + default: + break; + } + }; + + return ( + + {t('sections.products.0.caption')} + ), + }} /> + {!isDesktop && ( +
- ), - }} /> - {!isDesktop && ( -
- -
- )} - - ))} +
+ )} + + ); + })}
diff --git a/src/pages/SecureZone.tsx b/src/pages/SecureZone.tsx new file mode 100644 index 0000000..b5c89e8 --- /dev/null +++ b/src/pages/SecureZone.tsx @@ -0,0 +1,460 @@ +import { CheckOutlined, CloseOutlined, } from '@ant-design/icons'; +import { Button, Col, Layout, Row, Typography, } from 'antd'; +import { MDBContainer, MDBTable, MDBTableBody, MDBTableHead, } from 'mdb-react-ui-kit'; +import 'mdb-react-ui-kit/dist/css/mdb.min.css'; +import { useTranslation, } from 'react-i18next'; +import { ScrollRestoration, } from 'react-router-dom'; +import { useMediaQuery, } from 'usehooks-ts'; + +import { Footer, Header, InfoText, } from '../components'; +import { PAGE_WIDTH, SECTION_WIDTH, } from '../constants'; + +export const SecureZone = () => { + const isDesktop = useMediaQuery('(min-width: 1000px)'); + + const { t, } = useTranslation(); + + const handleDownload = () => window.open('https://geekylifehacks.gitbook.io/securezone/', '_blank'); + + return ( + +
+ + +
+
+ + {t('sections.products.0.title')} + + + {t('sections.products.0.subtitle')} + + +
+
+
+
+ + +
+ + + + {t('sections.products.0.tagline.0')} + + + {t('sections.products.0.summary.0')} + + + + + + + {t('sections.products.0.tagline.1')} + + + {t('sections.products.0.summary.1')} + + + +
+ + + + +
+ + + + {t('sections.products.0.tagline.2')} + + + {t('sections.products.0.summary.2')} + + + +
+
+
+ + {t('label.upgrade.title')} + + + + + + + {(t('label.upgrade.plans', { + returnObjects : true, + }) as string[]).map(plan => ( + + + {plan} + + + ))} + + + + + + + {t('label.upgrade.plans.0.description')} + + + {t('label.upgrade.plans.1.description')} + + + {t('label.upgrade.plans.2.description')} + + + + + {t('label.upgrade.plans.price')} + + + {t('label.upgrade.plans.0.price')} + + + {t('label.upgrade.plans.1.price')} + + + {t('label.upgrade.plans.2.price')} + + + + + {t('label.upgrade.running_monitors')} + + 2 + 5 + 15 + + + + {t('label.upgrade.intrusion_trigger')} + + + + + + + + + + + + + + {t('label.upgrade.synology_integration')} + + + + + + + + + + + + + + {t('label.upgrade.snapshot_capture')} + + + + + + + + + + + + + + {t('label.upgrade.video_capture')} + + + + + {t('label.upgrade.video_capture_option_1')} + + {t('label.upgrade.video_capture_option_2')} + + + + + { t('label.upgrade.rich_notifications') } + + + + + + + + + + + + + + {t('label.upgrade.customer_support')} + + {(t('label.upgrade.customer_support_options', { + returnObjects : true, + }) as string[]).map((option, index) => ( + + { option } + + ))} + + + + + + + + + + + + + + + + +
+
+