├── src ├── hooks │ ├── index.ts │ └── layout │ │ ├── index.ts │ │ ├── useIsMobileByMediaQuery.ts │ │ ├── useIsMobileByWindowsResizing.ts │ │ ├── useIsMobileForNextJs.ts │ │ ├── useSetBodyClassMobileOrDesktop.ts │ │ └── useWindowSize.ts ├── components │ ├── Logo │ │ ├── index.tsx │ │ ├── Logo.module.css │ │ └── Logo.tsx │ ├── Icon │ │ ├── icons │ │ │ ├── info.txt │ │ │ └── IconNotFound.tsx │ │ ├── index.tsx │ │ ├── config.ts │ │ ├── Icon.tsx │ │ └── Icon.test.tsx │ ├── Video │ │ ├── index.tsx │ │ └── Video.tsx │ ├── common │ │ ├── Link │ │ │ ├── index.tsx │ │ │ ├── Link.tsx │ │ │ └── Link.test.tsx │ │ ├── Stack │ │ │ ├── index.ts │ │ │ ├── Stack.module.css │ │ │ ├── Stack.tsx │ │ │ └── Stack.test.tsx │ │ ├── Button │ │ │ ├── index.tsx │ │ │ ├── Button.module.css │ │ │ ├── Button.tsx │ │ │ └── Button.test.tsx │ │ ├── HtmlTag │ │ │ ├── index.tsx │ │ │ └── HtmlTag.tsx │ │ ├── Typo │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── utlils.ts │ │ │ ├── Typo.tsx │ │ │ ├── Typo.module.css │ │ │ └── Typo.test.tsx │ │ └── index.tsx │ ├── Picture │ │ ├── index.tsx │ │ ├── config.ts │ │ └── Picture.tsx │ ├── layout │ │ ├── Footer │ │ │ ├── index.tsx │ │ │ ├── Footer.module.css │ │ │ └── Footer.tsx │ │ ├── Header │ │ │ ├── index.tsx │ │ │ ├── TopMenuContent.tsx │ │ │ ├── Header.module.css │ │ │ └── Header.tsx │ │ ├── Wrapper │ │ │ ├── index.tsx │ │ │ ├── Wrapper.module.css │ │ │ ├── Wrapper.tsx │ │ │ └── Wrapper.test.tsx │ │ └── index.tsx │ ├── ContactForm │ │ ├── index.tsx │ │ ├── ContactForm.module.css │ │ └── ContactForm.tsx │ ├── SocialMedia │ │ ├── index.tsx │ │ └── SocialMedia.tsx │ ├── DownloadButton │ │ ├── index.tsx │ │ └── DownloadButton.tsx │ ├── tooling │ │ ├── MobileOrDesktop │ │ │ ├── index.tsx │ │ │ └── MobileOrDesktop.tsx │ │ ├── StylesInjector │ │ │ ├── index.tsx │ │ │ └── InjectVariablesIntoCss.tsx │ │ ├── Analytics │ │ │ ├── index.tsx │ │ │ ├── GoogleAnalytics.tsx │ │ │ ├── Analytics.tsx │ │ │ ├── AmplitudeAnalytics.tsx │ │ │ └── GoogleAnalyticsWithPageView.tsx │ │ ├── Advertising │ │ │ ├── index.tsx │ │ │ ├── Advertising.tsx │ │ │ └── GoogleAdsense.tsx │ │ └── index.tsx │ └── index.tsx ├── testing-library.test.ts ├── app │ ├── icon.png │ ├── favicon.ico │ ├── apple-icon.png │ ├── (main) │ │ ├── page.tsx │ │ ├── contact │ │ │ ├── BlockContactForm.tsx │ │ │ ├── page.tsx │ │ │ └── BlockSocialMedia.tsx │ │ ├── download │ │ │ ├── BlockDownloadButtons.tsx │ │ │ └── page.tsx │ │ ├── legal │ │ │ ├── page.tsx │ │ │ ├── terms-conditions │ │ │ │ └── page.tsx │ │ │ └── privacy-policy │ │ │ │ └── page.tsx │ │ ├── main.css │ │ ├── layout.tsx │ │ ├── dev │ │ │ └── page.tsx │ │ ├── sitemap │ │ │ └── page.tsx │ │ └── home │ │ │ └── page.tsx │ ├── layout.tsx │ ├── (unstyled) │ │ ├── simple-page │ │ │ └── page.tsx │ │ ├── unstyled.css │ │ └── layout.tsx │ ├── default.css │ ├── config.ts │ └── sitemap.ts ├── assets │ ├── info.txt │ └── favicon_package_v0.16.zip ├── style │ ├── index.ts │ ├── non-next-fonts.css │ ├── colors.ts │ ├── config.ts │ ├── fonts.ts │ └── non-next-fonts.ts ├── utils │ ├── index.ts │ ├── type.ts │ ├── sleep.ts │ ├── navigation.ts │ ├── localStorage.ts │ ├── sessionStorage.ts │ ├── text.ts │ └── environment.ts ├── lib │ ├── emailjs.js │ ├── ga.js │ └── amplitude.js └── config.ts ├── .prettierignore ├── public ├── robots.txt ├── files │ └── info.txt ├── ads.txt ├── img │ ├── favicon │ │ ├── 16x16.png │ │ ├── 32x32.png │ │ ├── 180x180.png │ │ ├── 192x192.png │ │ ├── 256x256.png │ │ ├── mstile-150x150.png │ │ └── safari-pinned-tab.svg │ ├── logo │ │ ├── logo-128.png │ │ ├── logo-256.png │ │ └── logo-96.png │ └── social │ │ ├── icon-256x256.png │ │ ├── umka-1200-630.png │ │ └── screenshot-400x600.png ├── test.html └── site.webmanifest ├── .eslintrc.json ├── jest.setup.ts ├── next.config.mjs ├── .gitignore ├── tsconfig.json ├── prettier.config.js ├── jest.config.ts ├── .env.example ├── README.md └── package.json /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layout'; 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | out 4 | styles -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | Disallow: /files/* 4 | -------------------------------------------------------------------------------- /public/files/info.txt: -------------------------------------------------------------------------------- 1 | Folder with files to download, excluded from indexing in robots.txt -------------------------------------------------------------------------------- /public/ads.txt: -------------------------------------------------------------------------------- 1 | TODO: put real adsense code here 2 | google.com, pub-xxxx, DIRECT, f1xxxxxx -------------------------------------------------------------------------------- /src/components/Logo/index.tsx: -------------------------------------------------------------------------------- 1 | import Logo from './Logo'; 2 | 3 | export default Logo; 4 | -------------------------------------------------------------------------------- /src/testing-library.test.ts: -------------------------------------------------------------------------------- 1 | test('testing library is properly configured', () => {}); 2 | -------------------------------------------------------------------------------- /src/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/src/app/icon.png -------------------------------------------------------------------------------- /src/components/Icon/icons/info.txt: -------------------------------------------------------------------------------- 1 | Put all custom SVG icons here as SWG files or React components -------------------------------------------------------------------------------- /src/components/Video/index.tsx: -------------------------------------------------------------------------------- 1 | import Video from './Video'; 2 | 3 | export default Video; 4 | -------------------------------------------------------------------------------- /src/components/common/Link/index.tsx: -------------------------------------------------------------------------------- 1 | import Link from './Link'; 2 | 3 | export default Link; 4 | -------------------------------------------------------------------------------- /src/components/Picture/index.tsx: -------------------------------------------------------------------------------- 1 | import Picture from './Picture'; 2 | 3 | export default Picture; 4 | -------------------------------------------------------------------------------- /src/components/common/Stack/index.ts: -------------------------------------------------------------------------------- 1 | import Stack from './Stack'; 2 | 3 | export default Stack; 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/assets/info.txt: -------------------------------------------------------------------------------- 1 | Assets and raw materials. Content of ths folder will not be included in the final build. -------------------------------------------------------------------------------- /src/components/common/Button/index.tsx: -------------------------------------------------------------------------------- 1 | import Button from './Button'; 2 | 3 | export default Button; 4 | -------------------------------------------------------------------------------- /src/components/layout/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import Footer from './Footer'; 2 | 3 | export default Footer; 4 | -------------------------------------------------------------------------------- /src/components/layout/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import Header from './Header'; 2 | 3 | export default Header; 4 | -------------------------------------------------------------------------------- /src/style/index.ts: -------------------------------------------------------------------------------- 1 | export * from './colors'; 2 | export * from './config'; 3 | export * from './fonts'; 4 | -------------------------------------------------------------------------------- /src/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/src/app/apple-icon.png -------------------------------------------------------------------------------- /src/components/common/HtmlTag/index.tsx: -------------------------------------------------------------------------------- 1 | import HtmlTag from './HtmlTag'; 2 | 3 | export default HtmlTag; 4 | -------------------------------------------------------------------------------- /src/components/layout/Wrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import Wrapper from './Wrapper'; 2 | 3 | export default Wrapper; 4 | -------------------------------------------------------------------------------- /src/components/ContactForm/index.tsx: -------------------------------------------------------------------------------- 1 | import ContactForm from './ContactForm'; 2 | 3 | export default ContactForm; 4 | -------------------------------------------------------------------------------- /src/components/SocialMedia/index.tsx: -------------------------------------------------------------------------------- 1 | import SocialMedia from './SocialMedia'; 2 | 3 | export default SocialMedia; 4 | -------------------------------------------------------------------------------- /public/img/favicon/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/favicon/16x16.png -------------------------------------------------------------------------------- /public/img/favicon/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/favicon/32x32.png -------------------------------------------------------------------------------- /public/img/logo/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/logo/logo-128.png -------------------------------------------------------------------------------- /public/img/logo/logo-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/logo/logo-256.png -------------------------------------------------------------------------------- /public/img/logo/logo-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/logo/logo-96.png -------------------------------------------------------------------------------- /src/components/Icon/index.tsx: -------------------------------------------------------------------------------- 1 | import Icon from './Icon'; 2 | 3 | export * from './config'; 4 | export default Icon; 5 | -------------------------------------------------------------------------------- /public/img/favicon/180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/favicon/180x180.png -------------------------------------------------------------------------------- /public/img/favicon/192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/favicon/192x192.png -------------------------------------------------------------------------------- /public/img/favicon/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/favicon/256x256.png -------------------------------------------------------------------------------- /src/components/common/Typo/index.ts: -------------------------------------------------------------------------------- 1 | import Typo from './Typo'; 2 | 3 | export * from './types'; 4 | export default Typo; 5 | -------------------------------------------------------------------------------- /src/components/DownloadButton/index.tsx: -------------------------------------------------------------------------------- 1 | import DownloadButton from './DownloadButton'; 2 | 3 | export default DownloadButton; 4 | -------------------------------------------------------------------------------- /public/img/social/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/social/icon-256x256.png -------------------------------------------------------------------------------- /public/img/social/umka-1200-630.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/social/umka-1200-630.png -------------------------------------------------------------------------------- /src/assets/favicon_package_v0.16.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/src/assets/favicon_package_v0.16.zip -------------------------------------------------------------------------------- /public/img/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /src/components/tooling/MobileOrDesktop/index.tsx: -------------------------------------------------------------------------------- 1 | import MobileOrDesktop from './MobileOrDesktop'; 2 | 3 | export default MobileOrDesktop; 4 | -------------------------------------------------------------------------------- /public/img/social/screenshot-400x600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umka-co/nextjs-website-starter/HEAD/public/img/social/screenshot-400x600.png -------------------------------------------------------------------------------- /src/components/tooling/StylesInjector/index.tsx: -------------------------------------------------------------------------------- 1 | import StylesInjector from './InjectVariablesIntoCss'; 2 | 3 | export default StylesInjector; 4 | -------------------------------------------------------------------------------- /src/app/(main)/page.tsx: -------------------------------------------------------------------------------- 1 | import HomePage from './home/page'; 2 | 3 | export * from './home/page'; // `metadata` and so on... 4 | 5 | export default HomePage; 6 | -------------------------------------------------------------------------------- /src/components/tooling/Analytics/index.tsx: -------------------------------------------------------------------------------- 1 | import Analytics from './Analytics'; 2 | 3 | /** 4 | * Analytics scripts injector 5 | */ 6 | 7 | export default Analytics; 8 | -------------------------------------------------------------------------------- /src/components/layout/index.tsx: -------------------------------------------------------------------------------- 1 | import Footer from './Footer'; 2 | import Header from './Header'; 3 | import Wrapper from './Wrapper'; 4 | 5 | export { Footer, Header, Wrapper }; 6 | -------------------------------------------------------------------------------- /src/components/tooling/Advertising/index.tsx: -------------------------------------------------------------------------------- 1 | import Advertising from './Advertising'; 2 | 3 | /** 4 | * Advertising scripts injector 5 | */ 6 | 7 | export default Advertising; 8 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"], 3 | "rules": { 4 | "import/no-cycle": "error" 5 | }, 6 | "ignorePatterns": ["node_modules", "/**/*.test.*", "/**/*.spec.*"] 7 | } 8 | -------------------------------------------------------------------------------- /src/components/layout/Wrapper/Wrapper.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | margin: 0 auto; 3 | max-width: 1200px; 4 | padding: 0 0.5rem; 5 | } 6 | 7 | .fullWidth { 8 | max-width: 100%; 9 | width: 100%; 10 | padding: 0; 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './environment'; 2 | export * from './localStorage'; 3 | export * from './navigation'; 4 | export * from './sessionStorage'; 5 | export * from './sleep'; 6 | export * from './type'; 7 | export * from './text'; 8 | -------------------------------------------------------------------------------- /src/components/common/index.tsx: -------------------------------------------------------------------------------- 1 | import Button from './Button'; 2 | import HtmlTag from './HtmlTag'; 3 | import Link from './Link'; 4 | import Stack from './Stack'; 5 | import Typo from './Typo'; 6 | 7 | export { Button, HtmlTag, Link, Stack, Typo }; 8 | -------------------------------------------------------------------------------- /src/components/common/Typo/types.ts: -------------------------------------------------------------------------------- 1 | export type TypoColor = 'dark' | 'light' | 'primary' | 'secondary' | string; 2 | export type TypoVariant = 'text' | 'paragraph' | 'header1' | 'header2' | 'header3' | 'list'; 3 | export type TypoAlign = 'center' | 'left' | 'right' | 'justify'; 4 | -------------------------------------------------------------------------------- /src/components/tooling/index.tsx: -------------------------------------------------------------------------------- 1 | import Analytics from './Analytics'; 2 | import Advertising from './Advertising'; 3 | import MobileOrDesktop from './MobileOrDesktop'; 4 | import StylesInjector from './StylesInjector'; 5 | 6 | export { Analytics, Advertising, MobileOrDesktop, StylesInjector }; 7 | -------------------------------------------------------------------------------- /src/utils/type.ts: -------------------------------------------------------------------------------- 1 | // Helper to read object's properties as obj['name'] 2 | export type ObjectPropByName = Record; 3 | 4 | // Props for Pictures, Screenshots, etc. 5 | export interface PictureProps extends Partial { 6 | alt?: string; 7 | src: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/sleep.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Delays code executions for specific amount of time. Must be called with await! 3 | * @param {number} interval - number of milliseconds to wait for 4 | */ 5 | export async function sleep(interval = 1000) { 6 | return new Promise((resolve) => setTimeout(resolve, interval)); 7 | } 8 | 9 | export default sleep; 10 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent, PropsWithChildren } from 'react'; 2 | import './default.css'; 3 | 4 | /** 5 | * Root layout, renders only the tag! 6 | * @layout Root 7 | */ 8 | const RootLayout: FunctionComponent = ({ children }) => { 9 | return {children}; 10 | }; 11 | 12 | export default RootLayout; 13 | -------------------------------------------------------------------------------- /src/lib/emailjs.js: -------------------------------------------------------------------------------- 1 | import emailjs from '@emailjs/browser'; 2 | 3 | let emailjsInitialized = false; 4 | 5 | if (process.env.NEXT_PUBLIC_EMAILJS_KEY) { 6 | emailjs.init(process.env.NEXT_PUBLIC_EMAILJS_KEY); 7 | emailjsInitialized = true; 8 | } 9 | 10 | // Helper to check if EmailJs was initialized. 11 | export const isEmailJsInitialized = () => emailjsInitialized; 12 | 13 | export default emailjs; 14 | -------------------------------------------------------------------------------- /jest.setup.ts: -------------------------------------------------------------------------------- 1 | // Optional: configure or set up a testing framework before each test. 2 | // If you delete this file, remove `setupFilesAfterEnv` from `jest.config.*` 3 | 4 | // Used for __tests__/testing-library.js 5 | // Learn more: https://github.com/testing-library/jest-dom 6 | import '@testing-library/jest-dom'; 7 | 8 | // To get 'next/router' working with tests 9 | jest.mock('next/router', () => require('next-router-mock')); 10 | -------------------------------------------------------------------------------- /src/components/index.tsx: -------------------------------------------------------------------------------- 1 | import Icon from './Icon'; 2 | import Logo from './Logo'; 3 | import Picture from './Picture'; 4 | import SocialMedia from './SocialMedia'; 5 | import Video from './Video'; 6 | 7 | export * from './common'; 8 | export * from './layout'; 9 | export * from './tooling'; 10 | 11 | // Note: Re-export only frequently used components to avoid the mess in codebase 12 | export { Icon, Logo, Picture, SocialMedia, Video }; 13 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: 'export', 4 | trailingSlash: true, 5 | images: { unoptimized: true }, 6 | 7 | env: { 8 | AUTHOR: 'KARPOLAN', 9 | // npm_package_name: process.env.npm_package_name, 10 | // npm_package_version: process.env.npm_package_version, 11 | }, 12 | 13 | reactStrictMode: true, 14 | // reactStrictMode: false, 15 | }; 16 | 17 | export default nextConfig; 18 | -------------------------------------------------------------------------------- /src/app/(unstyled)/simple-page/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | /** 4 | * Content of "Unstyled" page 5 | * @page Unstyled 6 | */ 7 | const SimplePage = () => { 8 | return ( 9 |
10 |

Simple Page

11 |

This is a simple of "Unstyled" page.

12 |

OK, with minimum styling :)

13 | Go back to home 14 |
15 | ); 16 | }; 17 | 18 | export default SimplePage; 19 | -------------------------------------------------------------------------------- /src/components/tooling/MobileOrDesktop/MobileOrDesktop.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useModifyBodyClassMobileOrDesktop } from '@/hooks/layout'; 3 | 4 | /** 5 | * Injects a hook to change the body class depending on screen size: isMobile vs. isDesktop 6 | * @injector MobileOrDesktop 7 | */ 8 | const MobileOrDesktop = () => { 9 | useModifyBodyClassMobileOrDesktop(); 10 | return null; // No need to render anything 11 | }; 12 | 13 | export default MobileOrDesktop; 14 | -------------------------------------------------------------------------------- /src/components/common/Stack/Stack.module.css: -------------------------------------------------------------------------------- 1 | .stack { 2 | /* !!! Don't set padding, margins, or height on this element. !!! */ 3 | display: flex; 4 | } 5 | 6 | .column { 7 | flex-direction: column; 8 | } 9 | 10 | .row { 11 | flex-direction: row; 12 | justify-content: center; 13 | flex-wrap: wrap; 14 | } 15 | 16 | .column-reverse { 17 | flex-direction: column-reverse; 18 | } 19 | 20 | .row-reverse { 21 | flex-direction: row-reverse; 22 | justify-content: center; 23 | flex-wrap: wrap; 24 | } 25 | -------------------------------------------------------------------------------- /src/style/non-next-fonts.css: -------------------------------------------------------------------------------- 1 | /* This file is just an example how to import fonts manually, without using next/fonts package */ 2 | 3 | /* Font Roboto */ 4 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap'); 5 | /* Font Open Sans */ 6 | @import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap'); 7 | 8 | .font-roboto { 9 | font-family: 'Roboto'; 10 | } 11 | 12 | .font-open-sans { 13 | font-family: 'Open Sans'; 14 | } 15 | -------------------------------------------------------------------------------- /public/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Page 5 | 6 | 14 | 15 | 16 | 17 | This is a Test Page, nothing to see here. 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/common/HtmlTag/HtmlTag.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent, HTMLAttributes, createElement } from 'react'; 2 | 3 | interface Props extends HTMLAttributes { 4 | tag: keyof JSX.IntrinsicElements; 5 | } 6 | 7 | /** 8 | * Renders the HTML tag by given tag name 9 | * @component HtmlTag 10 | * @param {string} tag - tag name to render 11 | */ 12 | const HtmlTag: FunctionComponent = ({ children, tag, ...restOfProps }) => 13 | createElement(tag, restOfProps, children); 14 | 15 | export default HtmlTag; 16 | -------------------------------------------------------------------------------- /src/style/colors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Theme colors for the website, applied in CSS as `var(--color-nameOfColorsObjectProperty)` 3 | */ 4 | export const COLORS = { 5 | // TODO: Put your theme/style color here 6 | text: '#222222', // Text color 7 | background: '#FFFFFF', // Background color and contrast color for icons 8 | headerAndFooter: '#fff5ed', // Background color for header and footer 9 | primary: '#BF2A1D', 10 | secondary: '#404040', 11 | success: '#008000', 12 | warning: '#bbbb00', 13 | error: '#ff0000', 14 | }; 15 | -------------------------------------------------------------------------------- /src/app/default.css: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | Basic styles, shared by (main) and (unstyled) layouts 4 | 5 | *******************************************************************************/ 6 | * { 7 | box-sizing: border-box; 8 | padding: 0; 9 | margin: 0; 10 | } 11 | 12 | @media (prefers-color-scheme: dark) { 13 | html { 14 | color-scheme: dark; 15 | } 16 | } 17 | 18 | a { 19 | color: inherit; 20 | text-decoration: none; 21 | } 22 | 23 | a:hover { 24 | text-decoration: underline; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/(unstyled)/unstyled.css: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | Styles used by the Unstyled layout. Applied to `/src/app/(unstyled)/...` pages. 4 | 5 | *******************************************************************************/ 6 | 7 | body { 8 | /* margin: 0 auto; 9 | max-width: 800px; */ 10 | } 11 | 12 | h1, 13 | h2 { 14 | margin-top: 1rem; 15 | margin-bottom: 0.5rem; 16 | } 17 | 18 | ul, 19 | p { 20 | margin-top: 0.5rem; 21 | margin-bottom: 0.5rem; 22 | } 23 | 24 | li { 25 | margin-left: 1.5rem; 26 | } 27 | -------------------------------------------------------------------------------- /src/hooks/layout/index.ts: -------------------------------------------------------------------------------- 1 | import { IS_SERVER } from '@/utils/environment'; 2 | import { SERVER_SIDE_MOBILE_FIRST } from '@/style'; 3 | import useIsMobileForNextJs from './useIsMobileForNextJs'; 4 | import useSetBodyClassMobileOrDesktop from './useSetBodyClassMobileOrDesktop'; 5 | 6 | /** 7 | * We need "smart export wrappers", because we can not use hooks on the server side 8 | */ 9 | export const useIsMobile = IS_SERVER ? () => SERVER_SIDE_MOBILE_FIRST : useIsMobileForNextJs; 10 | export const useModifyBodyClassMobileOrDesktop = IS_SERVER ? () => undefined : useSetBodyClassMobileOrDesktop; 11 | -------------------------------------------------------------------------------- /src/hooks/layout/useIsMobileByMediaQuery.ts: -------------------------------------------------------------------------------- 1 | import { useMediaQuery } from 'react-responsive'; 2 | import { MOBILE_SCREEN_MAX_WIDTH } from '@/style'; 3 | 4 | /** 5 | * Hook to detect isMobile vs. isDesktop using Media Query 6 | * @param {number} [maxWidth] - max width for mobile screen, default is MOBILE_SCREEN_MAX_WIDTH 7 | * @returns {boolean} true when the screen size is matching isMobile criteria 8 | */ 9 | function useIsMobileByMediaQuery(maxWidth = MOBILE_SCREEN_MAX_WIDTH) { 10 | const isMobile = useMediaQuery({ maxWidth }); 11 | return isMobile; 12 | } 13 | 14 | export default useIsMobileByMediaQuery; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /src/hooks/layout/useIsMobileByWindowsResizing.ts: -------------------------------------------------------------------------------- 1 | import { MOBILE_SCREEN_MAX_WIDTH } from '@/style'; 2 | import useWindowsSize from './useWindowSize'; 3 | 4 | /** 5 | * Hook to detect isMobile vs. isDesktop using "resize" event listener 6 | * @param {number} [maxWidth] - max width for mobile screen, default is MOBILE_SCREEN_MAX_WIDTH 7 | * @returns {boolean} true when the screen size is matching isMobile criteria 8 | */ 9 | function useIsMobileByWindowsResizing(maxWidth = MOBILE_SCREEN_MAX_WIDTH) { 10 | const { width } = useWindowsSize(); 11 | const isMobile = width <= maxWidth; 12 | return isMobile; 13 | } 14 | 15 | export default useIsMobileByWindowsResizing; 16 | -------------------------------------------------------------------------------- /src/app/(main)/contact/BlockContactForm.tsx: -------------------------------------------------------------------------------- 1 | import { APP_NAME } from '@/config'; 2 | import { Typo } from '@/components'; 3 | import ContactForm from '@/components/ContactForm'; 4 | 5 | const BlockContactForm = () => { 6 | return ( 7 | <> 8 | Contact form 9 | 10 | If you have any inquiries or feedback concerning the {APP_NAME}, please don't hesitate to 11 | reach out to us. Simply complete the form below, and we'll get back to you promptly. 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | export default BlockContactForm; 19 | -------------------------------------------------------------------------------- /src/style/config.ts: -------------------------------------------------------------------------------- 1 | // Styling and layout configuration 2 | // NOTE: Manage Fonts and Colors outside this file 3 | 4 | import { COLORS } from './colors'; 5 | 6 | export const MOBILE_SCREEN_MAX_WIDTH = 800; // 640 7 | export const SERVER_SIDE_MOBILE_FIRST = true; // true - for mobile, false - for desktop 8 | 9 | /** 10 | * Button component 11 | */ 12 | export const BUTTON_VARIANT = 'contained'; // | 'text' | 'outlined' 13 | export const BUTTON_MARGIN = '0.5rem'; 14 | export const BUTTON_ICON_SIZE = '1.5rem'; 15 | 16 | /** 17 | * Icon component 18 | */ 19 | export const ICON_SIZE = '4rem'; 20 | export const ICON_COLOR_NORMAL = COLORS.primary; 21 | export const ICON_COLOR_INVERTED = COLORS.background; 22 | -------------------------------------------------------------------------------- /src/app/(unstyled)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent, PropsWithChildren } from 'react'; 2 | import Analytics from '@/components/tooling/Analytics'; 3 | import './unstyled.css'; 4 | import StylesInjector from '../../components/tooling/StylesInjector'; 5 | 6 | /** 7 | * Layout for (unstyled) pages, renders head and body tags 8 | * @layout Unstyled 9 | */ 10 | const UnstyledLayout: FunctionComponent = ({ children }) => { 11 | return ( 12 | <> 13 | 14 | 15 | 16 | 17 | 18 |
{children}
19 | 20 | 21 | ); 22 | }; 23 | 24 | export default UnstyledLayout; 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | }, 23 | "target": "ES2017" 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /src/app/(main)/contact/page.tsx: -------------------------------------------------------------------------------- 1 | import { APP_NAME, PUBLIC_URL } from '@/config'; 2 | import { Wrapper } from '@/components'; 3 | import BlockSocialMedia from './BlockSocialMedia'; 4 | import BlockContactForm from './BlockContactForm'; 5 | 6 | /** 7 | * Content of the "Contact" page 8 | * @page Contact 9 | */ 10 | const ContactPage = () => { 11 | return ( 12 | 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | /** 20 | * MetaData for the page 21 | */ 22 | export const metadata = { 23 | title: `Contact - ${APP_NAME}`, 24 | alternates: { 25 | canonical: `${PUBLIC_URL}/contact/`, 26 | }, 27 | }; 28 | 29 | export default ContactPage; 30 | -------------------------------------------------------------------------------- /src/components/tooling/Advertising/Advertising.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react'; 2 | import { IS_PRODUCTION } from '@/config'; 3 | import GoogleAdsense from './GoogleAdsense'; 4 | 5 | /** 6 | * Add supporting scripts for Google Adsense, and other advertising services. 7 | * @injector Advertising 8 | */ 9 | const Advertising = () => { 10 | if (!IS_PRODUCTION) { 11 | console.log(`Advertising scripts WERE NOT installed due to NEXT_PUBLIC_ENV == "${process.env.NEXT_PUBLIC_ENV}"`); 12 | return null; // Don't render advertising scripts on development and test environments 13 | } 14 | 15 | return ( 16 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default Advertising; 23 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentEnvironment } from '@/utils/environment'; 2 | 3 | export const IS_DEBUG = process.env.NEXT_PUBLIC_DEBUG === 'true'; // Enables logging, etc. 4 | 5 | export const IS_PRODUCTION = getCurrentEnvironment() === 'production'; // Enables analytics, etc. 6 | // export const IS_PRODUCTION = process.env.NEXT_PUBLIC_ENV === 'production'; // Enables analytics, etc. 7 | 8 | // export const PUBLIC_URL = envRequired(process.env.NEXT_PUBLIC_PUBLIC_URL); // Variant 1 - value is required 9 | export const PUBLIC_URL = process.env.NEXT_PUBLIC_PUBLIC_URL ?? 'http://localhost:3000'; // Variant 2 - value is optional 10 | 11 | export const APP_NAME = 'TODO: Add Name'; 12 | export const APP_SHORT_NAME = 'TODO: Add Short Name'; 13 | -------------------------------------------------------------------------------- /src/app/(main)/contact/BlockSocialMedia.tsx: -------------------------------------------------------------------------------- 1 | import { Typo } from '@/components'; 2 | import SocialMedia from '@/components/SocialMedia'; 3 | 4 | const BlockSocialMedia = () => { 5 | return ( 6 | <> 7 | Social media 8 | 9 | Connect with our team through various social media channels. Choose the one that suits you best: 10 | 11 | 12 | 13 | Feel free to send us a message or follow our updates on your preferred platform. We value your engagement and 14 | look forward to connecting with you! 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default BlockSocialMedia; 21 | -------------------------------------------------------------------------------- /src/components/tooling/Analytics/GoogleAnalytics.tsx: -------------------------------------------------------------------------------- 1 | import { GA_ID } from '@/lib/ga'; 2 | 3 | /** 4 | * Adds supporting scripts for Google Analytics, also tracks each page view. 5 | * @injector GoogleAnalytics 6 | */ 7 | const GoogleAnalytics = () => { 8 | return ( 9 | <> 10 |