diff --git a/bun.lock b/bun.lock index ace049f..a5dc5d9 100644 --- a/bun.lock +++ b/bun.lock @@ -11,12 +11,16 @@ "@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.6", + "@radix-ui/react-slot": "^1.3.0", "@reduxjs/toolkit": "^2.12.0", "clsx": "^2.1.1", "date-fns": "^4.4.0", + "framer-motion": "^12.42.2", + "lucide-react": "^1.25.0", "react": "^19.2.7", "react-confetti-explosion": "^3.0.3", "react-dom": "^19.2.7", + "react-helmet-async": "^3.0.0", "react-redux": "^9.3.0", "react-router-dom": "^7.18.1", "tailwind-merge": "^3.6.0", @@ -197,6 +201,10 @@ "@pkgr/core": ["@pkgr/core@0.3.6", "", {}, "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA=="], + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="], + + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA=="], + "@reduxjs/toolkit": ["@reduxjs/toolkit@2.12.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], @@ -511,6 +519,8 @@ "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], + "framer-motion": ["framer-motion@12.42.2", "", { "dependencies": { "motion-dom": "^12.42.2", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], @@ -531,6 +541,8 @@ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="], + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], @@ -601,6 +613,8 @@ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "lucide-react": ["lucide-react@1.25.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/mdJTRbiwcLOQ1NZZK1amZF9rIZyvO18D6r9TngE6TG1NmqHgFuT4eE7Xrkm9UsXMbBJD1NlfwHVltCDWHrOTw=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], @@ -611,6 +625,10 @@ "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + "motion-dom": ["motion-dom@12.42.2", "", { "dependencies": { "motion-utils": "^12.39.0" } }, "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA=="], + + "motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], @@ -669,6 +687,10 @@ "react-dom": ["react-dom@19.2.7", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.7" } }, "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ=="], + "react-fast-compare": ["react-fast-compare@3.2.2", "", {}, "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="], + + "react-helmet-async": ["react-helmet-async@3.0.0", "", { "dependencies": { "invariant": "^2.2.4", "react-fast-compare": "^3.2.2", "shallowequal": "^1.1.0" }, "peerDependencies": { "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w=="], + "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], "react-redux": ["react-redux@9.3.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g=="], @@ -699,6 +721,8 @@ "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + "shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], diff --git a/index.html b/index.html index 5b0d039..a26c04a 100644 --- a/index.html +++ b/index.html @@ -2,13 +2,17 @@ - - - Lakshmikanta Patra | FrontendFixer [♥] + + + + + + +
- + diff --git a/package.json b/package.json index ab777a0..1a9291f 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,16 @@ "@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.6", + "@radix-ui/react-slot": "^1.3.0", "@reduxjs/toolkit": "^2.12.0", "clsx": "^2.1.1", "date-fns": "^4.4.0", + "framer-motion": "^12.42.2", + "lucide-react": "^1.25.0", "react": "^19.2.7", "react-confetti-explosion": "^3.0.3", "react-dom": "^19.2.7", + "react-helmet-async": "^3.0.0", "react-redux": "^9.3.0", "react-router-dom": "^7.18.1", "tailwind-merge": "^3.6.0", diff --git a/public/Lakshmikanta.pdf b/public/Lakshmikanta.pdf deleted file mode 100644 index cdffd35..0000000 Binary files a/public/Lakshmikanta.pdf and /dev/null differ diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000..f459fcc Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000..f71def1 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..affa9a8 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/demo.webp b/public/demo.webp deleted file mode 100644 index 7d791df..0000000 Binary files a/public/demo.webp and /dev/null differ diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..d5a15e3 Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..9624341 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..ccad1ed Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/og-image.png b/public/og-image.png new file mode 100644 index 0000000..1922960 Binary files /dev/null and b/public/og-image.png differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..4f20ecd --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://frontendfixer.dev/sitemap.xml diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..7d8262d --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "FrontendFixer", + "short_name": "FrontendFixer", + "description": "Full-Stack Developer for SaaS & MVPs", + "start_url": "/", + "display": "standalone", + "background_color": "#09090B", + "theme_color": "#14b8a6", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..af18137 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,8 @@ + + + + https://frontendfixer.dev/ + weekly + 1.0 + + diff --git a/public/twitter-image.png b/public/twitter-image.png new file mode 100644 index 0000000..1922960 Binary files /dev/null and b/public/twitter-image.png differ diff --git a/src/App.tsx b/src/App.tsx index 04789dd..7e70868 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,18 +1,20 @@ import { Route, Routes } from 'react-router-dom'; -import Weeding from '#routes/Weeding'; +import { SEO } from '#/components/common/SEO'; +import Navigation from '#routes/Navigation.tsx'; import Home from './routes/Home'; -import Navigation from './routes/navigation'; const App = () => { return ( - - }> - } /> - - } /> - + <> + + + }> + } /> + + + ); }; diff --git a/src/assets/images/portfolio/hero-dashboard.webp b/src/assets/images/portfolio/hero-dashboard.webp new file mode 100644 index 0000000..0da2b4f Binary files /dev/null and b/src/assets/images/portfolio/hero-dashboard.webp differ diff --git a/src/assets/images/portfolio/profile-placeholder.webp b/src/assets/images/portfolio/profile-placeholder.webp new file mode 100644 index 0000000..f13c16a Binary files /dev/null and b/src/assets/images/portfolio/profile-placeholder.webp differ diff --git a/src/assets/images/portfolio/vaultdrive-dashboard.webp b/src/assets/images/portfolio/vaultdrive-dashboard.webp new file mode 100644 index 0000000..960c99d Binary files /dev/null and b/src/assets/images/portfolio/vaultdrive-dashboard.webp differ diff --git a/src/assets/images/portfolio/workspace-placeholder.png b/src/assets/images/portfolio/workspace-placeholder.png new file mode 100644 index 0000000..835478c Binary files /dev/null and b/src/assets/images/portfolio/workspace-placeholder.png differ diff --git a/src/components/AltHeroSection.tsx b/src/components/AltHeroSection.tsx deleted file mode 100644 index d1229bf..0000000 --- a/src/components/AltHeroSection.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import heroImage from '#assets/images/hero-alt.webp'; -// import HeroIllustration from '#assets/images/hero-illustration.webp'; - -const AltHeroSection = () => { - return ( -
-
-

- FullStack Web Developer -

-

- Transforming Ideas into Immersive User Experiences with the Power of{' '} - NodeJs and PHP -

-
-
- image of three people discussing their coding skill and development -
-
- ); -}; - -export default AltHeroSection; diff --git a/src/components/BackToTop.tsx b/src/components/BackToTop.tsx deleted file mode 100644 index 120967f..0000000 --- a/src/components/BackToTop.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { faArrowCircleUp } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useEffect, useState } from 'react'; - -const BackToTop = () => { - const [visible, setVisible] = useState(false); - const [top, setTop] = useState(0); - - const handleClick = () => { - window.scrollTo({ top: 0, behavior: 'smooth' }); - }; - - useEffect(() => { - const intersectionObserver = new IntersectionObserver(entries => { - setTop(entries[0].boundingClientRect.top); - if (top <= 0) { - return setVisible(true); - } else setVisible(false); - }); - window.addEventListener('scroll', () => { - intersectionObserver.observe(document.querySelectorAll('section')[1]); - }); - }, [top]); - - return ( - - ); -}; - -export default BackToTop; diff --git a/src/components/Button.tsx b/src/components/Button.tsx deleted file mode 100644 index 0b678a7..0000000 --- a/src/components/Button.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { ReactNode } from 'react'; - -import { cn } from '#/lib/cn'; - -type ButtonType = { - type?: 'button' | 'submit' | 'reset' | undefined; - fullWidth?: boolean; - children?: ReactNode; - onClick?: () => void; - buttonType?: 'secondary' | 'inverted' | undefined; - disabled?: boolean; - className?: string; -}; - -const Button = ({ - type, - fullWidth, - children, - onClick, - disabled, - buttonType, - className, -}: ButtonType) => { - return ( - - ); -}; - -export default Button; diff --git a/src/components/CVDownloadButton.tsx b/src/components/CVDownloadButton.tsx deleted file mode 100644 index e557127..0000000 --- a/src/components/CVDownloadButton.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Button from './Button'; - -const downloadFile = () => { - const element = document.createElement('a'); - element.href = '/Lakshmikanta.pdf'; - element.download = 'Lakshmikanta.pdf'; - document.body.appendChild(element); - element.click(); -}; - -const CVDownloadButton = ({ - buttonType, -}: { - buttonType?: 'secondary' | 'inverted' | undefined; -}) => { - return ( - - ); -}; - -export default CVDownloadButton; diff --git a/src/components/Contact.tsx b/src/components/Contact.tsx deleted file mode 100644 index 8447a14..0000000 --- a/src/components/Contact.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useSelector } from 'react-redux'; - -import connectImg from '#assets/images/connect1.svg'; -import { RootState } from '#store/store'; - -import ContactForm from './ContactForm'; -import MessageSent from './MessageSent'; - -const Contact = () => { - const { form } = useSelector((state: RootState) => state); - - return ( -
-
- -
-
- {form.status !== 'completed' ? : } -
-
- ); -}; - -export default Contact; diff --git a/src/components/ContactCapsule.tsx b/src/components/ContactCapsule.tsx deleted file mode 100644 index 5cfb8a8..0000000 --- a/src/components/ContactCapsule.tsx +++ /dev/null @@ -1,11 +0,0 @@ -const ContactCapsule = () => { - return ( -
-

- Get in touch with me -

-
- ); -}; - -export default ContactCapsule; diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx deleted file mode 100644 index 802efa0..0000000 --- a/src/components/ContactForm.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import emailjs from '@emailjs/browser'; -import { faPaperPlane } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ChangeEvent, FormEvent, useRef, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import validator from 'validator'; - -import { cn } from '#/lib/cn'; -import { setFormData, setFormStatus } from '#store/reducer/FormSlice'; -import { AppDispatch, RootState } from '#store/store'; - -import Button from './Button'; -import Spinner from './Spinner'; - -const ContactForm = () => { - const initialState = { name: '', email: '', message: '' }; - const [inputs, setInputs] = useState(initialState); - const formEl = useRef(null); - - const { form } = useSelector((state: RootState) => state); - const dispatch: AppDispatch = useDispatch(); - - const errors: { name: string; errorMessage: string }[] = []; - const validationSchema = [ - { - name: 'name', - valid: validator.isLength(inputs.name, { min: 1 }), - error: 'Name must be between 4-20 characters', - }, - { - name: 'email', - valid: validator.isEmail(inputs.email), - error: 'Enter valid email', - }, - { - name: 'message', - valid: validator.isLength(inputs.message, { min: 20 }), - error: 'Message must be at least 20 characters', - }, - ]; - validationSchema.forEach(check => { - if (!check.valid) { - return errors.push({ name: check.name, errorMessage: check.error }); - } - errors.filter(err => err.name === check.name); - }); - - const handleChange = ( - e: ChangeEvent, - ) => { - setInputs({ ...inputs, [e.target.name]: e.target.value }); - }; - - const handelSubmit = async (e: FormEvent) => { - e.preventDefault(); - if (errors.length > 0) return; - dispatch(setFormData(inputs)); - dispatch(setFormStatus('loading')); - if (formEl.current == null) throw new Error(); - - const res = await emailjs.sendForm( - 'service_qdjdol9', - 'template_lobv7nq', - formEl.current, - 'G0MWsjYi9pbxnAwfJ', - ); - if (res.status === 200) { - return dispatch(setFormStatus('completed')); - } - return dispatch(setFormStatus('rejected')); - }; - - return ( -
-
- - -
-
- - -
-
- {!validator.isLength(inputs.message, { min: 20 }) ? ( - - Message should be at least 20 characters - - ) : ( - '' - )} -