build improvements
This commit is contained in:
parent
4d239f7c7a
commit
5c3fe39074
@ -26,6 +26,11 @@ services:
|
||||
ports:
|
||||
- "4012:5432"
|
||||
|
||||
yogamail:
|
||||
image: dockage/mailcatcher:0.9.0
|
||||
ports:
|
||||
- "4013:1080"
|
||||
- "4014:1025"
|
||||
|
||||
volumes:
|
||||
next-db: {}
|
||||
|
||||
@ -1 +1 @@
|
||||
202501081712.9d7bb39
|
||||
202502042213.4d239f7
|
||||
|
||||
@ -9,4 +9,4 @@ echo "build image ${TAG} from folder ${PROJECT_ROOT}"
|
||||
|
||||
cd $PROJECT_ROOT
|
||||
|
||||
docker build --platform linux/amd64 -t $TAG .
|
||||
docker build -t $TAG .
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ENV_DEV_DIR=$(readlink -f "${CURRENT_DIR}/../../../environments/dev/docker-compose")
|
||||
COMPOSE_FILE=$(readlink -f "/docker-compose.yml")
|
||||
@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CURRENT_DIR=$(dirname "$0")
|
||||
source "${CURRENT_DIR}/start.docker.compose.env.sh"
|
||||
|
||||
cd "${ENV_DEV_DIR}"
|
||||
echo "starting compose file in ${ENV_DEV_DIR}"
|
||||
docker compose up -d
|
||||
@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CURRENT_DIR=$(dirname "$0")
|
||||
source "${CURRENT_DIR}/build.docker.env.sh"
|
||||
source "${CURRENT_DIR}/start.docker.compose.env.sh"
|
||||
echo "updating image version to ${VERSION} in compose file ${COMPOSE_FILE}"
|
||||
#export TAG=docker.rschneider.hu/infra/yogastic:$VERSION
|
||||
|
||||
case $(uname) in
|
||||
"Darwin")
|
||||
echo "Detected macOS"
|
||||
sed -i'' -e "s/docker.rschneider.hu\\/infra\\/yogastic:.*/docker.rschneider.hu\\/infra\\/yogastic:$VERSION/g" $COMPOSE_FILE
|
||||
;;
|
||||
*)
|
||||
sed -i "s/docker.rschneider.hu\\/infra\\/yogastic:.*/docker.rschneider.hu\\/infra\\/yogastic:$VERSION/g" $COMPOSE_FILE
|
||||
#sed -i "s/docker.rschneider.hu\\/infra\\/yogastic:.*/docker.rschneider.hu\\/infra\\/yogastic:$VERSION/g" $COMPOSE_FILE
|
||||
;;
|
||||
esac
|
||||
@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
CURRENT_DIR=$(dirname "$0")
|
||||
source "${CURRENT_DIR}/build.docker.env.sh"
|
||||
source "${CURRENT_DIR}/start.docker.compose.env.sh"
|
||||
|
||||
cd ${ENV_DEV_DIR}
|
||||
echo "Destroy compose in ${ENV_DEV_DIR}"
|
||||
docker compose down -v
|
||||
@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
CURRENT_DIR=$(dirname "$0")
|
||||
source "${CURRENT_DIR}/build.docker.env.sh"
|
||||
source "${CURRENT_DIR}/start.docker.compose.env.sh"
|
||||
|
||||
cd ${ENV_DEV_DIR}
|
||||
echo "Stopping compose in ${ENV_DEV_DIR}"
|
||||
docker compose down
|
||||
@ -1 +1 @@
|
||||
202501071722.a8b144f
|
||||
202502042213.4d239f7
|
||||
|
||||
49
environment/infra/jenkins/build.cms.image.Jenkinsfile
Normal file
49
environment/infra/jenkins/build.cms.image.Jenkinsfile
Normal file
@ -0,0 +1,49 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
DOCKER_IMAGE = 'yoga-cms'
|
||||
DOCKER_REGISTRY = 'your-docker-registry'
|
||||
DOCKER_CREDENTIALS_ID = 'your-docker-credentials-id'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout([$class: 'GitSCM', branches: [[name: '*/main']], userRemoteConfigs: [[url: 'https://gitea.rschneider.hu/rschneider/yogastic.git', credentialsId: 'rschneider_gitea.rschneider.hu']]])
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build Docker Image') {
|
||||
steps {
|
||||
script {
|
||||
// docker.build("${DOCKER_IMAGE}:${env.BUILD_ID}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Push Docker Image') {
|
||||
steps {
|
||||
script {
|
||||
// docker.withRegistry("https://${DOCKER_REGISTRY}", DOCKER_CREDENTIALS_ID) {
|
||||
// docker.image("${DOCKER_IMAGE}:${env.BUILD_ID}").push()
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Cleanup') {
|
||||
steps {
|
||||
script {
|
||||
docker.image("${DOCKER_IMAGE}:${env.BUILD_ID}").remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
cleanWs()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,4 +21,5 @@ echo $CMD1
|
||||
echo $CMD2
|
||||
${CMD1}
|
||||
${CMD2}
|
||||
|
||||
echo "done"
|
||||
|
||||
@ -10,7 +10,28 @@ const compat = new FlatCompat({
|
||||
});
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||
...compat.config(
|
||||
{
|
||||
extends:[
|
||||
"next/core-web-vitals",
|
||||
"next/typescript",
|
||||
],
|
||||
// rules: {
|
||||
// "@typescript-eslint/no-explicit-any": "off"
|
||||
// },
|
||||
}
|
||||
|
||||
),
|
||||
|
||||
// {
|
||||
// name: "custom",
|
||||
// "files": ["src/types/generated-strapi-interfaces/api/article.ts"], // Or *.test.js
|
||||
// rules: {
|
||||
// "@typescript-eslint/no-explicit-any": "off"
|
||||
// },
|
||||
// }
|
||||
];
|
||||
|
||||
console.info("eslint config",eslintConfig)
|
||||
|
||||
export default eslintConfig;
|
||||
|
||||
@ -3,6 +3,7 @@ import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: "standalone",
|
||||
|
||||
sassOptions: {
|
||||
silenceDeprecations: [
|
||||
'abs-percent',
|
||||
|
||||
1310
yoga-app/package-lock.json
generated
1310
yoga-app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@strapi/database": "^5.10.3",
|
||||
"@types/aos": "^3.0.7",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
@ -18,6 +19,9 @@
|
||||
"@types/ityped": "^1.0.3",
|
||||
"@types/pg": "^8.11.10",
|
||||
"@types/qs": "^6.9.18",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"ajv-i18n": "^4.2.0",
|
||||
"aos": "^2.3.4",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bootstrap": "^4.6.2",
|
||||
@ -25,16 +29,19 @@
|
||||
"dotenv": "^16.4.7",
|
||||
"ityped": "^1.0.3",
|
||||
"next": "15.1.3",
|
||||
"nodemailer": "^6.10.0",
|
||||
"pg": "^8.13.1",
|
||||
"qs": "^6.14.0",
|
||||
"react": "^19.0.0",
|
||||
"react-bootstrap": "^2.10.9",
|
||||
"react-dom": "^19.0.0"
|
||||
"react-dom": "^19.0.0",
|
||||
"validator": "^13.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/node": "^20",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
|
||||
65
yoga-app/src/actions/contactus-actions.ts
Normal file
65
yoga-app/src/actions/contactus-actions.ts
Normal file
@ -0,0 +1,65 @@
|
||||
'use server'
|
||||
|
||||
import {sendMail} from "@/actions/mail-actions";
|
||||
import {contactUsFormValidator} from "@/validation/validation";
|
||||
import {copyFormValues, copyValidationErrors, FormControl, formDataToJson} from "@/util/form-util";
|
||||
import localize_hu from 'ajv-i18n/localize/hu';
|
||||
|
||||
|
||||
export type ContactFormState = {
|
||||
firstname: FormControl,
|
||||
lastname: FormControl,
|
||||
phone: FormControl,
|
||||
email: FormControl,
|
||||
comment: FormControl,
|
||||
formState: 'empty' | 'invalid' | 'success'
|
||||
}
|
||||
export type ContactFormData = {
|
||||
firstname: string,
|
||||
lastname: string,
|
||||
phone: string,
|
||||
email: string,
|
||||
comment: string,
|
||||
}
|
||||
|
||||
export async function sendContactUsMail(prevState: ContactFormState, formState: FormData) {
|
||||
const newState: ContactFormState = {
|
||||
comment: {name: 'comment'}, email: {name: 'email'}, firstname: {name: 'firstname'},
|
||||
lastname: {name: 'lastname'},
|
||||
phone: {name: 'phone'},
|
||||
formState: 'invalid'
|
||||
};
|
||||
const contactUsFormData: ContactFormData = formDataToJson(formState) as ContactFormData;
|
||||
const validationResult = contactUsFormValidator(contactUsFormData);
|
||||
localize_hu(contactUsFormValidator.errors)
|
||||
if (!validationResult) {
|
||||
copyFormValues(newState, contactUsFormData);
|
||||
copyValidationErrors(newState, contactUsFormValidator)
|
||||
} else {
|
||||
sendMail(
|
||||
{
|
||||
email: contactUsFormData.email,
|
||||
sendTo: process.env.SITE_MAIL_RECIEVER,
|
||||
subject: 'Kapcsolat',
|
||||
text: `
|
||||
Kapcsolat üzenet érkezett:
|
||||
Név: ${contactUsFormData.lastname} ${contactUsFormData.firstname}
|
||||
Tel: ${contactUsFormData.phone}
|
||||
Email: ${contactUsFormData.email}
|
||||
Üzenet:
|
||||
${contactUsFormData.comment}
|
||||
`,
|
||||
html:`
|
||||
Kapcsolat üzenet érkezett:
|
||||
<br>Név: ${contactUsFormData.lastname} ${contactUsFormData.firstname}
|
||||
<br>Tel: ${contactUsFormData.phone}
|
||||
<br>Email: ${contactUsFormData.email}
|
||||
<br>Üzenet:
|
||||
<br>${contactUsFormData.comment}
|
||||
`,
|
||||
}
|
||||
)
|
||||
newState.formState = 'success';
|
||||
}
|
||||
return newState;
|
||||
}
|
||||
50
yoga-app/src/actions/mail-actions.ts
Normal file
50
yoga-app/src/actions/mail-actions.ts
Normal file
@ -0,0 +1,50 @@
|
||||
'use server';
|
||||
import nodemailer from 'nodemailer';
|
||||
const SMTP_SERVER_HOST = process.env.SMTP_SERVER_HOST;
|
||||
const SMTP_SERVER_PORT =parseInt( process.env.SMTP_SERVER_PORT!,10);
|
||||
const SMTP_SERVER_SECURE = "true" == process.env.SMTP_SERVER_SECIRE ;
|
||||
const SMTP_SERVER_USERNAME = process.env.SMTP_SERVER_USERNAME;
|
||||
const SMTP_SERVER_PASSWORD = process.env.SMTP_SERVER_PASSWORD;
|
||||
const SITE_MAIL_RECIEVER = process.env.SITE_MAIL_RECIEVER;
|
||||
const transporter = nodemailer.createTransport({
|
||||
// service: 'gmail',
|
||||
host: SMTP_SERVER_HOST,
|
||||
port: SMTP_SERVER_PORT,
|
||||
secure: SMTP_SERVER_SECURE,
|
||||
auth: {
|
||||
user: SMTP_SERVER_USERNAME,
|
||||
pass: SMTP_SERVER_PASSWORD,
|
||||
},
|
||||
});
|
||||
|
||||
export async function sendMail({
|
||||
email,
|
||||
sendTo,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
}: {
|
||||
email: string;
|
||||
sendTo?: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
html?: string;
|
||||
}) {
|
||||
try {
|
||||
const isVerified = await transporter.verify();
|
||||
console.log("isVerified",isVerified)
|
||||
} catch (error) {
|
||||
console.error('Something Went Wrong', SMTP_SERVER_USERNAME, SMTP_SERVER_PASSWORD, error);
|
||||
return;
|
||||
}
|
||||
const info = await transporter.sendMail({
|
||||
from: email,
|
||||
to: sendTo || SITE_MAIL_RECIEVER,
|
||||
subject: subject,
|
||||
text: text,
|
||||
html: html ? html : '',
|
||||
});
|
||||
console.log('Message Sent', info.messageId);
|
||||
console.log('Mail sent to',sendTo, SITE_MAIL_RECIEVER);
|
||||
return info;
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
|
||||
export interface HomePageData{
|
||||
|
||||
}
|
||||
|
||||
|
||||
export interface OurServiceData{
|
||||
|
||||
}
|
||||
|
||||
export interface AboutUsData{
|
||||
|
||||
}
|
||||
|
||||
export interface OurSpecialitiesData{
|
||||
|
||||
}
|
||||
@ -1,10 +1,5 @@
|
||||
import React from "react";
|
||||
import AosComponent from "@/components/aos.component";
|
||||
import OurServicesComponent from "@/components/our.services.component";
|
||||
import OurSpecialitiesComponent from "@/components/our.specialities.component";
|
||||
import ContactUsComponent from "@/components/contact.us.component";
|
||||
import PricingComponent from "@/components/pricing.component";
|
||||
import FeedbackComponent from "@/components/feedbackComponent";
|
||||
import BlogPostsComponent from "@/components/blog.posts.component";
|
||||
import FooterComponent from "@/components/footer.component";
|
||||
import SubscribeComponent from "@/components/subscribe.component";
|
||||
@ -43,11 +38,6 @@ export default async function About() {
|
||||
{ ourVision && <TextWithImageComponent config={ourVision} />}
|
||||
{ achievements && <AchievementsComponent config={achievements}/>}
|
||||
|
||||
{/*{ ourServices && <OurServicesComponent title={ourServices?.title!} header={ourServices?.header!} description={ourServices?.description!} /> }*/}
|
||||
{/*{ ourSpecialities && <OurSpecialitiesComponent config={ourSpecialities} /> }*/}
|
||||
{/*{ contactUs && <ContactUsComponent contactUs={contactUs}/>}*/}
|
||||
{/*{ prices && <PricingComponent config={prices} /> }*/}
|
||||
{/*{ feedbacks && <FeedbackComponent config={feedbacks} /> }*/}
|
||||
{ blogs && <BlogPostsComponent config={blogs} />}
|
||||
{ subscribeNow && <SubscribeComponent config={subscribeNow} /> }
|
||||
{ footer && <FooterComponent config={footer} />}
|
||||
@ -59,6 +49,5 @@ export default async function About() {
|
||||
);
|
||||
}
|
||||
|
||||
export const getBreadCrumbName = () => {
|
||||
return "Rólunk;"
|
||||
}
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@ -4,3 +4,4 @@ export default function BlogListComponent(){
|
||||
<div>Blog list</div>
|
||||
)
|
||||
}
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import strapiApi from "@/api/strapi/strapi-api";
|
||||
import SubHeaderComponent from "@/components/subHeader.component";
|
||||
import FaqComponent from "@/components/faq.component";
|
||||
import BlogPostsComponent from "@/components/blog.posts.component";
|
||||
import SubscribeComponent from "@/components/subscribe.component";
|
||||
import FooterComponent from "@/components/footer.component";
|
||||
import AosComponent from "@/components/aos.component";
|
||||
@ -34,3 +32,5 @@ export default async function ContactPage(){
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@ -1,11 +1,5 @@
|
||||
import React from "react";
|
||||
import AosComponent from "@/components/aos.component";
|
||||
import OurServicesComponent from "@/components/our.services.component";
|
||||
import AboutUsComponent from "@/components/about.us.component";
|
||||
import OurSpecialitiesComponent from "@/components/our.specialities.component";
|
||||
import ContactUsComponent from "@/components/contact.us.component";
|
||||
import PricingComponent from "@/components/pricing.component";
|
||||
import FeedbackComponent from "@/components/feedbackComponent";
|
||||
import BlogPostsComponent from "@/components/blog.posts.component";
|
||||
import FooterComponent from "@/components/footer.component";
|
||||
import SubscribeComponent from "@/components/subscribe.component";
|
||||
@ -24,7 +18,7 @@ export default async function About() {
|
||||
footer
|
||||
} = await strapiApi.getFaqPage();
|
||||
return (
|
||||
<>
|
||||
<>xxxxx
|
||||
{ <SubHeaderComponent header1={header} description={description} /> }
|
||||
{ questionsAndAnswers && <FaqComponent config={questionsAndAnswers} /> }
|
||||
{ blogs && <BlogPostsComponent config={blogs} /> }
|
||||
@ -36,3 +30,4 @@ export default async function About() {
|
||||
|
||||
);
|
||||
}
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@ -13,3 +13,4 @@ const Login: React.FC = () => {
|
||||
|
||||
|
||||
export default Login;
|
||||
|
||||
|
||||
@ -50,3 +50,5 @@ export default async function Home() {
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@ -38,3 +38,5 @@ export default async function PricesPage( ) {
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import React from "react";
|
||||
import AosComponent from "@/components/aos.component";
|
||||
import OurServicesComponent from "@/components/our.services.component";
|
||||
import AboutUsComponent from "@/components/about.us.component";
|
||||
import OurSpecialitiesComponent from "@/components/our.specialities.component";
|
||||
import ContactUsComponent from "@/components/contact.us.component";
|
||||
import PricingComponent from "@/components/pricing.component";
|
||||
import FeedbackComponent from "@/components/feedbackComponent";
|
||||
import BlogPostsComponent from "@/components/blog.posts.component";
|
||||
import FooterComponent from "@/components/footer.component";
|
||||
@ -39,3 +37,5 @@ export default async function Services() {
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import YogaImageComponent from "@/components/yoga.image.component";
|
||||
import {
|
||||
YogaAboutUsComponent,
|
||||
YogaAboutUsComponent_Plain
|
||||
} from "@/types/generated-strapi-interfaces/api/yoga-about-us-component";
|
||||
import {StrapiFile} from "@/types/types";
|
||||
|
||||
@ -7,7 +7,7 @@ export interface Props{
|
||||
config: YogaAboutUsWithBoxesComponent_Plain
|
||||
}
|
||||
|
||||
export default function AboutUsWithBoxesComponent({ config: {title,header,description,image, box1,box2,box3,box4}}: Props){
|
||||
export default function AboutUsWithBoxesComponent({ config: {title,header,description, box1,box2,box3,box4}}: Props){
|
||||
|
||||
|
||||
return (
|
||||
|
||||
@ -8,7 +8,7 @@ export interface Props{
|
||||
|
||||
}
|
||||
export default function AchievementsItemComponent({achievement
|
||||
:{ image,title}
|
||||
:{ image}
|
||||
}: Props){
|
||||
const imageFile: StrapiFile = image as StrapiFile;
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import YogaImageComponent from "@/components/yoga.image.component";
|
||||
import {YogaBlogPostsComponent_Plain} from "@/types/generated-strapi-interfaces/api/yoga-blog-posts-component";
|
||||
import {YogaBlogPost_Plain} from "@/types/generated-strapi-interfaces/api/yoga-blog-post";
|
||||
import {StrapiFile} from "@/types/types";
|
||||
import strapiApi from "@/api/strapi/strapi-api";
|
||||
|
||||
@ -4,7 +4,7 @@ import { useEffect } from "react";
|
||||
export default function BootstrapComponent()
|
||||
{
|
||||
useEffect(()=>{
|
||||
// @ts-ignore
|
||||
// @ts-expect-error promise
|
||||
import( "bootstrap/dist/js/bootstrap.bundle")
|
||||
},[])
|
||||
return <></>
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import React, { ReactNode } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
import {usePathname, useRouter} from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import {usePathname} from 'next/navigation'
|
||||
|
||||
type Props = {
|
||||
}
|
||||
|
||||
const pathToBreadCrumbs = (path: string) => {
|
||||
const mapping: Record<string, string> = {
|
||||
@ -34,13 +31,13 @@ const NextBreadcrumb = () => {
|
||||
<div className="btn_wrapper">
|
||||
<span className="sub_home_span">{pathToBreadCrumbs( "/" )} </span>
|
||||
{
|
||||
pathNames.map((value,index) => {
|
||||
pathNames.map((value) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<React.Fragment key={value}>
|
||||
<i className="fa-solid fa-angles-right" aria-hidden="true"></i>
|
||||
<span className="sub_span">{pathToBreadCrumbs( value )}</span>
|
||||
</>
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,10 +1,43 @@
|
||||
'use client'
|
||||
import YogaImageComponent from "@/components/yoga.image.component";
|
||||
import {YogaContactUs_Plain} from "@/types/generated-strapi-interfaces/api/yoga-contact-us";
|
||||
import {RefObject, startTransition, useActionState, useEffect, useRef} from "react";
|
||||
import {ContactFormState, sendContactUsMail} from "@/actions/contactus-actions";
|
||||
import clsx from "clsx";
|
||||
|
||||
export interface Props{
|
||||
export interface Props {
|
||||
contactUs: YogaContactUs_Plain
|
||||
}
|
||||
const ContactUsComponent = ( { contactUs :{ header,firstName,lastName,phone,title,message,email,buttonText }}: Props) => {
|
||||
|
||||
const ContactUsComponent = ({
|
||||
contactUs: {
|
||||
header,
|
||||
firstName,
|
||||
lastName,
|
||||
phone,
|
||||
title,
|
||||
message,
|
||||
email,
|
||||
buttonText
|
||||
}
|
||||
}: Props) => {
|
||||
const initialState = () => {
|
||||
return {
|
||||
firstname: {value: ''},
|
||||
lastname: {value: ''},
|
||||
comment: {value: ''},
|
||||
email: {value: ''},
|
||||
phone: {value: ''}
|
||||
} as ContactFormState;
|
||||
}
|
||||
const [state, formAction] = useActionState(sendContactUsMail, initialState());
|
||||
const ref: RefObject<HTMLFormElement|null> = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
if ( state.formState == 'success'){
|
||||
ref.current?.reset()
|
||||
}
|
||||
}, [state]);
|
||||
return (
|
||||
<section className="get_in_touch_section">
|
||||
<div className="container">
|
||||
@ -13,39 +46,67 @@ const ContactUsComponent = ( { contactUs :{ header,firstName,lastName,phone,ti
|
||||
<div className="get_in_touch_content">
|
||||
<h5>{title}</h5>
|
||||
<h2>{header}</h2>
|
||||
<form>
|
||||
<form
|
||||
ref={ref}
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
startTransition(() => formAction(new FormData(e.currentTarget)));
|
||||
}}
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-lg-6 col-md-6 col-sm-6">
|
||||
<div className="form-group mb-0">
|
||||
<input type="text" name="fname" id="fname" className="form-control"
|
||||
placeholder={firstName}/>
|
||||
<input type="text" name="firstname" id="firstname"
|
||||
className={clsx("form-control", {"mb-0": state.firstname.error})}
|
||||
placeholder={firstName}
|
||||
/>
|
||||
</div>
|
||||
{state.firstname.error &&
|
||||
<div className="text-danger mb-3">{state.firstname.error}</div>}
|
||||
</div>
|
||||
<div className="col-lg-6 col-md-6 col-sm-6">
|
||||
<div className="form-group mb-0">
|
||||
<input type="text" name="lname" id="lname"
|
||||
className="form-control form_style" placeholder={lastName}/>
|
||||
<input type="text" name="lastname" id="lname"
|
||||
className={clsx("form-control", "form_style", {"mb-0": state.lastname.error})}
|
||||
placeholder={lastName}/>
|
||||
</div>
|
||||
{state.lastname.error &&
|
||||
<div className="text-danger mb-3">{state.lastname.error}</div>}
|
||||
|
||||
</div>
|
||||
<div className="col-lg-6 col-md-6 col-sm-6">
|
||||
<div className="form-group mb-0">
|
||||
<input type="tel" name="phonenum" id="phonenum" className="form-control"
|
||||
<input type="tel" name="phone" id="phonenum"
|
||||
className={clsx("form-control", {"mb-0": state.phone.error})}
|
||||
|
||||
placeholder={phone}/>
|
||||
</div>
|
||||
{state.phone.error &&
|
||||
<div className="text-danger mb-3">{state.phone.error}</div>}
|
||||
|
||||
</div>
|
||||
<div className="col-lg-6 col-md-6 col-sm-6">
|
||||
<div className="form-group mb-0">
|
||||
<input type="email" name="emailaddrs" id="emailaddrs"
|
||||
className="form-control form_style" placeholder={email}/>
|
||||
<input type="email" name="email" id="emailaddrs"
|
||||
className={clsx("form-control", "form_style", {"mb-0": state.email.error})}
|
||||
placeholder={email}/>
|
||||
</div>
|
||||
{state.email.error &&
|
||||
<div className="text-danger mb-3">{state.email.error}</div>}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-lg-12">
|
||||
<div className=" form-group mb-0">
|
||||
<textarea rows={3} name="comment" id="msg" className="form-control"
|
||||
<textarea rows={3} name="comment" id="msg"
|
||||
className={clsx("form-control", {"mb-0": state.comment.error})}
|
||||
|
||||
placeholder={message}></textarea>
|
||||
</div>
|
||||
{state.comment.error &&
|
||||
<div className="text-danger mb-3">{state.comment.error}</div>}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="btn_wrapper">
|
||||
@ -61,7 +122,7 @@ const ContactUsComponent = ( { contactUs :{ header,firstName,lastName,phone,ti
|
||||
href="https://previews.customer.envatousercontent.com/6720474d-ddc3-4b86-acf1-8d093cb37b6d/watermarked_preview/watermarked_preview.mp4">
|
||||
<figure className="video_img mb-0">
|
||||
<YogaImageComponent className="thumb img-fluid" style={{"cursor": "pointer"}}
|
||||
src="/assets/images/get_in_touch_video_icon.png" alt=""/>
|
||||
src="/assets/images/get_in_touch_video_icon.png" alt=""/>
|
||||
</figure>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
import {YogaFaqComponent_Plain} from "@/types/generated-strapi-interfaces/api/yoga-faq-component";
|
||||
import {useState} from "react";
|
||||
import FaqQaComponent from "@/components/faq.qa.component";
|
||||
import { MouseEvent } from 'react'
|
||||
|
||||
|
||||
export interface Props {
|
||||
@ -53,6 +52,7 @@ export default function FaqComponent({
|
||||
questionsAndAnswers.map((qa, index) => {
|
||||
return (
|
||||
<FaqQaComponent
|
||||
key={qa.id}
|
||||
qa={qa}
|
||||
id={index}
|
||||
onClick={onQAClick}
|
||||
|
||||
@ -2,27 +2,18 @@ import YogaImageComponent from "@/components/yoga.image.component";
|
||||
import {YogaFaqQa_Plain} from "@/types/generated-strapi-interfaces/api/yoga-faq-qa";
|
||||
import clsx from "clsx";
|
||||
|
||||
import classes from "./faq.qa.module.scss";
|
||||
import {useEffect, useState} from "react";
|
||||
|
||||
export interface Props{
|
||||
id: number,
|
||||
qa: YogaFaqQa_Plain,
|
||||
isOpen: boolean,
|
||||
onClick: (id: number) => void
|
||||
}
|
||||
export default function FaqQaComponent({id,qa,isOpen,onClick}: Props){
|
||||
|
||||
|
||||
export default function FaqQaComponent({id,qa,isOpen}: Props){
|
||||
|
||||
return (
|
||||
<div className="accordion-card">
|
||||
<div className="card-header" id={"heading" + id}>
|
||||
<a href="#"
|
||||
// onClick={ (event) => {
|
||||
// event.preventDefault();
|
||||
// onClick(id)
|
||||
// }}
|
||||
className={clsx("btn", "btn-link", {"collapsed": !isOpen})}
|
||||
data-toggle="collapse" data-target={"#collapse" + id}
|
||||
aria-expanded="false" aria-controls={"collapse" + id}>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
YogaGoogleMapsComponent,
|
||||
YogaGoogleMapsComponent_Plain
|
||||
} from "@/types/generated-strapi-interfaces/api/yoga-google-maps-component";
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ const ITypedComponent = ( {text}: Props ) => {
|
||||
loop: true
|
||||
})
|
||||
setReady(true)
|
||||
}, [ready]);
|
||||
}, [ready,text]);
|
||||
return (<></> );
|
||||
}
|
||||
|
||||
|
||||
@ -6,10 +6,8 @@ export interface Props{
|
||||
const MainHeaderComponent = ({ config: {
|
||||
title,
|
||||
header,
|
||||
headerIType,
|
||||
description,
|
||||
button,
|
||||
image
|
||||
|
||||
}}: Props) => {
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
import {FC} from "react";
|
||||
import YogaImageComponent from "@/components/yoga.image.component";
|
||||
import clsx from "clsx";
|
||||
import exp from "node:constants";
|
||||
|
||||
export interface MenuItem{
|
||||
href?: string;
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
import YogaImageComponent from "@/components/yoga.image.component";
|
||||
import {
|
||||
OurSpecialitiesComponent_Plain
|
||||
} from "@/types/generated-strapi-interfaces/components/yoga-site/OurSpecialitiesComponent";
|
||||
import OurSpecialitiesItemComponent from "@/components/our.specialities.item.component";
|
||||
import {YogaSpecialitiesComponent_Plain} from "@/types/generated-strapi-interfaces/api/yoga-specialities-component";
|
||||
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
import YogaImageComponent from "@/components/yoga.image.component";
|
||||
import {
|
||||
OurSpecialitiesComponent_Plain
|
||||
} from "@/types/generated-strapi-interfaces/components/yoga-site/OurSpecialitiesComponent";
|
||||
import {TitleDescription_Plain} from "@/types/generated-strapi-interfaces/components/shared/TitleDescription";
|
||||
import clsx from "clsx";
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import YogaImageComponent from "@/components/yoga.image.component";
|
||||
import {YogaPriceComponent_Plain} from "@/types/generated-strapi-interfaces/api/yoga-price-component";
|
||||
import {PriceItemComponent} from "@/components/price.item.component";
|
||||
|
||||
|
||||
@ -5,10 +5,9 @@ import {HeaderB} from "@/types/generated-strapi-interfaces/components/yoga-site/
|
||||
import NextBreadcrumb from "@/components/breadcrumbs.component";
|
||||
|
||||
|
||||
export interface Props extends HeaderB{
|
||||
}
|
||||
export type Props = HeaderB ;
|
||||
|
||||
const SubHeaderComponent = ({header1,header2,description}: Props) =>{
|
||||
const SubHeaderComponent = ({header1,description}: Props) =>{
|
||||
return (
|
||||
<div className="sub-banner-section">
|
||||
<Nav menuItems={MAIN_MENU} />
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
import clsx from "clsx";
|
||||
export interface Props{
|
||||
config: YogaSubscribeNowComponent_Plain,
|
||||
styleClass: string
|
||||
styleClass?: string
|
||||
}
|
||||
const SubscribeComponent = ({
|
||||
config: {title,header,placeHolderEmail,buttonSubscribeLabel},
|
||||
|
||||
@ -11,6 +11,7 @@ export interface Properties {
|
||||
|
||||
const YogaImageComponent = ( {src,alt,className,style}: Properties ) => {
|
||||
return (
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
<img src={src} alt={alt || ""} className={ clsx( className )} style={style} />
|
||||
);
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ export interface Article {
|
||||
cover?: { data: Media };
|
||||
author?: { data: Author };
|
||||
category?: { data: Category };
|
||||
blocks?: any;
|
||||
blocks?: object;
|
||||
};
|
||||
}
|
||||
export interface Article_Plain {
|
||||
@ -28,7 +28,7 @@ export interface Article_Plain {
|
||||
cover?: Media_Plain;
|
||||
author?: Author_Plain;
|
||||
category?: Category_Plain;
|
||||
blocks?: any;
|
||||
blocks?: object;
|
||||
}
|
||||
|
||||
export interface Article_NoRelations {
|
||||
@ -39,7 +39,7 @@ export interface Article_NoRelations {
|
||||
cover?: number;
|
||||
author?: number;
|
||||
category?: number;
|
||||
blocks?: any;
|
||||
blocks?: object;
|
||||
}
|
||||
|
||||
export interface Article_AdminPanelLifeCycle {
|
||||
@ -50,5 +50,5 @@ export interface Article_AdminPanelLifeCycle {
|
||||
cover?: AdminPanelRelationPropertyModification<Media_Plain>;
|
||||
author?: AdminPanelRelationPropertyModification<Author_Plain>;
|
||||
category?: AdminPanelRelationPropertyModification<Category_Plain>;
|
||||
blocks?: any;
|
||||
blocks?: object;
|
||||
}
|
||||
|
||||
53
yoga-app/src/util/form-util.ts
Normal file
53
yoga-app/src/util/form-util.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import {ValidateFunction} from "ajv";
|
||||
|
||||
export type FormControl = {
|
||||
name: string,
|
||||
value?: string,
|
||||
error?: string
|
||||
}
|
||||
|
||||
export function formDataToJson(formData: FormData) {
|
||||
const object: Record<string, string | string[] | File | File[]> = {};
|
||||
formData.forEach((value, key) => {
|
||||
if ( key == 'formState'){
|
||||
return;
|
||||
}
|
||||
// Reflect.has in favor of: object.hasOwnProperty(key)
|
||||
if (!Reflect.has(object, key)) {
|
||||
object[key] = value;
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(object[key])) {
|
||||
object[key] = [];
|
||||
}
|
||||
if (value instanceof File) {
|
||||
(object[key] as File[]).push(value);
|
||||
} else {
|
||||
(object[key] as string[]).push(value);
|
||||
}
|
||||
});
|
||||
return object;
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export function copyFormValues(form: Record<string, FormControl|string|any>,jsonFormData: Record<string, FormDataEntryValue>){
|
||||
const keys = Object.getOwnPropertyNames(form);
|
||||
for(const key of keys){
|
||||
if (key == 'formState'){
|
||||
continue;
|
||||
}
|
||||
const formControl: FormControl = form[key];
|
||||
formControl.value = jsonFormData[key] as string;
|
||||
|
||||
}
|
||||
}
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export function copyValidationErrors(form: Record<string, FormControl|string|any >,validateFunction: ValidateFunction){
|
||||
const keys = Object.getOwnPropertyNames(form);
|
||||
for(const key of keys){
|
||||
const formControl: FormControl = form[key];
|
||||
if ( key == 'formState') continue;
|
||||
formControl.error = validateFunction.errors?.find( err => err.instancePath =="/"+key)?.message;
|
||||
|
||||
}
|
||||
}
|
||||
23
yoga-app/src/validation/validation.ts
Normal file
23
yoga-app/src/validation/validation.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import Ajv, {JSONSchemaType} from "ajv"
|
||||
import addFormats from "ajv-formats"
|
||||
import {ContactFormData} from "@/actions/contactus-actions";
|
||||
|
||||
const ajv = new Ajv({allErrors: true}) // options can be passed, e.g. {allErrors: true}
|
||||
addFormats(ajv)
|
||||
|
||||
const contactUsFormSchema: JSONSchemaType<ContactFormData> = {
|
||||
type: "object",
|
||||
properties: {
|
||||
firstname: {type: "string",minLength: 3,maxLength: 20},
|
||||
lastname: {type: "string",minLength: 3,maxLength: 20},
|
||||
phone: {type: "string",minLength: 3,maxLength: 20},
|
||||
email: {type: "string", format: 'email'},
|
||||
comment: {type: "string",minLength: 3,maxLength: 500}
|
||||
},
|
||||
required: ["firstname","lastname","phone","email","comment"],
|
||||
additionalProperties: true
|
||||
}
|
||||
|
||||
export const contactUsFormValidator = ajv.compile(contactUsFormSchema)
|
||||
|
||||
|
||||
@ -7,11 +7,11 @@ ENV NODE_ENV=${NODE_ENV}
|
||||
WORKDIR /opt/
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install -g node-gyp
|
||||
RUN npm config set fetch-retry-maxtimeout 600000 -g && npm install --only=production
|
||||
RUN npm config set fetch-retry-maxtimeout 600000 -g && npm install --only=production --debug
|
||||
ENV PATH=/opt/node_modules/.bin:$PATH
|
||||
WORKDIR /opt/app
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
RUN npm run build --debug
|
||||
|
||||
# Creating final production image
|
||||
FROM node:18-alpine
|
||||
|
||||
31
yoga-cms/Dockerfile.prod
Normal file
31
yoga-cms/Dockerfile.prod
Normal file
@ -0,0 +1,31 @@
|
||||
# Creating multi-stage build for production
|
||||
FROM node:18-alpine as build
|
||||
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev vips-dev git > /dev/null 2>&1
|
||||
ARG NODE_ENV=production
|
||||
ENV NODE_ENV=${NODE_ENV}
|
||||
|
||||
WORKDIR /opt/
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install -g node-gyp
|
||||
RUN npm config set fetch-retry-maxtimeout 600000 -g && npm install --only=production --debug
|
||||
ENV PATH=/opt/node_modules/.bin:$PATH
|
||||
WORKDIR /opt/app
|
||||
COPY . .
|
||||
RUN pwd && ls -lah
|
||||
RUN npm run build --debug
|
||||
|
||||
# Creating final production image
|
||||
FROM node:18-alpine
|
||||
# RUN apk add --no-cache vips-dev
|
||||
# ARG NODE_ENV=production
|
||||
# ENV NODE_ENV=${NODE_ENV}
|
||||
# WORKDIR /opt/
|
||||
# COPY --from=build /opt/node_modules ./node_modules
|
||||
# WORKDIR /opt/app
|
||||
# COPY --from=build /opt/app ./
|
||||
# ENV PATH=/opt/node_modules/.bin:$PATH
|
||||
|
||||
# RUN chown -R node:node /opt/app
|
||||
USER node
|
||||
EXPOSE 1337
|
||||
# CMD ["npm", "run", "start"]
|
||||
Loading…
Reference in New Issue
Block a user