├── src ├── assets │ ├── tours.png │ ├── birthday.png │ ├── reviews.png │ ├── questions.png │ ├── react.svg │ └── hero.svg ├── App.jsx ├── main.jsx ├── data.js ├── Hero.jsx ├── Projects.jsx ├── fetchProjects.jsx └── index.css ├── vite.config.js ├── .gitignore ├── README.md ├── index.html ├── package.json └── public └── vite.svg /src/assets/tours.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/john-smilga/react-vite-projects-16-contentful/HEAD/src/assets/tours.png -------------------------------------------------------------------------------- /src/assets/birthday.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/john-smilga/react-vite-projects-16-contentful/HEAD/src/assets/birthday.png -------------------------------------------------------------------------------- /src/assets/reviews.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/john-smilga/react-vite-projects-16-contentful/HEAD/src/assets/reviews.png -------------------------------------------------------------------------------- /src/assets/questions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/john-smilga/react-vite-projects-16-contentful/HEAD/src/assets/questions.png -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import Hero from './Hero'; 2 | import Projects from './Projects'; 3 | 4 | const App = () => { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | }; 12 | export default App; 13 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ```js 2 | const contentful = require('contentful'); 3 | 4 | const client = contentful.createClient({ 5 | space: 'qz00uzgg3leh', 6 | environment: 'master', // defaults to 'master' if not set 7 | accessToken: 'your token', 8 | }); 9 | 10 | client 11 | .getEntries() 12 | .then((response) => console.log(response.items)) 13 | .catch(console.error); 14 | ``` 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Contentful 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contentful", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "contentful": "^9.3.5", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.28", 18 | "@types/react-dom": "^18.0.11", 19 | "@vitejs/plugin-react": "^3.1.0", 20 | "vite": "^4.2.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | const projects = [ 2 | { 3 | title: 'birthday buddy', 4 | url: 'https://react-vite-projects-1-birthday-buddy.netlify.app/', 5 | image: './assets/birthday.png', 6 | }, 7 | { 8 | title: 'tours', 9 | url: 'https://react-vite-projects-2-tours.netlify.app/', 10 | image: './assets/tours.png', 11 | }, 12 | { 13 | title: 'reviews', 14 | url: 'https://react-vite-projects-3-reviews.netlify.app/', 15 | image: './assets/reviews.png', 16 | }, 17 | { 18 | title: 'questions', 19 | url: 'https://react-vite-projects-4-accordion.netlify.app/', 20 | image: './assets/questions.png', 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /src/Hero.jsx: -------------------------------------------------------------------------------- 1 | import heroImg from './assets/hero.svg'; 2 | const Hero = () => { 3 | return ( 4 |
5 |
6 |
7 |

contentful CMS

8 |

9 | Pitchfork schlitz tonx, coloring book celiac tousled succulents 10 | ascot affogato cardigan jianbing crucifix seitan. Synth man braid 11 | everyday carry try-hard pour-over keffiyeh slow-carb sriracha 12 | chillwave banjo gochujang kinfolk small batch mustache. 13 |

14 |
15 |
16 | woman and the browser 17 |
18 |
19 |
20 | ); 21 | }; 22 | export default Hero; 23 | -------------------------------------------------------------------------------- /src/Projects.jsx: -------------------------------------------------------------------------------- 1 | import useFetchProjects from './fetchProjects'; 2 | 3 | const Projects = () => { 4 | const { loading, projects } = useFetchProjects(); 5 | 6 | if (loading) { 7 | return ( 8 |
9 |
10 |

loading...

11 |
12 |
13 | ); 14 | } 15 | return ( 16 |
17 |
18 |

Projects

19 |
20 |
21 |
22 | {projects.map((project) => { 23 | const { id, img, url, title } = project; 24 | return ( 25 | 32 | title 33 |
{title}
34 |
35 | ); 36 | })} 37 |
38 |
39 | ); 40 | }; 41 | export default Projects; 42 | -------------------------------------------------------------------------------- /src/fetchProjects.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { createClient } from 'contentful'; 3 | 4 | const client = createClient({ 5 | space: 'qz00uzgg3leh', 6 | environment: 'master', // defaults to 'master' if not set 7 | accessToken: import.meta.env.VITE_API_KEY, 8 | }); 9 | 10 | const useFetchProjects = () => { 11 | const [loading, setLoading] = useState(true); 12 | const [projects, setProjects] = useState([]); 13 | 14 | const getData = async () => { 15 | try { 16 | const response = await client.getEntries({ 17 | content_type: 'projects', 18 | }); 19 | const projects = response.items.map((item) => { 20 | const { title, url, image } = item.fields; 21 | const id = item.sys.id; 22 | const img = image?.fields?.file?.url; 23 | return { title, url, id, img }; 24 | }); 25 | setLoading(false); 26 | setProjects(projects); 27 | } catch (error) { 28 | console.log(error); 29 | setLoading(false); 30 | } 31 | }; 32 | 33 | useEffect(() => { 34 | getData(); 35 | }, []); 36 | return { loading, projects }; 37 | }; 38 | 39 | export default useFetchProjects; 40 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* ============= GLOBAL CSS =============== */ 2 | 3 | *, 4 | ::after, 5 | ::before { 6 | margin: 0; 7 | padding: 0; 8 | box-sizing: border-box; 9 | } 10 | 11 | html { 12 | font-size: 100%; 13 | } /*16px*/ 14 | 15 | :root { 16 | /* colors */ 17 | --primary-100: #e2e0ff; 18 | --primary-200: #c1beff; 19 | --primary-300: #a29dff; 20 | --primary-400: #837dff; 21 | --primary-500: #645cff; 22 | --primary-600: #504acc; 23 | --primary-700: #3c3799; 24 | --primary-800: #282566; 25 | --primary-900: #141233; 26 | 27 | /* grey */ 28 | --grey-50: #f8fafc; 29 | --grey-100: #f1f5f9; 30 | --grey-200: #e2e8f0; 31 | --grey-300: #cbd5e1; 32 | --grey-400: #94a3b8; 33 | --grey-500: #64748b; 34 | --grey-600: #475569; 35 | --grey-700: #334155; 36 | --grey-800: #1e293b; 37 | --grey-900: #0f172a; 38 | /* rest of the colors */ 39 | --black: #222; 40 | --white: #fff; 41 | --red-light: #f8d7da; 42 | --red-dark: #842029; 43 | --green-light: #d1e7dd; 44 | --green-dark: #0f5132; 45 | 46 | --small-text: 0.875rem; 47 | --extra-small-text: 0.7em; 48 | /* rest of the vars */ 49 | --backgroundColor: var(--grey-50); 50 | --textColor: var(--grey-900); 51 | --borderRadius: 0.25rem; 52 | --letterSpacing: 1px; 53 | --transition: 0.3s ease-in-out all; 54 | --max-width: 1120px; 55 | --fixed-width: 600px; 56 | --view-width: 90vw; 57 | /* box shadow*/ 58 | --shadow-1: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); 59 | --shadow-2: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 60 | 0 2px 4px -1px rgba(0, 0, 0, 0.06); 61 | --shadow-3: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 62 | 0 4px 6px -2px rgba(0, 0, 0, 0.05); 63 | --shadow-4: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 64 | 0 10px 10px -5px rgba(0, 0, 0, 0.04); 65 | } 66 | 67 | body { 68 | background: var(--backgroundColor); 69 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 70 | Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 71 | font-weight: 400; 72 | line-height: 1; 73 | color: var(--textColor); 74 | } 75 | p { 76 | margin: 0; 77 | } 78 | h1, 79 | h2, 80 | h3, 81 | h4, 82 | h5 { 83 | margin: 0; 84 | font-family: var(--headingFont); 85 | font-weight: 400; 86 | line-height: 1; 87 | text-transform: capitalize; 88 | letter-spacing: var(--letterSpacing); 89 | } 90 | 91 | h1 { 92 | font-size: clamp(2rem, 5vw, 5rem); /* Large heading */ 93 | } 94 | 95 | h2 { 96 | font-size: clamp(1.5rem, 3vw, 3rem); /* Medium heading */ 97 | } 98 | 99 | h3 { 100 | font-size: clamp(1.25rem, 2.5vw, 2.5rem); /* Small heading */ 101 | } 102 | 103 | h4 { 104 | font-size: clamp(1rem, 2vw, 2rem); /* Extra small heading */ 105 | } 106 | 107 | h5 { 108 | font-size: clamp(0.875rem, 1.5vw, 1.5rem); /* Tiny heading */ 109 | } 110 | 111 | /* BIGGER FONTS */ 112 | /* h1 { 113 | font-size: clamp(3rem, 6vw, 6rem); 114 | } 115 | 116 | h2 { 117 | font-size: clamp(2.5rem, 5vw, 5rem); 118 | } 119 | 120 | h3 { 121 | font-size: clamp(2rem, 4vw, 4rem); 122 | } 123 | 124 | h4 { 125 | font-size: clamp(1.5rem, 3vw, 3rem); 126 | } 127 | 128 | h5 { 129 | font-size: clamp(1rem, 2vw, 2rem); 130 | } 131 | */ 132 | 133 | .text { 134 | margin-bottom: 1.5rem; 135 | max-width: 40em; 136 | } 137 | 138 | small, 139 | .text-small { 140 | font-size: var(--small-text); 141 | } 142 | 143 | a { 144 | text-decoration: none; 145 | } 146 | ul { 147 | list-style-type: none; 148 | padding: 0; 149 | } 150 | 151 | .img { 152 | width: 100%; 153 | display: block; 154 | object-fit: cover; 155 | } 156 | /* buttons */ 157 | 158 | .btn { 159 | cursor: pointer; 160 | color: var(--white); 161 | background: var(--primary-500); 162 | border: transparent; 163 | border-radius: var(--borderRadius); 164 | letter-spacing: var(--letterSpacing); 165 | padding: 0.375rem 0.75rem; 166 | box-shadow: var(--shadow-1); 167 | transition: var(--transition); 168 | text-transform: capitalize; 169 | display: inline-block; 170 | } 171 | .btn:hover { 172 | background: var(--primary-700); 173 | box-shadow: var(--shadow-3); 174 | } 175 | .btn-hipster { 176 | color: var(--primary-500); 177 | background: var(--primary-200); 178 | } 179 | .btn-hipster:hover { 180 | color: var(--primary-200); 181 | background: var(--primary-700); 182 | } 183 | .btn-block { 184 | width: 100%; 185 | } 186 | 187 | /* alerts */ 188 | .alert { 189 | padding: 0.375rem 0.75rem; 190 | margin-bottom: 1rem; 191 | border-color: transparent; 192 | border-radius: var(--borderRadius); 193 | } 194 | 195 | .alert-danger { 196 | color: var(--red-dark); 197 | background: var(--red-light); 198 | } 199 | .alert-success { 200 | color: var(--green-dark); 201 | background: var(--green-light); 202 | } 203 | /* form */ 204 | 205 | .form { 206 | width: 90vw; 207 | max-width: var(--fixed-width); 208 | background: var(--white); 209 | border-radius: var(--borderRadius); 210 | box-shadow: var(--shadow-2); 211 | padding: 2rem 2.5rem; 212 | margin: 3rem auto; 213 | } 214 | .form-label { 215 | display: block; 216 | font-size: var(--small-text); 217 | margin-bottom: 0.5rem; 218 | text-transform: capitalize; 219 | letter-spacing: var(--letterSpacing); 220 | } 221 | .form-input, 222 | .form-textarea { 223 | width: 100%; 224 | padding: 0.375rem 0.75rem; 225 | border-radius: var(--borderRadius); 226 | background: var(--backgroundColor); 227 | border: 1px solid var(--grey-200); 228 | } 229 | 230 | .form-row { 231 | margin-bottom: 1rem; 232 | } 233 | 234 | .form-textarea { 235 | height: 7rem; 236 | } 237 | ::placeholder { 238 | font-family: inherit; 239 | color: var(--grey-400); 240 | } 241 | .form-alert { 242 | color: var(--red-dark); 243 | letter-spacing: var(--letterSpacing); 244 | text-transform: capitalize; 245 | } 246 | /* alert */ 247 | 248 | @keyframes spinner { 249 | to { 250 | transform: rotate(360deg); 251 | } 252 | } 253 | 254 | .loading { 255 | width: 6rem; 256 | height: 6rem; 257 | border: 5px solid var(--grey-400); 258 | border-radius: 50%; 259 | border-top-color: var(--primary-500); 260 | animation: spinner 0.6s linear infinite; 261 | margin: 0 auto; 262 | } 263 | 264 | /* title */ 265 | 266 | .title { 267 | text-align: center; 268 | } 269 | 270 | .title-underline { 271 | background: var(--primary-500); 272 | width: 4rem; 273 | height: 0.25rem; 274 | margin: 0 auto; 275 | margin-top: 1rem; 276 | } 277 | 278 | /* 279 | ============= 280 | PROJECT CSS 281 | ============= 282 | */ 283 | 284 | body { 285 | background: var(--grey-200); 286 | } 287 | .hero { 288 | min-height: 40vh; 289 | background: var(--white); 290 | display: flex; 291 | align-items: center; 292 | justify-content: center; 293 | padding: 5rem 0; 294 | } 295 | 296 | .img-container { 297 | display: none; 298 | } 299 | 300 | .hero-center { 301 | width: 90vw; 302 | max-width: var(--max-width); 303 | } 304 | 305 | .hero-title h1 { 306 | margin-bottom: 2rem; 307 | font-weight: 700; 308 | } 309 | .hero-title p { 310 | line-height: 2; 311 | max-width: 35em; 312 | color: var(--grey-600); 313 | } 314 | 315 | @media screen and (min-width: 992px) { 316 | .hero-center { 317 | display: grid; 318 | grid-template-columns: 2fr 1fr; 319 | place-items: center; 320 | gap: 4rem; 321 | } 322 | .img-container { 323 | display: block; 324 | } 325 | } 326 | /* 327 | ============= 328 | PROJECTS 329 | ============= 330 | */ 331 | 332 | .projects { 333 | padding: 5rem 0; 334 | } 335 | .projects-center { 336 | margin: 0 auto; 337 | margin-top: 3rem; 338 | width: 90vw; 339 | max-width: var(--max-width); 340 | display: grid; 341 | gap: 2rem; 342 | } 343 | .project { 344 | background: var(--white); 345 | display: block; 346 | border-radius: var(--borderRadius); 347 | box-shadow: var(--shadow-1); 348 | transition: var(--transition); 349 | } 350 | .project:hover { 351 | transform: scale(1.05); 352 | box-shadow: var(--shadow-2); 353 | } 354 | .project .img { 355 | height: 15rem; 356 | border-top-right-radius: var(--borderRadius); 357 | border-top-left-radius: var(--borderRadius); 358 | } 359 | .project h5 { 360 | text-align: center; 361 | padding: 1rem 0; 362 | color: var(--grey-700); 363 | } 364 | 365 | @media screen and (min-width: 768px) { 366 | .projects-center { 367 | grid-template-columns: 1fr 1fr; 368 | } 369 | } 370 | @media screen and (min-width: 992px) { 371 | .projects-center { 372 | grid-template-columns: 1fr 1fr 1fr; 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /src/assets/hero.svg: -------------------------------------------------------------------------------- 1 | Load_more --------------------------------------------------------------------------------