diff --git a/.env b/.env
index 5340f29..c520cb3 100644
--- a/.env
+++ b/.env
@@ -2,16 +2,14 @@
# For others consider to use .env.local
# Disable google analytics when running app in dev mode
-NEXT_PUBLIC_DISABLE_GA_IN_DEV_MODE=true
-
-NEXT_PUBLIC_INSTAGRAM_ADDRESS="https://www.instagram.com/salsavivayerevan/"
-NEXT_PUBLIC_FACEBOOK_ADDRESS="https://www.facebook.com/SalsaVivaYerevan/"
-NEXT_PUBLIC_WHATSAPP_ADDRESS="https://wa.me/37443108588"
-NEXT_PUBLIC_TELEGRAM_ADDRESS="https://t.me/SV_Yerevan"
-NEXT_PUBLIC_CONTACT_EMAIL="sv.yerevan@gmail.com"
-NEXT_PUBLIC_CONTACT_PHONE="+374 431 085 88"
-
-NEXT_PUBLIC_LOCATION_GOOGLE_MAPS_LINK="https://shorturl.at/gsuK5"
-NEXT_PUBLIC_LOCATION_ADDRESS_TEXT=" 41, 4 Abovyan St, Yerevan 0009"
-
-BASE_PATH="/salsaviva"
+DISABLE_GA_IN_DEV_MODE=true
+
+# Socials
+INSTAGRAM_ADDRESS="https://www.instagram.com/salsavivayerevan/"
+FACEBOOK_ADDRESS="https://www.facebook.com/SalsaVivaYerevan/"
+WHATSAPP_ADDRESS="https://wa.me/37443108588"
+TELEGRAM_ADDRESS="https://t.me/SV_Yerevan"
+CONTACT_EMAIL="sv.yerevan@gmail.com"
+CONTACT_PHONE="+374 431 085 88"
+LOCATION_GOOGLE_MAPS_LINK="https://shorturl.at/gsuK5"
+LOCATION_ADDRESS_TEXT=" 41, 4 Abovyan St, Yerevan 0009"
diff --git a/.env.sample b/.env.sample
index 84021a4..f8e8b8c 100644
--- a/.env.sample
+++ b/.env.sample
@@ -2,7 +2,15 @@
# Copy contents of this file to .env.local
# Replace values with your own
-NEXT_PUBLIC_GA_TRACKING_ID=""
-NEXT_PUBLIC_HOTJAR_ID=""
-NEXT_PUBLIC_HOTJAR_SNIPPET_VERSION=""
-NEXT_PUBLIC_FORMSPREE_ID=""
+# Google analytics tracking id
+GA_TRACKING_ID=""
+
+# Hotjar id and version
+HOTJAR_ID=""
+HOTJAR_SNIPPET_VERSION=""
+
+# Fromspree id to send form data
+FORMSPREE_ID=""
+
+# Base path to load assets, for instance used in production on github pages
+ASSETS_BASE_PATH=""
diff --git a/.eslintrc.json b/.eslintrc.json
index a0552bd..b0f6e70 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -57,7 +57,8 @@
"newlines-between": "never",
"warnOnUnassignedImports": true
}
- ]
+ ],
+ "no-duplicate-imports": 1
},
"overrides": [
{
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4d697d2..e3df454 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -7,14 +7,9 @@ on:
inputs:
public_path:
type: string
- description: 'Inject public path'
+ description: 'Path to load static assets'
required: false
default: ''
- branch:
- type: string
- description: 'Branch to build'
- required: true
- default: 'master'
jobs:
build:
@@ -23,8 +18,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- with:
- ref: ${{ github.event.inputs.branch }}
- name: Setup Nodejs
uses: actions/setup-node@v3
@@ -36,17 +29,20 @@ jobs:
npm pkg delete scripts.prepare
npm ci
- - name: Make envfile
- uses: SpicyPizza/create-envfile@v2.0
- with:
- envkey_NEXT_PUBLIC_GA_TRACKING_ID: "${{ secrets.NEXT_PUBLIC_GA_TRACKING_ID }}"
- envkey_NEXT_PUBLIC_FORMSPREE_ID: "${{ secrets.NEXT_PUBLIC_FORMSPREE_ID }}"
- envkey_NEXT_PUBLIC_HOTJAR_ID: ${{ secrets.NEXT_PUBLIC_HOTJAR_ID }}
- envkey_NEXT_PUBLIC_HOTJAR_SNIPPET_VERSION: ${{ secrets.NEXT_PUBLIC_HOTJAR_SNIPPET_VERSION }}
- envkey_PUBLIC_PATH: ${{ github.event.inputs.public_path }}
- directory: '.'
- file_name: .env.local
- fail_on_empty: false
+ - name: Setup env
+ run: |
+ config='
+ PUBLIC_PATH = "${{ github.event.inputs.public_path }}""
+
+ GA_TRACKING_ID = "${{ secrets.GA_TRACKING_ID }}"
+ FORMSPREE_ID = "${{ secrets.FORMSPREE_ID }}"
+ HOTJAR_ID = "${{ secrets.HOTJAR_ID }}"
+ HOTJAR_SNIPPET_VERSION = "${{ secrets.HOTJAR_SNIPPET_VERSION }}"
+
+ ASSETS_BASE_PATH = "${{ inputs.public_path }}"
+ '
+ echo "$config" > env.production
+ cat ./env.production
- name: Lint
run: |
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index a1c6e9c..b36227f 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -19,7 +19,7 @@ jobs:
build:
uses: ./.github/workflows/build.yml
with:
- branch: ${{ github.event.head_commit.message }}
+ branch: ${{ github.sha }}
public_path: https://${{ github.repository_owner }}.github.io/${{ github.repository_name }}
secrets: inherit
@@ -42,12 +42,12 @@ jobs:
- name: Deploy
uses: JamesIves/github-pages-deploy-action@3.6.2
with:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- BRANCH: gh-pages
- FOLDER: out
+ token: ${{ secrets.GITHUB_TOKEN }}
+ branch: gh-pages
+ folder: out
CLEAN: true
- TARGET_FOLDER: .
- GIT_CONFIG_NAME: ${{ github.actor }}
- GIT_CONFIG_EMAIL: ${{ github.actor }}@users.noreply.github.com
- COMMIT_MESSAGE: Deploy from commit ${{ github.sha }}
+ target-folder: .
+ git-config-name: ${{ github.actor }}
+ git-config-email: ${{ github.actor }}@users.noreply.github.com
+ commit-message: Deploy from commit ${{ github.sha }}
diff --git a/.husky/pre-commit b/.husky/pre-commit
index d24fdfc..cf0c46b 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
-npx lint-staged
+npx --no-install lint-staged
diff --git a/app/contact/page.tsx b/app/contact/page.tsx
index d37bc5d..bafd748 100644
--- a/app/contact/page.tsx
+++ b/app/contact/page.tsx
@@ -5,6 +5,7 @@ import TransitionDuration from '@/lib/framerMotion/TransitionDuration';
import getTextSlideIntoViewVarinats from '@/lib/framerMotion/variants/getTextSlideIntoViewVarinats';
import FormWrapper from '@/components/pages/Contact/FormWrapper/FormWrapper';
import metadataBase from '../metadata';
+import {env} from '../env.mjs';
export const metadata: Metadata = {
title: 'Contact',
@@ -16,7 +17,7 @@ export const metadata: Metadata = {
};
const titleVariants = getTextSlideIntoViewVarinats('right');
-
+const {FORMSPREE_ID} = env;
const containerCn = clsx('flex', 'flex-col', 'min-h-screen', 'w-full');
const titleCn = clsx('text-8xl', 'mt-24', 'ml-4', 'text-center');
@@ -33,7 +34,7 @@ export default function Contact() {
>
CONTACT US
-
+
);
}
diff --git a/app/env.mjs b/app/env.mjs
index be97ead..d53d70b 100644
--- a/app/env.mjs
+++ b/app/env.mjs
@@ -1,36 +1,44 @@
import {createEnv} from '@t3-oss/env-nextjs';
import {z} from 'zod';
+/**
+ * Checks if a string is not empty.
+ */
const notEmptyString = z.string().min(1);
-const number = z
- .string()
- .refine(v => !Number.isNaN(Number(v)))
+/**
+ * Checks if a value is a number.
+ */
+const number = notEmptyString
+ .refine(v => {
+ return !isNaN(Number(v)) || !isNaN(Number(v.replace(/"'/g, '')));
+ })
.transform(Number);
+/**
+ * Checks if a value is a boolean.
+ */
const boolean = z
.string()
.refine(v => v === 'true' || v === 'false' || v === '')
.transform(value => value === 'true');
-const server = {};
-
-const client = {
- NEXT_PUBLIC_GA_TRACKING_ID: notEmptyString,
- NEXT_PUBLIC_FORMSPREE_ID: notEmptyString,
- NEXT_PUBLIC_DISABLE_GA_IN_DEV_MODE: boolean,
- NEXT_PUBLIC_INSTAGRAM_ADDRESS: notEmptyString.url(),
- NEXT_PUBLIC_FACEBOOK_ADDRESS: notEmptyString.url(),
- NEXT_PUBLIC_WHATSAPP_ADDRESS: notEmptyString.url(),
- NEXT_PUBLIC_TELEGRAM_ADDRESS: notEmptyString.url(),
- NEXT_PUBLIC_CONTACT_EMAIL: notEmptyString.email(),
- NEXT_PUBLIC_CONTACT_PHONE: notEmptyString,
- NEXT_PUBLIC_LOCATION_GOOGLE_MAPS_LINK: notEmptyString.url(),
- NEXT_PUBLIC_LOCATION_ADDRESS_TEXT: notEmptyString,
- NEXT_PUBLIC_GA_TRACKING_ID: notEmptyString,
- NEXT_PUBLIC_HOTJAR_ID: notEmptyString,
- NEXT_PUBLIC_HOTJAR_SNIPPET_VERSION: number,
- NEXT_PUBLIC_FORMSPREE_ID: notEmptyString,
+const server = {
+ INSTAGRAM_ADDRESS: notEmptyString.url(),
+ FACEBOOK_ADDRESS: notEmptyString.url(),
+ WHATSAPP_ADDRESS: notEmptyString.url(),
+ TELEGRAM_ADDRESS: notEmptyString.url(),
+ DISABLE_GA_IN_DEV_MODE: boolean,
+ GA_TRACKING_ID: notEmptyString,
+ HOTJAR_ID: notEmptyString,
+ HOTJAR_SNIPPET_VERSION: number,
+ FORMSPREE_ID: notEmptyString,
+ CONTACT_EMAIL: notEmptyString.email(),
+ CONTACT_PHONE: notEmptyString,
+ LOCATION_GOOGLE_MAPS_LINK: notEmptyString.url(),
+ LOCATION_ADDRESS_TEXT: notEmptyString,
};
+const client = {};
+
const shared = {
NODE_ENV: z.enum(['development', 'production']),
};
@@ -39,20 +47,20 @@ export const env = createEnv({
server,
client,
shared,
- experimental__runtimeEnv: {
+ runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
- NEXT_PUBLIC_GA_TRACKING_ID: process.env.NEXT_PUBLIC_GA_TRACKING_ID,
- NEXT_PUBLIC_FORMSPREE_ID: process.env.NEXT_PUBLIC_FORMSPREE_ID,
- NEXT_PUBLIC_DISABLE_GA_IN_DEV_MODE: process.env.NEXT_PUBLIC_DISABLE_GA_IN_DEV_MODE,
- NEXT_PUBLIC_INSTAGRAM_ADDRESS: process.env.NEXT_PUBLIC_INSTAGRAM_ADDRESS,
- NEXT_PUBLIC_FACEBOOK_ADDRESS: process.env.NEXT_PUBLIC_FACEBOOK_ADDRESS,
- NEXT_PUBLIC_WHATSAPP_ADDRESS: process.env.NEXT_PUBLIC_WHATSAPP_ADDRESS,
- NEXT_PUBLIC_TELEGRAM_ADDRESS: process.env.NEXT_PUBLIC_TELEGRAM_ADDRESS,
- NEXT_PUBLIC_CONTACT_EMAIL: process.env.NEXT_PUBLIC_CONTACT_EMAIL,
- NEXT_PUBLIC_CONTACT_PHONE: process.env.NEXT_PUBLIC_CONTACT_PHONE,
- NEXT_PUBLIC_LOCATION_GOOGLE_MAPS_LINK: process.env.NEXT_PUBLIC_LOCATION_GOOGLE_MAPS_LINK,
- NEXT_PUBLIC_LOCATION_ADDRESS_TEXT: process.env.NEXT_PUBLIC_LOCATION_ADDRESS_TEXT,
- NEXT_PUBLIC_HOTJAR_SNIPPET_VERSION: process.env.NEXT_PUBLIC_HOTJAR_SNIPPET_VERSION,
- NEXT_PUBLIC_HOTJAR_ID: process.env.NEXT_PUBLIC_HOTJAR_ID,
+ INSTAGRAM_ADDRESS: process.env.INSTAGRAM_ADDRESS,
+ FACEBOOK_ADDRESS: process.env.FACEBOOK_ADDRESS,
+ WHATSAPP_ADDRESS: process.env.WHATSAPP_ADDRESS,
+ TELEGRAM_ADDRESS: process.env.TELEGRAM_ADDRESS,
+ DISABLE_GA_IN_DEV_MODE: process.env.DISABLE_GA_IN_DEV_MODE,
+ GA_TRACKING_ID: process.env.GA_TRACKING_ID,
+ HOTJAR_ID: process.env.HOTJAR_ID,
+ HOTJAR_SNIPPET_VERSION: process.env.HOTJAR_SNIPPET_VERSION,
+ FORMSPREE_ID: process.env.FORMSPREE_ID,
+ CONTACT_EMAIL: process.env.CONTACT_EMAIL,
+ CONTACT_PHONE: process.env.CONTACT_PHONE,
+ LOCATION_GOOGLE_MAPS_LINK: process.env.LOCATION_GOOGLE_MAPS_LINK,
+ LOCATION_ADDRESS_TEXT: process.env.LOCATION_ADDRESS_TEXT,
},
});
diff --git a/app/error.tsx b/app/error.tsx
index c68d3e9..01ae891 100644
--- a/app/error.tsx
+++ b/app/error.tsx
@@ -1,29 +1,19 @@
'use client';
-import {useEffect} from 'react';
+import clsx from 'clsx';
+import Button from '@/components/shared/Button/Button';
+
+const errContainerCn = clsx('pt-64', 'flex', 'flex-col', 'gap-8');
/**
* @param {{error: Error; reset: () => void}} props Props.
* @returns React component.
*/
-export default function Error({error, reset}: {error: Error; reset: () => void}) {
- useEffect(() => {
- // Log the error to an error reporting service
- // eslint-disable-next-line no-console
- console.error(error);
- }, [error]);
-
+export default function Error({reset}: {error: Error; reset: () => void}) {
return (
-
+
Something went wrong!
-
+
);
}
diff --git a/app/gallery/page.tsx b/app/gallery/page.tsx
index 402bba3..dcff9b4 100644
--- a/app/gallery/page.tsx
+++ b/app/gallery/page.tsx
@@ -5,6 +5,7 @@ import AppearInViewport from '@/components/shared/AppearInViewport/AppearInViewp
import SocialIcons from '@/components/shared/SocialIcons/SocialIcons';
import TransitionDuration from '@/lib/framerMotion/TransitionDuration';
import metadataBase from '../metadata';
+import getSocialicons from '../socialIcons';
import './styles.css';
export const metadata: Metadata = {
@@ -68,7 +69,7 @@ export default function GalleryPage() {
>
Want more? Follow us on social media!
-
+
);
diff --git a/app/layout.tsx b/app/layout.tsx
index 952ce6f..12867c2 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,12 +1,19 @@
import clsx from 'clsx';
import '@/lib/fontawesome/configure';
import Footer from '@/components/shared/Footer/Footer';
-import WithGtag from '@/lib/gtag/WithGtag';
+import WithGtagScript from '@/lib/gtag/WithGtagScript';
import CustomCursor from '@/lib/customCursor/CustomCursor';
import WebVitals from '@/components/shared/WebVitals/WebVitals';
+import WithHotjarScript from '@/lib/hotjar/WithHotjarScript';
+import {LocationChangeTracker} from '@/lib/gtag/LocationChangeTracker';
+import TopMenu from '@/components/shared/TopMenu/TopMenu';
+import ScrollToTopButton from '@/components/shared/ScrollToTop/ScrollToTop';
+import MotoinProvider from '@/lib/framerMotion/MotionProvider';
import meta from './metadata';
import viewportData from './viewport';
import {kumbhSans, robotoMono} from './fonts';
+import {env} from './env.mjs';
+import {menuItems, socialLinks} from './topMenuConfig';
import './styles.css';
export const metadata = meta;
@@ -33,14 +40,33 @@ const mainCn = clsx('flex', 'flex-col', 'items-center', 'justify-start', 'grow')
* @returns Global layout.
*/
export default function RootLayout({children}: {children: React.ReactNode}) {
+ const {GA_TRACKING_ID, DISABLE_GA_IN_DEV_MODE} = env;
return (
-
+
+
-
- {children}
+
+
+
+
+ {children}
+
+
+ {!DISABLE_GA_IN_DEV_MODE && (
+ <>
+
+
+ >
+ )}
);
diff --git a/app/page.tsx b/app/page.tsx
index 732fbc2..0233878 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -3,9 +3,10 @@ import AppearInViewport from '@/components/shared/AppearInViewport/AppearInViewp
import MainPageVideoPlayer from '@/components/pages/MainPageVideoPlayer/MainPageVideoPlayer';
import CATLink from '@/components/shared/CATLink/CATLink';
import SocialIcons from '@/components/shared/SocialIcons/SocialIcons';
+import getSocialicons from './socialIcons';
const containerCn = clsx('relative', 'z-10', 'flex', 'flex-col', 'items-center', 'grow');
-const titleCn = clsx('font-light', 'text-8xl', 'mt-24');
+const titleCn = clsx('font-light', 'text-8xl', 'mt-24', 'text-center');
const subtitleCn = clsx('text-1.5xl', 'font-light', 'tracking-widest', 'text-center');
const titleContainerCn = clsx('font-sans', 'select-none');
const exploreCn = clsx('mt-auto');
@@ -31,9 +32,9 @@ export default function Home() {
/>
>
diff --git a/app/sitemap.ts b/app/sitemap.ts
index 80473a2..a9a0c16 100644
--- a/app/sitemap.ts
+++ b/app/sitemap.ts
@@ -1,30 +1,25 @@
+import fs from 'fs';
+import {default as pathModule} from 'node:path';
import {MetadataRoute} from 'next';
import {origin} from './origin';
+// eslint-disable-next-line jsdoc/require-jsdoc
+function getDirectories(path: string) {
+ return fs.readdirSync(path).filter(function (file) {
+ return fs.statSync(pathModule.join(path, file)).isDirectory();
+ });
+}
+
+const pageFolders = getDirectories(process.cwd() + '/app');
+
/**
* @returns Sitemap template.
*/
export default function sitemap(): MetadataRoute.Sitemap {
- return [
- {
- url: origin,
- lastModified: new Date(),
- },
- {
- url: `${origin}/about`,
- lastModified: new Date(),
- },
- {
- url: `${origin}/gallery`,
- lastModified: new Date(),
- },
- {
- url: `${origin}/contact`,
- lastModified: new Date(),
- },
- {
- url: `${origin}/teachers`,
+ return pageFolders.map(folder => {
+ return {
+ url: `${origin}/${folder}`,
lastModified: new Date(),
- },
- ];
+ };
+ });
}
diff --git a/app/socialIcons.tsx b/app/socialIcons.tsx
new file mode 100644
index 0000000..d6ffe92
--- /dev/null
+++ b/app/socialIcons.tsx
@@ -0,0 +1,82 @@
+import {faFacebook, faInstagram, faTelegram, faWhatsapp} from '@fortawesome/free-brands-svg-icons';
+import {FontAwesomeIcon, FontAwesomeIconProps} from '@fortawesome/react-fontawesome';
+import clsx from 'clsx';
+import {ReactNode} from 'react';
+import {env} from '@/app/env.mjs';
+import UniversalLink from '@/components/shared/UniversalLink/UniversalLink';
+
+/**
+ * Icon configuration.
+ */
+type IconConfig = {
+ icon: ReactNode;
+};
+
+const {FACEBOOK_ADDRESS, INSTAGRAM_ADDRESS, TELEGRAM_ADDRESS, WHATSAPP_ADDRESS} = env;
+
+const iconCn = clsx('transition-transform hover:-translate-y-0.5 hover:scale-105');
+
+/**
+ * @param size Size.
+ * @returns Icons config.
+ */
+const getSocialicons = (size?: FontAwesomeIconProps['size']): IconConfig[] => [
+ {
+ icon: (
+
+
+
+ ),
+ },
+ {
+ icon: (
+
+
+
+ ),
+ },
+ {
+ icon: (
+
+
+
+ ),
+ },
+ {
+ icon: (
+
+
+
+ ),
+ },
+];
+
+export default getSocialicons;
diff --git a/app/template.tsx b/app/template.tsx
index 007a65d..536981c 100644
--- a/app/template.tsx
+++ b/app/template.tsx
@@ -1,9 +1,7 @@
'use client';
import {LazyMotion} from 'framer-motion';
-import {LocationChangeTracker} from '@/lib/gtag/LocationChangeTracker';
import useDynamicFavicon from '@/lib/dynamicFavicon/useDynamicFavicon';
-import TopMenu from '@/components/shared/TopMenu/TopMenu';
import {loadFeatures} from '@/lib/framerMotion/loadFeatures';
import ScrollToTopButton from '@/components/shared/ScrollToTop/ScrollToTop';
@@ -16,8 +14,6 @@ export default function Template({children}: {children: React.ReactNode}) {
return (
-
-
+ ABOUT
+
+ ),
+ },
+ {
+ icon: (
+
+ GALLERY
+
+ ),
+ bgImgPath: 'top-menu.1.jpg',
+ },
+ {
+ icon: (
+
+ TEACHERS
+
+ ),
+ bgImgPath: 'top-menu.2.jpg',
+ },
+ {
+ icon: (
+
+ CONTACT
+
+ ),
+ bgImgPath: 'top-menu.3.jpg',
+ },
+];
+
+const socialLinks = [
+ {
+ link: (
+
+ INSTAGRAM
+
+ ),
+ },
+ {
+ link: (
+
+ TELEGRAM
+
+ ),
+ },
+ {
+ link: (
+
+ FACEBOOK
+
+ ),
+ },
+ {
+ link: (
+
+ WHATSAPP
+
+ ),
+ },
+];
+
+export {menuItems, socialLinks};
diff --git a/components/pages/About/WhyJoinBlock/WhyJoinBlock.tsx b/components/pages/About/WhyJoinBlock/WhyJoinBlock.tsx
index df6efa9..c5aa0ea 100644
--- a/components/pages/About/WhyJoinBlock/WhyJoinBlock.tsx
+++ b/components/pages/About/WhyJoinBlock/WhyJoinBlock.tsx
@@ -37,9 +37,9 @@ export default function WhyJoinBlock() {
>
Why join Salsaviva?
- {config.map((Item, index) => (
+ {config.map((Item, idx) => (
diff --git a/components/pages/Contact/DynamicFormBg/DynamicFormBg.tsx b/components/pages/Contact/DynamicFormBg/DynamicFormBg.tsx
index d366b79..8ea093d 100644
--- a/components/pages/Contact/DynamicFormBg/DynamicFormBg.tsx
+++ b/components/pages/Contact/DynamicFormBg/DynamicFormBg.tsx
@@ -11,6 +11,9 @@ type DynamicFormBgProps = {
children: ReactNode;
};
+const maxSteps = 3;
+const minSteps = 0;
+
/**
* @param {DynamicFormBgProps} props Props.
* @returns {React.JSX.Element} Background for the contact form.
@@ -19,10 +22,9 @@ const DynamicFormBg = ({children}: DynamicFormBgProps) => {
const containerCn = 'relative';
const bgTextCn = clsx(
'absolute left-0 top-0 bg-gradient-to-r text-[500px] font-extrabold opacity-40',
- 'from-accent0 to-accent3 bg-clip-text text-transparent',
+ 'select-none from-accent0 to-accent3 bg-clip-text text-transparent',
);
- const maxSteps = 3;
- const minSteps = 0;
+
const [steps, setSteps] = useState(0);
// eslint-disable-next-line jsdoc/require-jsdoc
const stepsSetter = useCallback(
diff --git a/components/pages/Contact/Form/Form.tsx b/components/pages/Contact/Form/Form.tsx
index 3f76963..d6fd2ac 100644
--- a/components/pages/Contact/Form/Form.tsx
+++ b/components/pages/Contact/Form/Form.tsx
@@ -1,10 +1,8 @@
'use client';
import {FormikContext, useFormik} from 'formik';
+import {m} from 'framer-motion';
import Button from '@/components/shared/Button/Button';
-import AppearInViewport from '@/components/shared/AppearInViewport/AppearInViewport';
-import useFormspree from '@/lib/formspree/useFormspree';
-import {env} from '@/app/env.mjs';
import validationSchema, {ContactFormData} from './validationSchema';
import FormField from './FormField';
@@ -12,8 +10,7 @@ import FormField from './FormField';
* Props.
*/
type FormProps = {
- onSubmit?: () => void;
- onErrorSubmit?: () => void;
+ onSubmit: (v: ContactFormData) => Promise;
};
const initialValues = {
@@ -21,54 +18,44 @@ const initialValues = {
message: '',
} satisfies ContactFormData;
-const {NEXT_PUBLIC_FORMSPREE_ID} = env;
-
/**
* @param {FormProps} props Props.
* @returns React element.
*/
-export default function Form({onSubmit: onSuccessSubmit, onErrorSubmit}: FormProps) {
+export default function Form({onSubmit}: FormProps) {
const mottoCn = 'text-center text-gray-300 mt-16';
const btnCn = 'mt-8';
- const [{status}, submit] = useFormspree(NEXT_PUBLIC_FORMSPREE_ID!);
- // eslint-disable-next-line jsdoc/require-jsdoc
- const onSubmit = async (values: ContactFormData) => {
- try {
- await submit(values);
- resetForm();
- onSuccessSubmit?.();
- } catch {
- onErrorSubmit?.();
- }
- };
const formikData = useFormik({
initialValues,
onSubmit,
validationSchema,
});
- const {handleSubmit, isSubmitting, isValid, dirty, resetForm} = formikData;
+ const {handleSubmit, isSubmitting, isValid, dirty} = formikData;
return (
-
-
-
+
+
+
+ Select your preferred method of contact:
+
+
+
+
+
);
}
diff --git a/components/pages/Contact/Form/FormField.tsx b/components/pages/Contact/Form/FormField.tsx
index ffc6196..2b108f0 100644
--- a/components/pages/Contact/Form/FormField.tsx
+++ b/components/pages/Contact/Form/FormField.tsx
@@ -1,5 +1,6 @@
import {useField} from 'formik';
import clsx from 'clsx';
+import {m} from 'framer-motion';
import formConfig from './formConfig';
import type {ContactFormData} from './validationSchema';
import TextField from '../TextField/TextField';
@@ -12,6 +13,23 @@ type FormFieldProps = {
name: keyof ContactFormData;
};
+const variants = {
+ visible: {
+ y: 0,
+ opacity: 1,
+ transition: {
+ y: {stiffness: 1000, velocity: -100},
+ },
+ },
+ hidden: {
+ y: 50,
+ opacity: 0,
+ transition: {
+ y: {stiffness: 1000},
+ },
+ },
+};
+
/**
* @param {FormFieldProps} props Props.
* @returns React element.
@@ -20,10 +38,13 @@ export default function FormField({name}: FormFieldProps) {
const configData = formConfig[name];
const [field, meta] = useField(name);
const hasError = meta.touched && !!meta.error;
- const fieldCn = clsx('relative', 'h-12');
+ const fieldCn = clsx('relative', 'h-16');
return (
-
+
-
+
);
}
diff --git a/components/pages/Contact/FormWrapper/FormWrapper.tsx b/components/pages/Contact/FormWrapper/FormWrapper.tsx
index 5f9208d..9cec5cc 100644
--- a/components/pages/Contact/FormWrapper/FormWrapper.tsx
+++ b/components/pages/Contact/FormWrapper/FormWrapper.tsx
@@ -2,18 +2,40 @@
import {useState} from 'react';
import clsx from 'clsx';
+import useFormspree from '@/lib/formspree/useFormspree';
import Form from '../Form/Form';
import SubmittedForm from './SubmittedForm';
import ErrorSubmitForm from './ErrorSubmitForm';
import DynamicFormBg from '../DynamicFormBg/DynamicFormBg';
+import {ContactFormData} from '../Form/validationSchema';
/**
+ * Form wrapper component props.
+ */
+type FormWrapperProps = {
+ formspreeId: string;
+};
+
+/**
+ * @param {FormWrapperProps} props Props.
* @returns React element.
*/
-export default function FormWrapper() {
+export default function FormWrapper({formspreeId}: FormWrapperProps) {
const [isSubmitted, setIsSubmitted] = useState(false);
const [isError, setIsError] = useState(false);
const wrapperCn = clsx('w-96', 'sm:w-100', 'mx-auto', 'mt-8', 'z-10');
+ const [_, submit] = useFormspree
(formspreeId);
+
+ // eslint-disable-next-line jsdoc/require-jsdoc
+ const onSubmit = async (values: ContactFormData) => {
+ try {
+ setIsError(false);
+ await submit(values);
+ setIsSubmitted(true);
+ } catch {
+ setIsError(true);
+ }
+ };
// eslint-disable-next-line jsdoc/require-jsdoc
const render = () => {
@@ -25,14 +47,11 @@ export default function FormWrapper() {
return ;
}
- return (
- ;
};
+ // here animation on swich screen should occur
+
return (
{render()}
diff --git a/components/pages/Gallery/GalleryImage.tsx b/components/pages/Gallery/GalleryImage.tsx
index 83bdec1..85ffea1 100644
--- a/components/pages/Gallery/GalleryImage.tsx
+++ b/components/pages/Gallery/GalleryImage.tsx
@@ -1,6 +1,5 @@
-import {MouseEvent} from 'react';
+import {MouseEvent, forwardRef} from 'react';
import clsx from 'clsx';
-import {forwardRef} from 'react';
import {useHotkeys} from 'react-hotkeys-hook';
import {customCursorClickableClass} from '@/lib/customCursor/customCursorClickableClass';
import useDisableRightClick from '@/lib/shared/useDisableRightClick';
diff --git a/components/pages/Teachers/Teachers.tsx b/components/pages/Teachers/Teachers.tsx
index 3e65095..cf14f72 100644
--- a/components/pages/Teachers/Teachers.tsx
+++ b/components/pages/Teachers/Teachers.tsx
@@ -18,7 +18,7 @@ export default function Teachers() {
>
{teachersListConfig.map(teacher => (
))}
diff --git a/components/pages/Teachers/teachersListConfig.ts b/components/pages/Teachers/teachersListConfig.ts
index 1198581..0dffae2 100644
--- a/components/pages/Teachers/teachersListConfig.ts
+++ b/components/pages/Teachers/teachersListConfig.ts
@@ -6,7 +6,6 @@ export type TeachersListConfigItem = {
subtitle: string;
danceStyles: string[];
imgSrc: string;
- id: string;
};
const teachersListConfig: TeachersListConfigItem[] = [
@@ -15,35 +14,30 @@ const teachersListConfig: TeachersListConfigItem[] = [
subtitle: 'Founder - Top teacher',
danceStyles: ['Salsa: Mambo, On1, Casino', 'Bachata', 'Kizomba'],
imgSrc: 'teachers.0.jpg',
- id: 'monica-conde',
},
{
name: 'Anna Militonyan',
subtitle: 'Top teacher',
danceStyles: ['Bachata', 'Kizomba'],
imgSrc: 'teachers.2.jpg',
- id: 'anna-militonyan',
},
{
name: 'Annie Ghantarjian',
subtitle: 'Top teacher',
danceStyles: ['Lady style', 'Kizomba', 'Bachata', 'Salsa/mambo'],
imgSrc: 'teachers.3.jpg',
- id: 'annie-ghantarjian',
},
{
name: 'Yuriy Pikhun',
subtitle: 'Top teacher',
danceStyles: ['Bachata', 'Kizomba', 'Casino'],
imgSrc: 'teachers.1.jpg',
- id: 'yuriy-pikhun',
},
{
name: 'Dima & Sveta',
subtitle: 'Top teachers',
danceStyles: ['Brazilian Zouk'],
imgSrc: 'teachers.4.jpg',
- id: 'dima-sveta',
},
];
diff --git a/components/shared/Footer/Footer.tsx b/components/shared/Footer/Footer.tsx
index d76b199..70d61f9 100644
--- a/components/shared/Footer/Footer.tsx
+++ b/components/shared/Footer/Footer.tsx
@@ -3,7 +3,7 @@ import Mail from './Mail';
import Phone from './Phone';
import Location from './Location';
-const footerCn = clsx('grid', 'sm:grid-cols-1', 'lg:grid-cols-3', 'p-24');
+const footerCn = clsx('grid', 'sm:grid-cols-1', 'lg:grid-cols-3', 'p-24', 'text-lg');
/**
* @returns React component.
diff --git a/components/shared/Footer/Location.tsx b/components/shared/Footer/Location.tsx
index 8fa69b9..cd86c88 100644
--- a/components/shared/Footer/Location.tsx
+++ b/components/shared/Footer/Location.tsx
@@ -3,7 +3,7 @@ import {memo} from 'react';
import {env} from '@/app/env.mjs';
import UniversalLink from '../UniversalLink/UniversalLink';
-const {NEXT_PUBLIC_LOCATION_ADDRESS_TEXT, NEXT_PUBLIC_LOCATION_GOOGLE_MAPS_LINK} = env;
+const {LOCATION_ADDRESS_TEXT, LOCATION_GOOGLE_MAPS_LINK} = env;
const containerCn = clsx('flex', 'justify-center', 'flex-nowrap', 'gap-4');
const addressCn = clsx('not-italic');
@@ -16,11 +16,11 @@ function Location() {
return (
- {NEXT_PUBLIC_LOCATION_ADDRESS_TEXT}
+ {LOCATION_ADDRESS_TEXT}
);
diff --git a/components/shared/Footer/Mail.tsx b/components/shared/Footer/Mail.tsx
index 5abbc76..e998f1a 100644
--- a/components/shared/Footer/Mail.tsx
+++ b/components/shared/Footer/Mail.tsx
@@ -3,7 +3,7 @@ import {memo} from 'react';
import {env} from '@/app/env.mjs';
import UniversalLink from '../UniversalLink/UniversalLink';
-const {NEXT_PUBLIC_CONTACT_EMAIL} = env;
+const {CONTACT_EMAIL} = env;
const containerCn = clsx('flex', 'justify-center');
const anchorCn = clsx('animated-link');
@@ -16,9 +16,9 @@ function Mail() {
- {NEXT_PUBLIC_CONTACT_EMAIL}
+ {CONTACT_EMAIL}
);
diff --git a/components/shared/Footer/Phone.tsx b/components/shared/Footer/Phone.tsx
index 593fbee..1aaa270 100644
--- a/components/shared/Footer/Phone.tsx
+++ b/components/shared/Footer/Phone.tsx
@@ -2,7 +2,7 @@ import clsx from 'clsx';
import {memo} from 'react';
import {env} from '@/app/env.mjs';
-const {NEXT_PUBLIC_CONTACT_PHONE} = env;
+const {CONTACT_PHONE} = env;
const containerCn = clsx('flex', 'justify-center');
const telCn = clsx('animated-link', 'tracking-wider');
@@ -15,9 +15,9 @@ function Phone() {
);
diff --git a/components/shared/Menu/MenuDynamicBg.tsx b/components/shared/Menu/MenuDynamicBg.tsx
index 6aa0fe4..ab71c15 100644
--- a/components/shared/Menu/MenuDynamicBg.tsx
+++ b/components/shared/Menu/MenuDynamicBg.tsx
@@ -1,6 +1,6 @@
import clsx from 'clsx';
import {m, AnimatePresence} from 'framer-motion';
-import {useContextSafeSafe} from '@/utils/useContextSafe';
+import {useContextSafe} from '@/utils/useContextSafe';
import {MenuContext} from './MenuContext';
import ImageWrapper from '../ImageWrapper/ImageWrapper';
@@ -11,7 +11,7 @@ const dynamicBgCn = clsx('absolute', 'top-0', 'left-0', 'w-full', 'h-full', 'z-3
* @returns React component.
*/
export default function MenuDynamicBg() {
- const {menuBg} = useContextSafeSafe(MenuContext);
+ const {menuBg} = useContextSafe(MenuContext);
return (
diff --git a/components/shared/Menu/MenuItem.tsx b/components/shared/Menu/MenuItem.tsx
index 0593193..0198287 100644
--- a/components/shared/Menu/MenuItem.tsx
+++ b/components/shared/Menu/MenuItem.tsx
@@ -4,7 +4,7 @@ import clsx from 'clsx';
import {twMerge} from 'tailwind-merge';
import {useHover} from '@/lib/shared/useHover';
import {useAssignRefs} from '@/lib/shared/useAssignRefs';
-import {useContextSafeSafe} from '@/utils/useContextSafe';
+import {useContextSafe} from '@/utils/useContextSafe';
import {MenuContext} from './MenuContext';
/**
@@ -43,7 +43,7 @@ const MenuItem = forwardRef(function MenuItem(
{children, className, bgImgPath},
forwardedRef,
) {
- const {setIsOpen, setMenuBg} = useContextSafeSafe(MenuContext);
+ const {setIsOpen, setMenuBg} = useContextSafe(MenuContext);
const [r, isHover] = useHover();
const ref = useAssignRefs(r, forwardedRef);
diff --git a/components/shared/Menu/MenuList.tsx b/components/shared/Menu/MenuList.tsx
index 6942178..67a5697 100644
--- a/components/shared/Menu/MenuList.tsx
+++ b/components/shared/Menu/MenuList.tsx
@@ -5,7 +5,7 @@ import {m} from 'framer-motion';
import {useHotkeys} from 'react-hotkeys-hook';
import clsx from 'clsx';
import useWindowDimensions from '@/lib/shared/useWindowDimensions';
-import {useContextSafeSafe} from '@/utils/useContextSafe';
+import {useContextSafe} from '@/utils/useContextSafe';
import {MenuContext} from './MenuContext';
import {MenuPosition} from './MenuPosition';
import menuButtonSize from './menuButtonSize';
@@ -60,7 +60,7 @@ const navCn = clsx(
* @returns React component.
*/
export default function MenuList({children}: MenuListProps) {
- const {isOpen, setIsOpen, position} = useContextSafeSafe(MenuContext);
+ const {isOpen, setIsOpen, position} = useContextSafe(MenuContext);
const close = useCallback(() => setIsOpen(false), [setIsOpen]);
const {height, width} = useWindowDimensions();
const bodyHasOverflow = height ? height < document.body.clientHeight : undefined;
diff --git a/components/shared/Menu/MenuToggle.tsx b/components/shared/Menu/MenuToggle.tsx
index 4652b54..466ac45 100644
--- a/components/shared/Menu/MenuToggle.tsx
+++ b/components/shared/Menu/MenuToggle.tsx
@@ -1,6 +1,6 @@
import {forwardRef, memo} from 'react';
import clsx from 'clsx';
-import {useContextSafeSafe} from '@/utils/useContextSafe';
+import {useContextSafe} from '@/utils/useContextSafe';
import {MenuContext} from './MenuContext';
import menuButtonSize from './menuButtonSize';
import Button from '../Button/Button';
@@ -26,7 +26,7 @@ const MenuToggle = forwardRef(function MenuT
{onToggle},
ref,
) {
- const {setIsOpen, isOpen, position} = useContextSafeSafe(MenuContext);
+ const {setIsOpen, isOpen, position} = useContextSafe(MenuContext);
// eslint-disable-next-line jsdoc/require-jsdoc
const onClick = () => {
setIsOpen(!isOpen);
diff --git a/components/shared/SocialIcons/SocialIcons.tsx b/components/shared/SocialIcons/SocialIcons.tsx
index 6ff5c90..ada3a9f 100644
--- a/components/shared/SocialIcons/SocialIcons.tsx
+++ b/components/shared/SocialIcons/SocialIcons.tsx
@@ -1,13 +1,10 @@
'use client';
-import {FontAwesomeIcon, FontAwesomeIconProps} from '@fortawesome/react-fontawesome';
import clsx from 'clsx';
import {twMerge} from 'tailwind-merge';
-import {memo} from 'react';
+import {ReactNode, memo} from 'react';
import {m} from 'framer-motion';
-import iconsConfig from './iconsConfig';
import AppearInViewport from '../AppearInViewport/AppearInViewport';
-import UniversalLink from '../UniversalLink/UniversalLink';
/**
* Direction.
@@ -18,10 +15,12 @@ type Direction = 'row' | 'column';
* Props.
*/
type SocialIconsProps = {
- iconSize: FontAwesomeIconProps['size'];
/** @default row */
direction?: Direction;
className?: string;
+ icons: Array<{
+ icon: ReactNode;
+ }>;
};
// eslint-disable-next-line jsdoc/require-jsdoc
@@ -34,7 +33,6 @@ const iconsCn = (direction: SocialIconsProps['direction']) =>
'select-none',
'z-10',
);
-const iconCn = clsx('transition-transform hover:-translate-y-0.5 hover:scale-105');
const variants = {
// eslint-disable-next-line jsdoc/require-jsdoc
@@ -59,28 +57,19 @@ const variants = {
* @param {SocialIconsProps} props Props.
* @returns React component.
*/
-function SocialIcons({iconSize, direction = 'row', className}: SocialIconsProps) {
+function SocialIcons({direction = 'row', className, icons}: SocialIconsProps) {
return (
- {iconsConfig.map(({href, target, className, ...rest}) => (
+ {icons.map(({icon}, idx) => (
-
-
-
+ {icon}
))}
diff --git a/components/shared/SocialIcons/iconsConfig.ts b/components/shared/SocialIcons/iconsConfig.ts
deleted file mode 100644
index 089e662..0000000
--- a/components/shared/SocialIcons/iconsConfig.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import {faFacebook, faInstagram, faTelegram, faWhatsapp} from '@fortawesome/free-brands-svg-icons';
-import {FontAwesomeIconProps} from '@fortawesome/react-fontawesome';
-import {env} from '@/app/env.mjs';
-
-/**
- * Icon configuration.
- */
-type IconConfig = {
- href: string;
-} & FontAwesomeIconProps;
-
-const {
- NEXT_PUBLIC_FACEBOOK_ADDRESS,
- NEXT_PUBLIC_INSTAGRAM_ADDRESS,
- NEXT_PUBLIC_TELEGRAM_ADDRESS,
- NEXT_PUBLIC_WHATSAPP_ADDRESS,
-} = env;
-
-/**
- * Social icons config.
- */
-const iconsConfig: IconConfig[] = [
- {
- icon: faInstagram,
- href: NEXT_PUBLIC_INSTAGRAM_ADDRESS ?? '',
- target: '_blank',
- },
- {
- icon: faTelegram,
- href: NEXT_PUBLIC_TELEGRAM_ADDRESS ?? '',
- target: '_blank',
- },
- {
- icon: faFacebook,
- href: NEXT_PUBLIC_FACEBOOK_ADDRESS ?? '',
- target: '_blank',
- },
- {
- icon: faWhatsapp,
- href: NEXT_PUBLIC_WHATSAPP_ADDRESS ?? '',
- target: '_blank',
- },
-];
-
-export default iconsConfig;
diff --git a/components/shared/TopMenu/TopMenu.tsx b/components/shared/TopMenu/TopMenu.tsx
index 964a76c..aa562b0 100644
--- a/components/shared/TopMenu/TopMenu.tsx
+++ b/components/shared/TopMenu/TopMenu.tsx
@@ -2,10 +2,23 @@
import clsx from 'clsx';
import {m} from 'framer-motion';
-import {menuItemsConfig, socialLinksConfig} from './topMenuCongif';
+import {ReactNode} from 'react';
import Menu from '../Menu/Menu';
import UniversalLink from '../UniversalLink/UniversalLink';
+/**
+ * TopMenu component props.
+ */
+type TopMenuProps = {
+ menuItems: Array<{
+ icon: ReactNode;
+ bgImgPath: string;
+ }>;
+ socialLinks: Array<{
+ link: ReactNode;
+ }>;
+};
+
const variants = {
open: {
transition: {staggerChildren: 0.07, delayChildren: 0.2},
@@ -15,16 +28,16 @@ const variants = {
},
};
-const linkCn = clsx('animated-link');
const socialLiksListCn = clsx('flex', 'gap-8', 'mt-auto', 'mb-16');
const socialLinkMenuItem = clsx('text-xs');
const itemsListCn = clsx('mt-24');
const homeMenuItem = clsx('mb-32', 'text-xl');
/**
+ * @param {TopMenuProps} props Props.
* @returns React component.
*/
-export default function TopMenu() {
+export default function TopMenu({menuItems, socialLinks}: TopMenuProps) {
return (