Skip to content

Commit

Permalink
Added Blogs Page
Browse files Browse the repository at this point in the history
navbar
  • Loading branch information
deveshsawant05 authored Aug 10, 2024
1 parent a53050d commit e51724a
Show file tree
Hide file tree
Showing 11 changed files with 535 additions and 0 deletions.
Binary file added public/blog_cover_poster.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
167 changes: 167 additions & 0 deletions src/app/blogs/components/blogCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React, { useState } from "react";
import Image from "next/image";
import Link from "next/link";

import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"

import { Montserrat } from "next/font/google";
const montserratFont = Montserrat({ weight: ["100", "200", "400", "500", "600"], subsets: ["latin"] });

export default function BlogCard(props) {
const [blog, setBlog] = useState(props.blog);
const [blogPosterUrl, setBlogPosterUrl] = useState(blog.posterUrl);
const [blogTitle, setBlogTitle] = useState(blog.title);
const [blogIntro, setBlogIntro] = useState(blog.intro);
const [creatorName, setCreatorName] = useState("Devesh Sawant")
const [createdAt, setCreatedAt] = useState(blog.created_at);
const [blogPoster, setblogPoster] = useState("/event_poster.avif");
const [blogLikes, setBlogLikes] = useState("10");
const [blogComments, setBlogComments] = useState("10");

console.log(trimString(blogTitle, 45));

return (
<div className="blogs-card grid lg:grid-cols-2 rounded-2xl shadow-sm space-y-4 lg:space-y-0 bg-secondary m-2 p-6">
<Link href={`/blog/${blog.id}`}>
<div className="blogs-poster-div col-span-1 rounded-xl">
<Image className="blogs-poster" src={blogPoster} width={500} height={500} alt="Blog Poster"></Image>
</div>
</Link>

<div className="blogs-details-div col-span-1 ">
<div className="blogs-details-container-1 mb-1.5 mr-3 flex justify-between content-between">
<p className={`blogs-date ${montserratFont.className}`}>{formatDate(new Date(createdAt))}</p>
<ShareButton href={`/blog/${blog.id}`} />
</div>

<div className="blogs-details-container-2" href={`/blog/${blog.id}`}>
<Link href={`/blog/${blog.id}` } className="h-full">
<div className="blogs-title-div">
<div></div>
<p className={`blogs-title ${montserratFont.className}`}>{trimString(blogTitle, 45)}</p>
</div>
<p className={`blogs-intro ${montserratFont.className}`}>{trimString(blogIntro, 200)}</p>
</Link>
<div className={`blogs-data ${montserratFont.className}`}>
<hr />
<div className="flex items-center justify-between mt-2">
<div className="flex items-center gap-2 ml-4">
<Avatar className="h-8 w-8">
<AvatarImage src="/placeholder-user.jpg" />
</Avatar>
<p>{creatorName}</p>
</div>
<div className="likes-comments-container flex gap-3">
<div className="likes-container flex gap-[0.1rem] items-center">
<LikeIcon />
<p>{blogLikes}</p>
</div>
<div className="comments-container flex gap-[0.1rem] items-center">
<CommentIcon />
<p>{blogComments}</p>
</div>
</div>
</div>
</div>
</div>

</div>
</div>
)
}


function trimString(str, length) {
// Use Intl.Segmenter to handle grapheme clusters (e.g., emojis)
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
const segments = segmenter.segment(str);

// Convert segments iterator to an array of strings
const graphemes = Array.from(segments, segment => segment.segment);

// Check if truncation is needed
if (graphemes.length > length) {
return graphemes.slice(0, length).join('') + "...";
}

// Return the original string if no truncation is needed
return str;
}


function formatDate(date) {
// Array of month names
const monthNames = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sept", "Oct", "Nov", "Dec"
];

// Extract day, month, and year from the date
const day = date.getDate();
const month = monthNames[date.getMonth()];
const year = date.getFullYear();

// Format the date
return `${day} ${month}, ${year}`;
}



const ShareButton = (props) => {
const handleShare = async () => {
if (navigator.share) {
try {
await navigator.share({
title: 'Check this out!',
text: 'This is an awesome React app.',
url: window.location.origin + props.href
});

} catch (error) {
console.error('Error sharing:', error);
}
} else {
console.log('Web Share API not supported');
// Optionally handle fallback here
}
};

return (
<button onClick={handleShare} className="share-button">
<ShareIcon />
</button>
);
};

const ShareIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512" id="share"
width={20} height={20} fill="currentColor"
>
<path d="M386.445,182.626A76.868,76.868,0,1,0,319.09,142.7L186.127,212.03a76.8,76.8,0,1,0-1.057,95.648l130.876,68.045a77.114,77.114,0,1,0,10.313-17.179L195.613,290.62a76.659,76.659,0,0,0,.695-61.342L331.1,158.994A76.578,76.578,0,0,0,386.445,182.626Z"></path>
</svg>
);


const CommentIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor"
viewBox="0 0 32 32"
width={20} height={20} id="comment"
>
<path d="M5.078 24.482A19.813 19.813 0 0 1 1.812 30c3.198 0 7.312-.42 10.482-2.364A19.52 19.52 0 0 0 16 28c8.836 0 16-5.82 16-13S24.836 2 16 2 0 7.82 0 15c0 3.744 1.96 7.11 5.078 9.482z"></path>
</svg>
);


const LikeIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
version="1"
fill="currentColor"
id="heart"
>
<path d="M2.2 9.4c0 1.3.2 3.3 2 5.1 1.6 1.6 6.9 5.2 7.1 5.4.2.1.4.2.6.2s.4-.1.6-.2c.2-.2 5.5-3.7 7.1-5.4 1.8-1.8 2-3.8 2-5.1 0-3-2.4-5.4-5.4-5.4-1.6 0-3.2.9-4.2 2.3C11 4.9 9.4 4 7.6 4 4.7 4 2.2 6.4 2.2 9.4z"></path>
</svg>
);
10 changes: 10 additions & 0 deletions src/app/blogs/components/blogCoverPoster.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

import Image from 'next/image';
export default function Blogs() {
const blogCoverImage = "/blog_cover_poster.png";
return(
<div className='lg:100vh w-full'>
<img src={blogCoverImage} className='lg:100vh w-full' alt="Blog cover poster" />
</div>
)
}
11 changes: 11 additions & 0 deletions src/app/blogs/components/comment.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const LikeIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
version="1"
id="heart"
>
<path d="M2.2 9.4c0 1.3.2 3.3 2 5.1 1.6 1.6 6.9 5.2 7.1 5.4.2.1.4.2.6.2s.4-.1.6-.2c.2-.2 5.5-3.7 7.1-5.4 1.8-1.8 2-3.8 2-5.1 0-3-2.4-5.4-5.4-5.4-1.6 0-3.2.9-4.2 2.3C11 4.9 9.4 4 7.6 4 4.7 4 2.2 6.4 2.2 9.4z"></path>
</svg>
);
23 changes: 23 additions & 0 deletions src/app/blogs/components/generateBlogCards.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { useEffect } from "react";
import BlogCard from "./blogCard";

import "@/styles/blogs.css"

import { Montserrat,Alata} from "next/font/google";
const alataFont = Alata({weight: ["400"], subsets: ["latin"]});
const montserratFont = Montserrat({weight: ["100","200","400","600"], subsets: ["latin"]});

export default function GenerateBlogCards(props){
const blogs = props.blogs;

return(
<div className="blogs-div">
<div className="blogs-container">
<p className={`blogs-heading-title ${alataFont.className}`}>Blogs</p>
<div className="grid md:grid-cols-2 ">
{blogs.map((blog)=><BlogCard key={blog.id} blog={blog}/>)}
</div>
</div>
</div>
)
}
1 change: 1 addition & 0 deletions src/app/blogs/components/heart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions src/app/blogs/fetchBlogs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useState, useEffect } from 'react';
import axios from 'axios';

const useFetchBlog = (currentPage) => {
const [blogs, setBlogs] = useState({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
setLoading(true);
const fetchBlog = async () => {
try {
const response = await axios.get(`/api/v1/get/blogs?page=${currentPage}`);
const result = response.data;
setBlogs(result.blogs);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
};

fetchBlog();
},[currentPage]);

return { blogs, loading, error };
};

export default useFetchBlog;
18 changes: 18 additions & 0 deletions src/app/blogs/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import Navbar from "@/components/navbar"
import Footer from "@/components/footer"


interface RootLayoutProps {
children: React.ReactNode;
}

export default function RootLayout({ children } : RootLayoutProps) {
return (
<>
<Navbar />
{children}
<Footer />
</>
)
}
123 changes: 123 additions & 0 deletions src/app/blogs/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
'use client'
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import useFetchBlog from './fetchBlogs';
import Loader from '@/components/ui/loader';
import Image from 'next/image';
import Link from 'next/link';

import BlogPosterCover from "./components/blogCoverPoster";
import GenerateBlogCards from './components/generateBlogCards';

import { Montserrat} from "next/font/google";
const montserratFont = Montserrat({weight: ["100","200","400","600"], subsets: ["latin"]});


export default function Blogs() {
const router = useRouter();

const [currentPage, setCurrentPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [blogsArray, setBlogsArray] = useState([]);

const [searchInputValue , setSearchInputValue ] = useState("");
const { blogs, loading, error } = useFetchBlog(currentPage);

useEffect(() => {
if (!loading && blogs) {
setBlogsArray(prevBlogs => {
// Create a new array by filtering out blogs that already exist in the array based on a unique identifier
const newBlogs = blogs.filter(blog => !prevBlogs.some(prevBlog => prevBlog.id === blog.id));
return [...prevBlogs, ...newBlogs];
});

if (blogs.length < 5) {
setHasMore(false);
}
}
}, [blogs, loading]);

if (error) {
console.error(error);
router.push('/');
return null;
}

const handlePageChange = () => {
if (hasMore) {
setCurrentPage(prevPage => prevPage + 1);
}
};



// const blogsArray =[
// {
// "id": 24,
// "title": "Seventh Blog",
// "intro": "This is the seventh blog",
// "writer": "b4e05b86-df08-49d8-a118-51c205216401",
// "created_at": "2024-07-26T07:33:28.697994+00:00",
// "posterUrl": "undefined/web_data/images/24/poster",
// "blogFileUrl": "undefined/web_data/blogs/24/blog",
// "images": []
// },
// {
// "id": 25,
// "title": "ReactJS Tic-Tac-Toe (💥Passed Job Interview 😂 ) (💥Passed Job Interview💥)",
// "intro": "From bugs to brilliance: the journey of a coder. From bugs to brilliance: the journey of a coder. From bugs to brilliance: the journey of a coder. From bugs to brilliance: the journey of a coder. From bugs to brilliance: the journey of a coder. From bugs to brilliance: the journey of a coder",
// "writer": "b4e05b86-df08-49d8-a118-51c205216401",
// "created_at": "2024-06-14T07:33:28.697994+00:00",
// "posterUrl": "undefined/web_data/images/24/poster",
// "blogFileUrl": "undefined/web_data/blogs/24/blog",
// "images": ["https://i.ibb.co/3RWZ6rF/image.png",
// "https://cdn.pixabay.com/photo/2024/05/16/20/20/digital-8766937_1280.png",
// "https://cdn.pixabay.com/photo/2024/05/20/13/28/ai-generated-8775234_1280.png"
// ]
// }
// ]
const handleSearchInputChange = (event) => {
setSearchInputValue(event.target.value);
};

return (
<>
<div className='flex flex-col items-center'>
<BlogPosterCover />
<div className="blogs-search-div">
<input type="text" value={searchInputValue} className={`${montserratFont.className} blogs-search-input`}
placeholder='Search...'
onChange={handleSearchInputChange}/>
<Link href="#" ><SearchIcon/></Link>
</div>
</div>
<GenerateBlogCards blogs={blogsArray} />

{loading?<div className="w-full flex justify-center p-12"><p className={`text-xl px-5 ${montserratFont.className}`}>Loading... </p><Loader /></div>:
hasMore?
<div className={`${montserratFont.className} w-full flex justify-center`}>
<button className="show-more-button" onClick={() => handlePageChange(currentPage + 1)} >
<p>Show More</p>
</button>
</div>
:null
}
</>
)
}

const SearchIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 128 128"
width={30}
height={30}
fill='currentColor'
id="search"
>
<path fill="none" d="M0 0h128v128H0z"></path>
<path d="M29.18 9.265C8.587 23.684 3.575 52.11 17.994 72.703c14.42 20.593 42.845 25.604 63.438 11.185 20.593-14.42 25.605-42.844 11.186-63.437C78.198-.142 49.773-5.155 29.18 9.265Zm2.868 4.095C50.38.524 75.686 4.985 88.523 23.318c12.836 18.332 8.373 43.638-9.959 56.474-18.332 12.836-43.637 8.375-56.474-9.957-12.837-18.333-8.374-43.638 9.958-56.475Z"></path>
<path d="M29.942 64.338c-9.803-14-6.395-33.323 7.604-43.126a2.502 2.502 0 0 0 .614-3.482 2.502 2.502 0 0 0-3.482-.614c-16.26 11.386-20.217 33.83-8.832 50.09a2.502 2.502 0 0 0 3.482.614 2.502 2.502 0 0 0 .614-3.482ZM78.014 83.364l5.09 7.27a2.502 2.502 0 0 0 3.482.614 2.502 2.502 0 0 0 .614-3.482l-5.09-7.27a2.502 2.502 0 0 0-3.482-.614 2.502 2.502 0 0 0-.614 3.482Z"></path>
<path d="M100.341 92.914a14.841 14.841 0 0 0-15.046-6.045h-.002a3.6 3.6 0 0 0-2.38 1.666v.002a14.841 14.841 0 0 0 .534 16.207l12.357 17.648a9.337 9.337 0 0 0 13.003 2.293l1.6-1.12a9.336 9.336 0 0 0 2.292-13.003L100.34 92.914Zm-13.458-1.242a9.84 9.84 0 0 0 .659 10.204l12.357 17.648a4.34 4.34 0 0 0 6.04 1.066l1.6-1.12a4.339 4.339 0 0 0 1.064-6.04L96.246 95.78a9.839 9.839 0 0 0-9.363-4.109Z"></path>
</svg>
);
Loading

1 comment on commit e51724a

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for iiitvcc ready!

✅ Preview
https://iiitvcc-l9ep5lvpd-iiitv-coding-clubs-projects.vercel.app

Built with commit e51724a.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.