commit 743f0d3ad15a9c5eaf198c43303e5f527c8fd30e Author: vitaliimulika-dev Date: Thu Jan 15 14:46:18 2026 +0200 Initial commit diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..02269f0 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +DISABLE_ESLINT_PLUGIN=true diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..8687ee0 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,62 @@ +name: Build + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to build' + required: true + default: 'main' + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout branch + uses: actions/checkout@v3 + with: + ref: ${{ gitea.event.inputs.branch }} + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 24 + + - name: Install dependencies + run: | + set -euo pipefail + npm install --no-audit --silent 2>&1 | tee install.log + env: + NODE_OPTIONS: '--max-old-space-size=4096' + + - name: Build (react-scripts build) + env: + CI: 'false' + NODE_OPTIONS: '--max-old-space-size=4096' + run: | + set -euo pipefail + npm run build 2>&1 | tee build.log + timeout-minutes: 5 + + - name: Verify build folder exists + run: test -d build || (echo "No build folder. Check build logs above."; exit 1) + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v3 + with: + name: build-logs + path: | + install.log + build.log + npm-debug.log* + if-no-files-found: ignore + + - name: Build completed + if: success() + run: echo "Build completed successfully" diff --git a/package.json b/package.json new file mode 100644 index 0000000..870a5a4 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "nestjs-jobs", + "version": "0.1.0", + "private": true, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-scripts": "^5.0.1", + "framer-motion": "^11.0.0", + "lucide-react": "^0.400.0" + }, + "devDependencies": { + "tailwindcss": "^3.4.0", + "postcss": "^8.4.0", + "autoprefixer": "^10.4.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} \ No newline at end of file diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..96bb01e --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..dc25f3e --- /dev/null +++ b/public/index.html @@ -0,0 +1,1235 @@ + + + + + + + + + + + + NestJS Jobs - Official Job Board + + + +
+ + + + + \ No newline at end of file diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..d07a526 --- /dev/null +++ b/src/App.js @@ -0,0 +1,26 @@ +import React from 'react'; +import Header from './components/Header'; +import Hero from './components/Hero'; +import JobsSection from './components/JobsSection'; +import TeamAugmentation from './components/TeamAugmentation'; +import CompaniesSection from './components/CompaniesSection'; +import SupportSection from './components/SupportSection'; +import Newsletter from './components/Newsletter'; +import Footer from './components/Footer'; + +function App() { + return ( +
+
+ + + + + + +
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/src/components/CompaniesSection.js b/src/components/CompaniesSection.js new file mode 100644 index 0000000..d8040fb --- /dev/null +++ b/src/components/CompaniesSection.js @@ -0,0 +1,157 @@ +import React from 'react'; +import { motion, useReducedMotion } from 'framer-motion'; + +const fadeUpPreset = (delay = 0, duration = 1.2) => ({ + initial: { opacity: 0, y: 20 }, + whileInView: { opacity: 1, y: 0 }, + viewport: { once: true, amount: 0.2 }, + transition: { delay, duration, ease: "easeOut" } +}); + +const CompaniesSection = () => { + const shouldReduceMotion = useReducedMotion(); + + const companies = [ + { + name: 'Sanofi', + logo: 'https://www.jobs.nestjs.com/img/logos/sanofi.png', + url: 'https://www.sanofi.com/' + }, + { + name: 'Adidas', + logo: 'https://www.jobs.nestjs.com/img/logos/adidas.svg', + url: 'https://adidas.com/' + }, + { + name: 'Autodesk', + logo: 'https://www.jobs.nestjs.com/img/logos/autodesk.png', + url: 'https://www.autodesk.com/' + }, + { + name: 'Mercedes', + logo: 'https://www.jobs.nestjs.com/img/logos/mercedes.png', + url: 'https://www.mercedes-benz.com/' + }, + { + name: 'Red Hat', + logo: 'https://www.jobs.nestjs.com/img/logos/red-hat.svg', + url: 'https://www.redhat.com/' + }, + { + name: 'BMW', + logo: 'https://www.jobs.nestjs.com/img/logos/bmw.svg', + url: '#' + }, + { + name: 'Roche', + logo: 'https://www.jobs.nestjs.com/img/logos/roche-logo.png', + url: 'https://roche.com/' + }, + { + name: 'IBM', + logo: 'https://www.jobs.nestjs.com/img/logos/ibm.svg', + url: 'https://www.ibm.com/' + }, + { + name: 'Société Générale', + logo: 'https://www.jobs.nestjs.com/img/logos/societe-generale-logo.png', + url: 'https://www.societegenerale.fr/' + }, + { + name: 'Decathlon', + logo: 'https://www.jobs.nestjs.com/img/logos/decathlon.png', + url: 'https://www.decathlon.com/' + }, + { + name: 'JetBrains', + logo: 'https://www.jobs.nestjs.com/img/logos/jetbrains.svg', + url: 'https://www.jetbrains.com/' + }, + { + name: 'REWE', + logo: 'https://www.jobs.nestjs.com/img/logos/rewe.svg', + url: 'https://www.rewe-digital.com/' + }, + { + name: 'TotalEnergies', + logo: 'https://www.jobs.nestjs.com/img/logos/totalenergies.svg', + url: 'https://totalenergies.com/' + }, + { + name: 'GitLab', + logo: 'https://www.jobs.nestjs.com/img/logos/gitlab.png', + url: 'https://about.gitlab.com/' + }, + { + name: 'Capgemini', + logo: 'https://www.jobs.nestjs.com/img/logos/capgemini.svg', + url: 'https://capgemini.com/' + } + ]; + + const CompanyLogo = ({ company, index }) => { + const logoContent = ( +
+ {company.url && company.url !== '#' ? ( + + {company.name} + + ) : ( + {company.name} + )} +
+ ); + + if (shouldReduceMotion) { + return logoContent; + } + + return ( + + {logoContent} + + ); + }; + + const sectionContent = ( +
+
+
+

+ Who is using Nest? +

+

+ Nest is proudly powering a large ecosystem of enterprises and products out there. Wanna see your logo here? + Find out more. +

+
+ +
+ {companies.map((company, index) => ( + + ))} +
+
+
+ ); + + if (shouldReduceMotion) { + return sectionContent; + } + + return ( + + {sectionContent} + + ); +}; + +export default CompaniesSection; \ No newline at end of file diff --git a/src/components/Footer.js b/src/components/Footer.js new file mode 100644 index 0000000..dbc301e --- /dev/null +++ b/src/components/Footer.js @@ -0,0 +1,88 @@ +import React from 'react'; +import { Github } from 'lucide-react'; + +const Footer = () => { + return ( + + ); +}; + +export default Footer; \ No newline at end of file diff --git a/src/components/Header.js b/src/components/Header.js new file mode 100644 index 0000000..995ea90 --- /dev/null +++ b/src/components/Header.js @@ -0,0 +1,94 @@ +import React, { useState } from 'react'; +import { Menu, X, ChevronDown, Github } from 'lucide-react'; + +const Header = () => { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [isResourcesOpen, setIsResourcesOpen] = useState(false); + + return ( +
+
+
+ {/* Logo */} +
+ + NestJS - A progressive Node.js framework + +
+ + {/* Desktop Navigation */} + + + {/* Mobile menu button */} +
+ +
+
+
+ + {/* Mobile Navigation */} + {isMenuOpen && ( +
+
+ + HOME + + + JOBS + + + SIGN IN + + + RESOURCES + +
+ + + + + + +
+
+
+ )} +
+ ); +}; + +export default Header; \ No newline at end of file diff --git a/src/components/Hero.js b/src/components/Hero.js new file mode 100644 index 0000000..f164aa7 --- /dev/null +++ b/src/components/Hero.js @@ -0,0 +1,73 @@ +import React from 'react'; +import { motion, useReducedMotion } from 'framer-motion'; + +const fadeUpPreset = (delay = 0, duration = 1.2) => ({ + initial: { opacity: 0, y: 20 }, + whileInView: { opacity: 1, y: 0 }, + viewport: { once: true, amount: 0.2 }, + transition: { delay, duration, ease: "easeOut" } +}); + +const Hero = () => { + const shouldReduceMotion = useReducedMotion(); + + if (shouldReduceMotion) { + return ( +
+
+
+

+ Official NestJS job board +

+

+ Discover companies looking for developers with NestJS experience and find your next role. +

+
+ + +
+
+
+ ); + } + + return ( + +
+
+ + Official NestJS job board + + + Discover companies looking for developers with NestJS experience and find your next role. + + + + + +
+
+ ); +}; + +export default Hero; \ No newline at end of file diff --git a/src/components/JobsSection.js b/src/components/JobsSection.js new file mode 100644 index 0000000..6202efb --- /dev/null +++ b/src/components/JobsSection.js @@ -0,0 +1,266 @@ +import React, { useState } from 'react'; +import { motion, useReducedMotion } from 'framer-motion'; +import { Search, MapPin, Globe, Star } from 'lucide-react'; + +const fadeUpPreset = (delay = 0, duration = 1.2) => ({ + initial: { opacity: 0, y: 20 }, + whileInView: { opacity: 1, y: 0 }, + viewport: { once: true, amount: 0.2 }, + transition: { delay, duration, ease: "easeOut" } +}); + +const JobsSection = () => { + const shouldReduceMotion = useReducedMotion(); + const [searchTerm, setSearchTerm] = useState(''); + + const jobs = [ + { + id: 1, + title: 'Senior Product Engineer', + company: 'Pandektes', + location: 'Remote', + country: '🇩🇰 Denmark', + salary: '$120k - $150k', + timeAgo: '4 hours ago', + logo: 'https://nestjs-jobs-bucket-localhost.s3.amazonaws.com/65dc7731-a4bb-4bbe-bc20-7401691a9ec5.png', + featured: false + }, + { + id: 2, + title: 'Senior Software Engineer', + company: 'Trilon', + location: 'Remote', + country: '🌏 Worldwide', + salary: null, + timeAgo: '25 days ago', + logo: 'https://nestjs-jobs-bucket-localhost.s3.amazonaws.com/612b8b42-9610-43b1-92bd-15373110159f.png', + featured: true + } + ]; + + const expiredJobs = [ + { + id: 3, + title: 'Full-Stack Developer Angular/NestJS/PHP/MySQL', + company: 'Nylon Technology', + location: 'Remote', + country: '🇺🇸 United States, Continental...', + salary: '$100k - $130k', + timeAgo: 'a month ago', + logo: 'https://nestjs-jobs-bucket-localhost.s3.amazonaws.com/c54da9a9-c7aa-4d9a-838d-1021b684d957.jpeg' + }, + { + id: 4, + title: 'Full Stack Typescript Developer (NextJs, NestJs)', + company: 'ClickTech', + location: 'Remote', + country: '🌏 Worldwide', + salary: '$40k - $50k', + timeAgo: '2 months ago', + logo: 'https://nestjs-jobs-bucket-localhost.s3.amazonaws.com/3e62a3bc-0bae-4f8b-8e5a-4b14db5ef4ab.jpeg' + }, + { + id: 5, + title: 'NestJS Testing Specialist (Freelancer/Consultant)', + company: 'Kapital', + location: '🌏 Worldwide', + country: '', + salary: null, + timeAgo: '2 months ago', + logo: 'https://nestjs-jobs-bucket-localhost.s3.amazonaws.com/6e1385c3-7fd6-4444-8fc4-908d4aef24be.png' + }, + { + id: 6, + title: 'Full-Stack Engineer (Mid-Senior) [NestJS, NextJS]', + company: 'OASYS NOW', + location: '🇳🇱 Netherlands, Delft', + country: '', + salary: '$60k - $80k', + timeAgo: '2 months ago', + logo: 'https://nestjs-jobs-bucket-localhost.s3.amazonaws.com/696a0fcf-7bdb-44e2-bb03-dbdb0fb2e18b.jpeg' + }, + { + id: 7, + title: 'Senior Backend Engineer', + company: 'Pandektes', + location: 'Remote', + country: '🇩🇰 Denmark, Copenhagen', + salary: '$110k - $140k', + timeAgo: '2 months ago', + logo: 'https://nestjs-jobs-bucket-localhost.s3.amazonaws.com/65dc7731-a4bb-4bbe-bc20-7401691a9ec5.png' + }, + { + id: 8, + title: 'Backend NestJS Developer', + company: 'ClickTech', + location: 'Remote', + country: '🌏 Worldwide', + salary: '$40k - $60k', + timeAgo: '6 months ago', + logo: 'https://nestjs-jobs-bucket-localhost.s3.amazonaws.com/3e62a3bc-0bae-4f8b-8e5a-4b14db5ef4ab.jpeg' + }, + { + id: 9, + title: 'Senior Software Engineer', + company: 'Pippen AI', + location: 'Remote', + country: '🇨🇦 Canada, Toronto', + salary: '$70k - $100k', + timeAgo: '6 months ago', + logo: 'https://nestjs-jobs-bucket-localhost.s3.amazonaws.com/69388f1e-2dff-444b-9fc4-9f035bbf654f.jpeg' + } + ]; + + const JobCard = ({ job, index }) => { + const cardContent = ( +
+ {job.featured && ( +
+ + + FEATURED + +
+ )} +
+
+ {`${job.company} +
+
+

{job.title}

+

at {job.company}

+
+
+ + {job.location} +
+ {job.country && ( +
+ + {job.country} +
+ )} + {job.salary && ( +
+ 💰 {job.salary} +
+ )} +
+
+ {job.timeAgo} +
+
+
+
+ ); + + if (shouldReduceMotion) { + return cardContent; + } + + return ( + + {cardContent} + + ); + }; + + const sectionContent = ( +
+
+
+

+ Find your next opportunity +

+

+ Browse through our list of NestJS jobs, find your perfect match and apply. Use filters for more accurate results! +

+ + {/* Newsletter Signup */} +
+

+ Get NestJS jobs right to your inbox +

+

Subscribe to our newsletter to get notified.

+
+ + +
+
+ + {/* Search and Filters */} +
+
+ + setSearchTerm(e.target.value)} + className="pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-pink focus:border-transparent outline-none w-80" + /> +
+
+ + + +
+
+
+ + {/* Active Jobs */} +
+ {jobs.map((job, index) => ( + + ))} +
+ + {/* Expired Jobs */} +
+

Expired listings

+
+ {expiredJobs.map((job, index) => ( + + ))} +
+
+
+
+ ); + + if (shouldReduceMotion) { + return sectionContent; + } + + return ( + + {sectionContent} + + ); +}; + +export default JobsSection; \ No newline at end of file diff --git a/src/components/Newsletter.js b/src/components/Newsletter.js new file mode 100644 index 0000000..a13ec4a --- /dev/null +++ b/src/components/Newsletter.js @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; +import { motion, useReducedMotion } from 'framer-motion'; +import { ArrowRight } from 'lucide-react'; + +const fadeUpPreset = (delay = 0, duration = 1.2) => ({ + initial: { opacity: 0, y: 20 }, + whileInView: { opacity: 1, y: 0 }, + viewport: { once: true, amount: 0.2 }, + transition: { delay, duration, ease: "easeOut" } +}); + +const Newsletter = () => { + const shouldReduceMotion = useReducedMotion(); + const [email, setEmail] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + // Handle newsletter signup + console.log('Newsletter signup:', email); + }; + + const sectionContent = ( +
+
+

+ Join our Newsletter +

+

+ Subscribe to stay up to date with the latest Nest updates, features, and videos! +

+ +
+ setEmail(e.target.value)} + className="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-brand-pink focus:border-transparent outline-none" + required + /> + +
+
+
+ ); + + if (shouldReduceMotion) { + return sectionContent; + } + + return ( + + {sectionContent} + + ); +}; + +export default Newsletter; \ No newline at end of file diff --git a/src/components/SupportSection.js b/src/components/SupportSection.js new file mode 100644 index 0000000..f343833 --- /dev/null +++ b/src/components/SupportSection.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { motion, useReducedMotion } from 'framer-motion'; + +const fadeUpPreset = (delay = 0, duration = 1.2) => ({ + initial: { opacity: 0, y: 20 }, + whileInView: { opacity: 1, y: 0 }, + viewport: { once: true, amount: 0.2 }, + transition: { delay, duration, ease: "easeOut" } +}); + +const SupportSection = () => { + const shouldReduceMotion = useReducedMotion(); + + const sectionContent = ( +
+
+

+ Does your team need additional support? +

+

+ Nest core team members can work directly with your team on a daily basis to help take your project to the next-level. Let us partner with you and your team to develop the most ambitious projects. +

+ +
+
+ ); + + if (shouldReduceMotion) { + return sectionContent; + } + + return ( + + {sectionContent} + + ); +}; + +export default SupportSection; \ No newline at end of file diff --git a/src/components/TeamAugmentation.js b/src/components/TeamAugmentation.js new file mode 100644 index 0000000..a33bdc4 --- /dev/null +++ b/src/components/TeamAugmentation.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { motion, useReducedMotion } from 'framer-motion'; + +const fadeUpPreset = (delay = 0, duration = 1.2) => ({ + initial: { opacity: 0, y: 20 }, + whileInView: { opacity: 1, y: 0 }, + viewport: { once: true, amount: 0.2 }, + transition: { delay, duration, ease: "easeOut" } +}); + +const TeamAugmentation = () => { + const shouldReduceMotion = useReducedMotion(); + + const sectionContent = ( +
+
+
+

+ Team augmentation. By your side at every step +

+

+ Nest core team members can work directly with your team on a daily basis to help take your project to the next-level. Let us partner with you and your team to develop the most ambitious projects. +

+ +
+
+ ); + + if (shouldReduceMotion) { + return sectionContent; + } + + return ( + + {sectionContent} + + ); +}; + +export default TeamAugmentation; \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..9462bea --- /dev/null +++ b/src/index.css @@ -0,0 +1,41 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + * { + @apply box-border; + } + + body { + @apply font-sans antialiased; + } +} + +@layer components { + .btn-primary { + @apply bg-brand-pink hover:bg-brand-pink-dark text-white font-medium px-6 py-3 rounded-full transition-colors duration-200; + } + + .btn-secondary { + @apply border-2 border-white text-white hover:bg-white hover:text-gray-900 font-medium px-6 py-3 rounded-full transition-all duration-200; + } + + .job-card { + @apply bg-white rounded-lg border border-gray-200 p-6 hover:shadow-lg transition-shadow duration-200; + } + + .job-card-featured { + @apply bg-gradient-to-r from-purple-50 to-pink-50 border-purple-200; + } + + .company-logo { + @apply w-12 h-12 object-contain grayscale hover:grayscale-0 transition-all duration-300; + } +} + +@layer utilities { + .text-gradient { + @apply bg-gradient-to-r from-brand-pink to-purple-600 bg-clip-text text-transparent; + } +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..0881df3 --- /dev/null +++ b/src/index.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; +import App from './App'; + +const container = document.getElementById('root'); +const root = createRoot(container); + +root.render( + + + +); \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..2e5e7b1 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,49 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./src/**/*.{js,jsx,ts,tsx}", + ], + theme: { + extend: { + colors: { + primary: { + 50: '#fef2f2', + 100: '#fee2e2', + 200: '#fecaca', + 300: '#fca5a5', + 400: '#f87171', + 500: '#ef4444', + 600: '#dc2626', + 700: '#b91c1c', + 800: '#991b1b', + 900: '#7f1d1d', + }, + brand: { + pink: '#e91e63', + 'pink-dark': '#c2185b', + 'pink-light': '#f06292', + }, + gray: { + 50: '#f9fafb', + 100: '#f3f4f6', + 200: '#e5e7eb', + 300: '#d1d5db', + 400: '#9ca3af', + 500: '#6b7280', + 600: '#4b5563', + 700: '#374151', + 800: '#1f2937', + 900: '#111827', + } + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], + }, + backgroundImage: { + 'hero-pattern': "url('https://www.jobs.nestjs.com/assets/header-2-CGRpDzPN.jpg')", + 'support-pattern': "url('https://www.jobs.nestjs.com/assets/support-ChYF6me3.jpg')", + } + }, + }, + plugins: [], +} \ No newline at end of file diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..760984a --- /dev/null +++ b/vercel.json @@ -0,0 +1,5 @@ +{ + "installCommand": "npm install", + "buildCommand": "CI=false npm run build", + "outputDirectory": "build" +} \ No newline at end of file