Skip to content

Commit 3f2a634

Browse files
Version 1 🚀🕺
1 parent 1515fc3 commit 3f2a634

32 files changed

+1572
-85
lines changed

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ yarn-debug.log*
2525
yarn-error.log*
2626

2727
# local env files
28+
.env
2829
.env.local
2930
.env.development.local
3031
.env.test.local

‎components/Author.jsx‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
import Image from 'next/image';
3+
4+
const Author = ({ author }) => (
5+
<div className="text-center mt-20 mb-8 p-12 relative rounded-lg bg-black bg-opacity-20">
6+
<div className="absolute left-0 right-0 -top-14">
7+
<img
8+
alt={author.name}
9+
height="100px"
10+
width="100px"
11+
className="align-middle rounded-full"
12+
src={author.photo.url}
13+
/>
14+
</div>
15+
<h3 className="text-white mt-4 mb-4 text-xl font-bold">{author.name}</h3>
16+
<p className="text-white text-ls">{author.bio}</p>
17+
</div>
18+
);
19+
20+
export default Author;

‎components/Categories.jsx‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React, { useState, useEffect } from 'react';
2+
import Link from 'next/link';
3+
4+
import { getCategories } from '../services';
5+
6+
const Categories = () => {
7+
const [categories, setCategories] = useState([]);
8+
9+
useEffect(() => {
10+
getCategories().then((newCategories) => {
11+
setCategories(newCategories);
12+
});
13+
}, []);
14+
15+
return (
16+
<div className="bg-white shadow-lg rounded-lg p-8 pb-12 mb-8">
17+
<h3 className="text-xl mb-8 font-semibold border-b pb-4">Categories</h3>
18+
{categories.map((category, index) => (
19+
<Link key={index} href={`/category/${category.slug}`}>
20+
<span className={`cursor-pointer block ${(index === categories.length - 1) ? 'border-b-0' : 'border-b'} pb-3 mb-3`}>{category.name}</span>
21+
</Link>
22+
))}
23+
</div>
24+
);
25+
};
26+
27+
export default Categories;

‎components/Comments.jsx‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React, { useEffect, useState } from 'react';
2+
import moment from 'moment';
3+
import parse from 'html-react-parser';
4+
5+
import { getComments } from '../services';
6+
7+
const Comments = ({ slug }) => {
8+
const [comments, setComments] = useState([]);
9+
10+
useEffect(() => {
11+
getComments(slug).then((result) => {
12+
setComments(result);
13+
});
14+
}, []);
15+
16+
return (
17+
<>
18+
{comments.length > 0 && (
19+
<div className="bg-white shadow-lg rounded-lg p-8 pb-12 mb-8">
20+
<h3 className="text-xl mb-8 font-semibold border-b pb-4">
21+
{comments.length}
22+
{' '}
23+
Comments
24+
</h3>
25+
{comments.map((comment, index) => (
26+
<div key={index} className="border-b border-gray-100 mb-4 pb-4">
27+
<p className="mb-4">
28+
<span className="font-semibold">{comment.name}</span>
29+
{' '}
30+
on
31+
{' '}
32+
{moment(comment.createdAt).format('MMM DD, YYYY')}
33+
</p>
34+
<p className="whitespace-pre-line text-gray-600 w-full">{parse(comment.comment)}</p>
35+
</div>
36+
))}
37+
</div>
38+
)}
39+
</>
40+
);
41+
};
42+
43+
export default Comments;
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { submitComment } from '../services';
3+
4+
const CommentsForm = ({ slug }) => {
5+
const [error, setError] = useState(false);
6+
const [localStorage, setLocalStorage] = useState(null);
7+
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
8+
const [formData, setFormData] = useState({ name: null, email: null, comment: null, storeData: false });
9+
10+
useEffect(() => {
11+
setLocalStorage(window.localStorage);
12+
const initalFormData = {
13+
name: window.localStorage.getItem('name'),
14+
email: window.localStorage.getItem('email'),
15+
storeData: window.localStorage.getItem('name') || window.localStorage.getItem('email'),
16+
};
17+
setFormData(initalFormData);
18+
}, []);
19+
20+
const onInputChange = (e) => {
21+
const { target } = e;
22+
if (target.type === 'checkbox') {
23+
setFormData((prevState) => ({
24+
...prevState,
25+
[target.name]: target.checked,
26+
}));
27+
} else {
28+
setFormData((prevState) => ({
29+
...prevState,
30+
[target.name]: target.value,
31+
}));
32+
}
33+
};
34+
35+
const handlePostSubmission = () => {
36+
setError(false);
37+
const { name, email, comment, storeData } = formData;
38+
if (!name || !email || !comment) {
39+
setError(true);
40+
return;
41+
}
42+
const commentObj = {
43+
name,
44+
email,
45+
comment,
46+
slug,
47+
};
48+
49+
if (storeData) {
50+
localStorage.setItem('name', name);
51+
localStorage.setItem('email', email);
52+
} else {
53+
localStorage.removeItem('name');
54+
localStorage.removeItem('email');
55+
}
56+
57+
submitComment(commentObj)
58+
.then((res) => {
59+
if (res.createComment) {
60+
if (!storeData) {
61+
formData.name = '';
62+
formData.email = '';
63+
}
64+
formData.comment = '';
65+
setFormData((prevState) => ({
66+
...prevState,
67+
...formData,
68+
}));
69+
setShowSuccessMessage(true);
70+
setTimeout(() => {
71+
setShowSuccessMessage(false);
72+
}, 3000);
73+
}
74+
});
75+
};
76+
77+
return (
78+
<div className="bg-white shadow-lg rounded-lg p-8 pb-12 mb-8">
79+
<h3 className="text-xl mb-8 font-semibold border-b pb-4">Leave a Reply</h3>
80+
<div className="grid grid-cols-1 gap-4 mb-4">
81+
<textarea value={formData.comment} onChange={onInputChange} className="p-4 outline-none w-full rounded-lg h-40 focus:ring-2 focus:ring-gray-200 bg-gray-100 text-gray-700" name="comment" placeholder="Comment" />
82+
</div>
83+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
84+
<input type="text" value={formData.name} onChange={onInputChange} className="py-2 px-4 outline-none w-full rounded-lg focus:ring-2 focus:ring-gray-200 bg-gray-100 text-gray-700" placeholder="Name" name="name" />
85+
<input type="email" value={formData.email} onChange={onInputChange} className="py-2 px-4 outline-none w-full rounded-lg focus:ring-2 focus:ring-gray-200 bg-gray-100 text-gray-700" placeholder="Email" name="email" />
86+
</div>
87+
<div className="grid grid-cols-1 gap-4 mb-4">
88+
<div>
89+
<input checked={formData.storeData} onChange={onInputChange} type="checkbox" id="storeData" name="storeData" value="true" />
90+
<label className="text-gray-500 cursor-pointer" htmlFor="storeData"> Save my name, email in this browser for the next time I comment.</label>
91+
</div>
92+
</div>
93+
{error && <p className="text-xs text-red-500">All fields are mandatory</p>}
94+
<div className="mt-8">
95+
<button type="button" onClick={handlePostSubmission} className="transition duration-500 ease hover:bg-indigo-900 inline-block bg-pink-600 text-lg font-medium rounded-full text-white px-8 py-3 cursor-pointer">Post Comment</button>
96+
{showSuccessMessage && <span className="text-xl float-right font-semibold mt-3 text-green-500">Comment submitted for review</span>}
97+
</div>
98+
</div>
99+
);
100+
};
101+
102+
export default CommentsForm;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from 'react';
2+
import moment from 'moment';
3+
import Image from 'next/image';
4+
import Link from 'next/link';
5+
6+
const FeaturedPostCard = ({ post }) => (
7+
<div className="relative h-72">
8+
<div className="absolute rounded-lg bg-center bg-no-repeat bg-cover shadow-md inline-block w-full h-72" style={{ backgroundImage: `url('${post.featuredImage.url}')` }} />
9+
<div className="absolute rounded-lg bg-center bg-gradient-to-b opacity-50 from-gray-400 via-gray-700 to-black w-full h-72" />
10+
<div className="flex flex-col rounded-lg p-4 items-center justify-center absolute w-full h-full">
11+
<p className="text-white mb-4 text-shadow font-semibold text-xs">{moment(post.createdAt).format('MMM DD, YYYY')}</p>
12+
<p className="text-white mb-4 text-shadow font-semibold text-2xl text-center">{post.title}</p>
13+
<div className="flex items-center absolute bottom-5 w-full justify-center">
14+
<img
15+
alt={post.author.name}
16+
height="30px"
17+
width="30px"
18+
className="align-middle drop-shadow-lg rounded-full"
19+
src={post.author.photo.url}
20+
/>
21+
<p className="inline align-middle text-white text-shadow ml-2 font-medium">{post.author.name}</p>
22+
</div>
23+
</div>
24+
<Link href={`/post/${post.slug}`}><span className="cursor-pointer absolute w-full h-full" /></Link>
25+
</div>
26+
);
27+
28+
export default FeaturedPostCard;

‎components/Header.jsx‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { getCategories } from '../services';
3+
import Link from 'next/link';
4+
5+
const Header = () => {
6+
const [categories, setCategories] = useState([]);
7+
8+
useEffect(() => {
9+
getCategories().then((newCategories) => {
10+
setCategories(newCategories);
11+
});
12+
}, []);
13+
return (
14+
<div className='container mx-auto px-10 mb-8'>
15+
<div className='border-b w-full inline-block border-blue-400 py-8'>
16+
<div className='md:float-left block'>
17+
<Link href="/">
18+
<span className='cursor-pointer font-bold text-4xl text-white'>
19+
Cosmox Blogs
20+
</span>
21+
</Link>
22+
<div className='hidden md:float-left md:contents'>
23+
{categories.map((category) => (
24+
<Link key={category.slug} href={`/category/${category.slug}`}>
25+
<span className='md:float-right mt-2 align-middle text-white ml-4 font-semibold cursor-pointer'>
26+
{category.name}
27+
</span>
28+
</Link>
29+
))}
30+
</div>
31+
</div>
32+
</div>
33+
</div>
34+
)
35+
}
36+
37+
export default Header

‎components/Layout.jsx‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react'
2+
import { Header } from './';
3+
4+
const Layout = ({children}) => {
5+
return (
6+
<>
7+
<Header />
8+
{children}
9+
</>
10+
)
11+
}
12+
13+
export default Layout

‎components/Loader.jsx‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
3+
const Loader = () => (
4+
<div className="text-center">
5+
<button
6+
type="button"
7+
className="inline-flex items-center px-4 py-2 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-rose-600 hover:bg-rose-500 focus:border-rose-700 active:bg-rose-700 transition ease-in-out duration-150 cursor-not-allowed"
8+
disabled=""
9+
>
10+
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
11+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
12+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
13+
</svg>
14+
Loading
15+
</button>
16+
</div>
17+
);
18+
19+
export default Loader;

‎components/PostCard.jsx‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from 'react'
2+
import Link from 'next/link';
3+
import moment from 'moment';
4+
5+
const PostCard = ({ post }) => {
6+
console.log(post);
7+
return (
8+
<div className='bg-white shadow-lg rounded-lg p-0 lg:p-8 pb-12 mb-8'>
9+
<div className='relative overflow-hidden shadow-md pb-80 mb-6'>
10+
<img
11+
src={post.featuredImage.url}
12+
alt={post.title}
13+
className='object-top absolute h-80 w-full object-cover shadow-lg rounded-t-lg lg:rounded-lg'
14+
/>
15+
</div>
16+
<h1 className="transition duration-1000 text-center mb-8 cursor-pointer hover:text-blue-600 text-3xl font-semibold">
17+
<Link href={`/post/${post.slug}`}>{post.title}</Link>
18+
</h1>
19+
<div className='block lg:flex text-center items-center justify-center mb-8 w-full'>
20+
<div className='flex items-center justify-center mb-4 lg:mb-0 w-full lg:w-auto mr-8'>
21+
<img
22+
alt={post.author.name}
23+
height="30px"
24+
width="30px"
25+
className="align-middle rounded-full"
26+
src={post.author.photo.url}
27+
/>
28+
<p className="inline align-middle text-gray-700 ml-2 font-medium text-lg">{post.author.name}</p>
29+
</div>
30+
<div className='font-medium text-gray-700'>
31+
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 inline mr-2 text-pink-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
32+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
33+
</svg>
34+
<span className="align-middle">{moment(post.createdAt).format('MMM DD, YYYY')}</span>
35+
</div>
36+
</div>
37+
<p className="text-center text-lg text-gray-700 font-normal px-4 lg:px-20 mb-8">
38+
{post.excerpt}
39+
</p>
40+
<div className="text-center">
41+
<Link href={`/post/${post.slug}`}>
42+
<span className="transition duration-500 ease transform hover:-translate-y-1 inline-block bg-pink-600 text-lg font-medium rounded-full text-white px-8 py-3 cursor-pointer">Continue Reading</span>
43+
</Link>
44+
</div>
45+
</div>
46+
)
47+
}
48+
49+
export default PostCard

0 commit comments

Comments
 (0)