├── 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 |

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 |
21 |
22 | {projects.map((project) => {
23 | const { id, img, url, title } = project;
24 | return (
25 |
32 |
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 |
--------------------------------------------------------------------------------