├── .gitignore ├── gatsby-browser.js ├── assets ├── blog1.png ├── gatsby.png ├── graphql.png ├── blog-main.png ├── contentful.png ├── cover_page.jpg ├── firebase.png ├── material-ui.png ├── react.svg └── netlify.svg ├── .vscode └── settings.json ├── src ├── components │ ├── modal │ │ ├── modal.css │ │ └── modal.tsx │ ├── Header │ │ ├── header.css │ │ └── header.tsx │ ├── Footer │ │ ├── footer.css │ │ └── footer.tsx │ ├── Layout │ │ └── Layout.tsx │ ├── BlogList │ │ ├── blogList.css │ │ └── blogList.tsx │ └── Blog │ │ ├── blogs.css │ │ └── blogs.tsx ├── style │ ├── global.css │ └── modules │ │ └── main.module.css ├── context │ ├── modal │ │ ├── modalReducer.tsx │ │ └── modal.tsx │ └── auth │ │ ├── authReducer.js │ │ └── auth.js ├── firebase │ └── firebaseConfig.js └── pages │ ├── about │ ├── about.css │ └── index.tsx │ └── index.tsx ├── LICENSE ├── data.tsx ├── gatsby-config.js ├── package.json ├── gatsby-node.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | node_modules 3 | public 4 | .env -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import "./src/style/global.css"; 2 | -------------------------------------------------------------------------------- /assets/blog1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hinakhadim/blog-in-gatsby/HEAD/assets/blog1.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "compile-hero.disable-compile-files-on-did-save-code": true 3 | } -------------------------------------------------------------------------------- /assets/gatsby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hinakhadim/blog-in-gatsby/HEAD/assets/gatsby.png -------------------------------------------------------------------------------- /assets/graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hinakhadim/blog-in-gatsby/HEAD/assets/graphql.png -------------------------------------------------------------------------------- /assets/blog-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hinakhadim/blog-in-gatsby/HEAD/assets/blog-main.png -------------------------------------------------------------------------------- /assets/contentful.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hinakhadim/blog-in-gatsby/HEAD/assets/contentful.png -------------------------------------------------------------------------------- /assets/cover_page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hinakhadim/blog-in-gatsby/HEAD/assets/cover_page.jpg -------------------------------------------------------------------------------- /assets/firebase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hinakhadim/blog-in-gatsby/HEAD/assets/firebase.png -------------------------------------------------------------------------------- /src/components/modal/modal.css: -------------------------------------------------------------------------------- 1 | .google{ 2 | height: 36px; 3 | margin-right: 10px; 4 | } 5 | -------------------------------------------------------------------------------- /assets/material-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hinakhadim/blog-in-gatsby/HEAD/assets/material-ui.png -------------------------------------------------------------------------------- /src/style/global.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Karla&display=swap'); 2 | 3 | body { 4 | margin: 0; 5 | font-family: 'Karla', sans-serif; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Header/header.css: -------------------------------------------------------------------------------- 1 | .image-Container{ 2 | position: relative; 3 | } 4 | 5 | .image-Container::after{ 6 | content: ''; 7 | position: absolute; 8 | top:0; 9 | left: 0; 10 | width: 100%; 11 | height: 600px; 12 | background-color: rgba(0,0,0,0.4); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Footer/footer.css: -------------------------------------------------------------------------------- 1 | 2 | .footer > p{ 3 | font-size: 12px !important; 4 | padding: 40px 0; 5 | color: #2E4053 !important; 6 | margin:0 !important; 7 | } 8 | 9 | .color{ 10 | color:#2471A3; 11 | font-weight: bold; 12 | } 13 | 14 | .center{ 15 | text-align: center; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/Layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Header from "../Header/header"; 3 | import { Footer} from '../Footer/footer'; 4 | 5 | export default function Layout({ children }) { 6 | return ( 7 |
8 |
9 | {children} 10 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/context/modal/modalReducer.tsx: -------------------------------------------------------------------------------- 1 | export const modalReducer = (state, action) => { 2 | switch (action.type) { 3 | case 'OPEN_MODAL': 4 | return { 5 | ...state, 6 | open: true 7 | } 8 | case 'CLOSE_MODAL': 9 | return { 10 | ...state, 11 | open:false 12 | } 13 | default: 14 | return state; 15 | } 16 | } -------------------------------------------------------------------------------- /src/firebase/firebaseConfig.js: -------------------------------------------------------------------------------- 1 | import firebase from "firebase"; 2 | import "firebase/app"; 3 | import "firebase/auth"; 4 | 5 | const firebaseConfig = { 6 | apiKey: process.env.GATSBY_FIREBASE_API_KEY, 7 | authDomain: process.env.GATSBY_FIREBASE_AUTH_DOMAIN, 8 | databaseURL: process.env.GATSBY_FIREBASE_AUTH_DATABASE_URL, 9 | projectId: process.env.GATSBY_FIREBASE_PROJECT_ID, 10 | storageBucket: process.env.GATSBY_FIREBASE_STORAGE_BUCKET, 11 | messagingSenderId: process.env.GATSBY_FIREBASE_MESSENGING_SENDER_ID, 12 | appId: process.env.GATSBY_FIREBASE_APP_ID, 13 | }; 14 | 15 | firebase.initializeApp(firebaseConfig); 16 | firebase.auth(); 17 | 18 | export default { firebaseConfig }; 19 | -------------------------------------------------------------------------------- /src/components/Footer/footer.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from 'react'; 2 | import './footer.css'; 3 | import { data } from '../../../data'; 4 | 5 | 6 | interface Props extends PropsWithChildren { 7 | center ?: boolean 8 | } 9 | 10 | export function Footer({ center=false}:Props) { 11 | return ( 12 |
13 |
14 |

15 | BLOGS 16 | {" "}© {new Date().getFullYear()}. POWERED BY{" "} 17 | {data.name.split(' ')[0]}.

18 |
19 | ); 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The BSD Zero Clause License (0BSD) 2 | 3 | Copyright (c) 2020 Gatsby Inc. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /data.tsx: -------------------------------------------------------------------------------- 1 | interface Info{ 2 | name: String, 3 | introduction: String, 4 | description: String, 5 | linkedIn: String, 6 | github: String, 7 | twitter: String, 8 | mail: String, 9 | facebook : String, 10 | } 11 | 12 | export const data: Info = { 13 | name: "HINA KHADIM", 14 | introduction: "Undergraduate Software Engineer | Learner | Mentor", 15 | description: 16 | "A Full Stack Web developer trying to make the world better place through coding 😇. Loves to code in Python and Javascript.💜 ", 17 | linkedIn: "https://www.linkedin.com/in/hina-khadim-632845178/", 18 | github: "https://github.com/Hina-softwareEngineer", 19 | twitter: "http://twitter.com/hinaKhadim_2002", 20 | mail: "hinakhadim2002@gmail.com", 21 | facebook: "https://www.facebook.com/hina.hina.35574406", 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/BlogList/blogList.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Crimson+Text&display=swap'); 2 | .blog-list{ 3 | max-width: 900px; 4 | margin: 80px auto; 5 | font-family: 'Karla', sans-serif; 6 | } 7 | 8 | .blog-list > h1{ 9 | font-size: 2.5em; 10 | margin-bottom: 10px; 11 | font-family: 'Crimson Text', serif; 12 | font-weight: 500; 13 | } 14 | 15 | @media screen and (max-width: 600px){ 16 | .blog-list > h1{ 17 | margin-left: 15px; 18 | } 19 | } 20 | 21 | .divider{ 22 | width: 100%; 23 | height: 1px; 24 | background-color: #69A297; 25 | margin-bottom: 100px; 26 | } 27 | 28 | h2{ 29 | font-size: 2rem !important; 30 | font-weight: 400 !important; 31 | color: #000000; 32 | text-decoration: none; 33 | font-family: 'Crimson Text', serif !important; 34 | cursor : pointer; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/context/modal/modal.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext,useReducer,createRef } from "react"; 2 | import { modalReducer } from './modalReducer'; 3 | 4 | export const ModalContext = createContext(null); 5 | 6 | let initialState = { 7 | open: false 8 | }; 9 | 10 | export const ModalContextProvider = (props) => { 11 | let [openLoginModal, dispatch] = useReducer(modalReducer, initialState); 12 | 13 | let blogsHeadingRef = createRef(); 14 | 15 | const handleOpenLoginModal = () => { 16 | dispatch({ 17 | type:"OPEN_MODAL" 18 | }); 19 | }; 20 | 21 | const handleCloseLoginModal = () => { 22 | dispatch({ 23 | type:"CLOSE_MODAL" 24 | }); 25 | }; 26 | 27 | return ( 28 | 32 | {props.children} 33 | 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config({ 2 | path: `.env`, 3 | }); 4 | 5 | module.exports = { 6 | siteMetadata: { 7 | title: `HINA KHADIM BLOGS`, 8 | description: `Hey folks, Are you interested in knowing how to become 9 | a professional Software Engineer or do you want to know my Coding Journey?...`, 10 | }, 11 | plugins: [ 12 | "gatsby-plugin-typescript", 13 | `gatsby-plugin-material-ui`, 14 | { 15 | resolve: `gatsby-transformer-remark`, 16 | options: { 17 | plugins: [ 18 | { 19 | resolve: `gatsby-remark-prismjs`, 20 | options: { 21 | aliases: { sh: "bash", js: "javascript" }, 22 | showLineNumbers: true, 23 | }, 24 | }, 25 | ], 26 | }, 27 | }, 28 | { 29 | resolve: `gatsby-source-contentful`, 30 | options: { 31 | spaceId: process.env.SPACE_ID, 32 | accessToken: process.env.ACCESS_TOKEN, 33 | forceFullSync: true, 34 | }, 35 | }, 36 | ], 37 | }; 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-starter-minimal", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "A Gatsby starter for running recipes and adding themes. It does not come with any pages.", 6 | "author": "Laurie Barth", 7 | "keywords": [ 8 | "gatsby" 9 | ], 10 | "scripts": { 11 | "develop": "gatsby develop", 12 | "start": "gatsby develop", 13 | "build": "gatsby build", 14 | "clean": "gatsby clean" 15 | }, 16 | "license": "0BSD", 17 | "dependencies": { 18 | "@contentful/rich-text-react-renderer": "^14.1.1", 19 | "@material-ui/core": "^4.11.0", 20 | "@material-ui/icons": "^4.9.1", 21 | "@material-ui/lab": "^4.0.0-alpha.56", 22 | "@material-ui/styles": "^4.10.0", 23 | "dotenv": "^8.2.0", 24 | "firebase": "^7.23.0", 25 | "gatsby": "^2.24.63", 26 | "gatsby-plugin-material-ui": "^2.1.10", 27 | "gatsby-remark-prismjs": "^3.5.16", 28 | "gatsby-source-contentful": "^2.3.49", 29 | "gatsby-transformer-remark": "^2.8.38", 30 | "mdi-material-ui": "^6.19.0", 31 | "prismjs": "^1.22.0", 32 | "react": "^16.13.1", 33 | "react-dom": "^16.13.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/pages/about/about.css: -------------------------------------------------------------------------------- 1 | .info-section{ 2 | max-width: 800px; 3 | margin: 20px auto; 4 | margin-top:115px; 5 | text-align: center; 6 | padding: 20px; 7 | background-color: #f6f6f6; 8 | box-shadow: 0 2px 4px lightgrey; 9 | } 10 | 11 | .info-section > h1{ 12 | font-family: 'Crimson Text', serif; 13 | font-size: 44px; 14 | } 15 | 16 | .info-section > h3{ 17 | font-family: 'Crimson Text', serif; 18 | font-size: 30px; 19 | margin: 0; 20 | } 21 | 22 | .info-section > p{ 23 | font-size: 18px; 24 | letter-spacing: 0.7px; 25 | } 26 | 27 | .social-links{ 28 | display: flex; 29 | justify-content: center; 30 | align-items: center; 31 | margin: 40px 0; 32 | } 33 | 34 | .social-links > li{ 35 | list-style-type: none; 36 | } 37 | 38 | .social-links > li > a{ 39 | margin: 0 10px; 40 | } 41 | 42 | .social-links > li > a > svg{ 43 | font-size: 2.5em; 44 | } 45 | 46 | .linkedin{color: #0073b1;} 47 | 48 | .github{color: #24292e} 49 | 50 | .twitter{ color: #1da1f2} 51 | 52 | .mail{color: #D44638} 53 | 54 | .facebook{color: #1877f2} 55 | 56 | @media screen and (max-width: 700px){ 57 | .info-section{ 58 | margin-left: 15px; 59 | margin-right: 15px; 60 | } 61 | } -------------------------------------------------------------------------------- /src/context/auth/authReducer.js: -------------------------------------------------------------------------------- 1 | export const authReducer = (state, action) => { 2 | switch (action.type) { 3 | case "LOADED": 4 | return { 5 | ...state, 6 | isLoading: true, 7 | isAuthenticated: false, 8 | }; 9 | case "LOADING": 10 | return { 11 | ...state, 12 | ...action.payload, 13 | isLoading: false, 14 | isAuthenticated: true, 15 | user: action.payload, 16 | error: null, 17 | }; 18 | case "LOGIN_SUCCESSFULLY": 19 | localStorage.setItem("token", action.payload.accessToken); 20 | return { 21 | ...state, 22 | isLoading: false, 23 | isAuthenticated: true, 24 | user: action.payload.email, 25 | error: null, 26 | }; 27 | case "LOGIN_FAILED": 28 | localStorage.removeItem("token"); 29 | return { 30 | ...state, 31 | isLoading: false, 32 | isAuthenticated: false, 33 | user: null, 34 | error: action.payload.message, 35 | }; 36 | case "SIGN_OUT": 37 | localStorage.removeItem("token"); 38 | return { 39 | ...state, 40 | isLoading: false, 41 | isAuthenticated: false, 42 | user: null, 43 | error: null, 44 | }; 45 | default: 46 | return state; 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/style/modules/main.module.css: -------------------------------------------------------------------------------- 1 | 2 | .aboutMe{ 3 | margin: 10% auto !important; 4 | text-align: center; 5 | padding: 20px; 6 | max-width: 600px; 7 | } 8 | 9 | .h3About{ 10 | margin: 0; 11 | color: #fff; 12 | font-family: 'Karla', sans-serif; 13 | text-shadow: 0 2px 4px #000; 14 | } 15 | 16 | .infoAbout{ 17 | margin: 0 ; 18 | font-size: 18px; 19 | color: #fff; 20 | font-family: 'Karla', sans-serif; 21 | text-shadow: 0 2px 4px #000; 22 | } 23 | 24 | .intro{ 25 | position: absolute; 26 | top:0; 27 | left:0; 28 | width: 100%; 29 | height: 100%; 30 | z-index:10; 31 | } 32 | 33 | .aboutName{ 34 | margin: 0; 35 | text-shadow: 0 2px 4px #000; 36 | font-family: 'Karla', sans-serif; 37 | color: #fff; 38 | } 39 | 40 | 41 | .hand{ 42 | display: inline-block; 43 | text-shadow:0 1px 2px #000; 44 | animation: rotate-anim 2s linear 1s infinite forwards; 45 | } 46 | 47 | @keyframes rotate-anim{ 48 | 0%{ 49 | transform: rotate(0deg); 50 | } 51 | 10%{ 52 | transform: rotate(-20deg); 53 | } 54 | 20%{ 55 | transform: rotate(20deg); 56 | } 57 | 30%{ 58 | transform: rotate(-5deg); 59 | } 60 | 40%{ 61 | transform: rotate(20deg); 62 | } 63 | 50%{ 64 | transform: rotate(0deg); 65 | } 66 | 100%{ 67 | transform: rotate(0deg); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | exports.onCreateWebpackConfig = ({ stage, actions, getConfig }) => { 2 | if (stage === "build-html") { 3 | actions.setWebpackConfig({ 4 | externals: getConfig().externals.concat(function ( 5 | context, 6 | request, 7 | callback 8 | ) { 9 | const regex = /^@?firebase(\/(.+))?/; 10 | if (regex.test(request)) { 11 | return callback(null, `umd ${request}`); 12 | } 13 | callback(); 14 | }), 15 | }); 16 | } 17 | }; 18 | 19 | exports.createPages = async function ({ graphql, actions }) { 20 | const { createPage } = actions; 21 | const response = await graphql(` 22 | query { 23 | allContentfulBlogModel { 24 | nodes { 25 | title 26 | slug 27 | publishedDate(formatString: "YYYY MMM, DD") 28 | featuredImage { 29 | fluid { 30 | src 31 | } 32 | } 33 | body { 34 | json 35 | } 36 | excerpt { 37 | excerpt 38 | } 39 | } 40 | } 41 | } 42 | `); 43 | 44 | response.data.allContentfulBlogModel.nodes.forEach((edge) => { 45 | createPage({ 46 | path: `/blog/${edge.slug}`, 47 | component: require.resolve("./src/components/Blog/blogs.tsx"), 48 | context: { 49 | title: edge.title, 50 | slug: edge.slug, 51 | publishedDate: edge.publishedDate, 52 | featuredImage: edge.featuredImage.fluid.src, 53 | body: edge.body.json, 54 | excerpt: edge, 55 | }, 56 | }); 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /src/pages/about/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link} from 'gatsby'; 3 | 4 | import { GlobalAuthProvider } from "../../context/auth/auth"; 5 | import { ModalContextProvider } from "../../context/modal/modal"; 6 | 7 | import Header from '../../components/Header/header'; 8 | import { data } from '../../../data'; 9 | 10 | import FacebookIcon from '@material-ui/icons/Facebook'; 11 | import TwitterIcon from '@material-ui/icons/Twitter'; 12 | import GitHubIcon from '@material-ui/icons/GitHub'; 13 | import EmailIcon from '@material-ui/icons/Email'; 14 | import LinkedInIcon from '@material-ui/icons/LinkedIn'; 15 | import './about.css'; 16 | 17 | export default function About() { 18 | return ( 19 | 20 | 21 |
22 |
23 |
24 |

About Me

25 |

I'm {data.name}

26 |

{data.introduction}

27 |

28 | { 29 | data.description 30 | } 31 |

32 | 33 |
34 |
  • 35 |
  • 36 |
  • 37 |
  • 38 |
  • 39 | 40 |
    41 |
    42 |
    43 |
    44 |
    45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /src/components/Blog/blogs.css: -------------------------------------------------------------------------------- 1 | .main-container{ 2 | max-width: 950px; 3 | margin: 10px auto; 4 | margin-top:64px; 5 | } 6 | 7 | .images-inside-container{ 8 | max-width: 800px; 9 | width: 100%; 10 | height: 350px; 11 | /* border:2px solid #000; */ 12 | } 13 | 14 | .main-blog{ 15 | max-width: 800px; 16 | margin: 0 auto; 17 | padding: 0 10px; 18 | } 19 | 20 | 21 | .main-blog > h1{ 22 | font-family: 'Crimson Text', serif; 23 | font-size: 44px; 24 | font-weight: 600; 25 | line-height: 1.2; 26 | } 27 | 28 | .main-blog > .date{ 29 | font-size: 14px; 30 | color: #aaa; 31 | letter-spacing: 1px; 32 | margin-bottom: 40px; 33 | } 34 | 35 | .main-blog >div> p{ 36 | font-family: 'Karla', sans-serif; 37 | font-size: 18px; 38 | color: #666; 39 | line-height: 1.6; 40 | margin-bottom: 40px; 41 | } 42 | 43 | .post-content{ 44 | margin: 80px 0; 45 | } 46 | 47 | .post-content > p:first-child::first-letter{ 48 | font-size: 60px; 49 | font-weight: 500; 50 | float: left; 51 | margin-right: 15px; 52 | position: relative; 53 | text-transform: uppercase; 54 | vertical-align: bottom; 55 | color: #000; 56 | line-height: 0.8; 57 | } 58 | 59 | pre{ 60 | background:#000; 61 | padding: 25px; 62 | border-radius: 3px; 63 | overflow-x: scroll; 64 | } 65 | 66 | pre > code{ 67 | color : #fff; 68 | } 69 | 70 | 71 | pre::-webkit-scrollbar { 72 | height: 8px; 73 | } 74 | 75 | /* Track */ 76 | pre::-webkit-scrollbar-track { 77 | background: #000; 78 | } 79 | 80 | /* Handle */ 81 | pre::-webkit-scrollbar-thumb { 82 | background: rgb(112,112,112); 83 | border-radius: 50px; 84 | } 85 | /* Handle on hover */ 86 | pre::-webkit-scrollbar-thumb:hover { 87 | background: rgb(73, 73, 73); 88 | 89 | } 90 | 91 | .feature-image{ 92 | width: 100%; 93 | height: 400px; 94 | } 95 | 96 | @media screen and (max-width: 600px){ 97 | 98 | .images-inside-container{ 99 | height: 200px; 100 | /* border:2px solid #000; */ 101 | } 102 | 103 | .feature-image{ 104 | height: 250px; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

    2 | 3 | Gatsby 4 | 5 |

    6 | 7 | 8 | 9 |

    10 | My Blog in GatsbyJs 🔥🔥 11 |

    12 | 13 | [![Netlify Status](https://api.netlify.com/api/v1/badges/189d6398-8c29-48e6-9ff6-4f8c820f63fd/deploy-status)](https://app.netlify.com/sites/hina-blogs-2002/deploys) 14 | 15 | ##### ⭐️ Live Demo : http://hina-blogs-2002.netlify.app/ 16 | 17 | #### ⭐️ What tech I used: 18 | 19 | ⚡️ [Gatsby Js Minimal Starter](https://github.com/gatsbyjs/gatsby-starter-minimal)
    20 | ⚡️ [Contentful (Headless CMS)](https://www.contentful.com/)
    21 | ⚡️ [Material UI](https://material-ui.com)
    22 | ⚡️ Firebase Authentication
    23 | ⚡️ React Context API for state management
    24 | ⚡️ Graphql
    25 | ⚡️ Hosted on Netlify
    26 | 27 |
    28 | 29 | ###### ⭐️ Tech: 30 | 31 |
    32 | Gatsby 33 | Contentful 34 | Material UI 35 | Firebase 36 | GraphQl 37 | Netlify 38 |
    39 | 40 |
    41 | 42 | ##### ⭐️ Preview : 43 | 44 | ![Main Page](./assets/blog-main.png) 45 | 46 |
    47 | 48 | ![Modal](./assets/blog1.png) 49 | 50 | 51 | 52 | 53 | ## 🚀 Quick start 54 | 55 | 1. **Create a Gatsby site.** 56 | 57 | Use the Gatsby CLI to create a new site, specifying the minimal starter. 58 | 59 | ```shell 60 | # create a new Gatsby site using the minimal starter 61 | gatsby new my-site https://github.com/gatsbyjs/gatsby-starter-minimal 62 | ``` 63 | 64 | 2. **Start developing.** 65 | 66 | Navigate into your new site’s directory and start it up. 67 | 68 | ```shell 69 | cd my-site/ 70 | gatsby develop 71 | ``` 72 | 73 | 3. **Open the code and start customizing!** 74 | 75 | Your site is now running at `http://localhost:8000`! 76 | 77 | 78 | > Any Feedback is appreciated.😃😃 -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | 3 | import Layout from '../components/Layout/Layout'; 4 | import Blogs from '../components/BlogList/blogList'; 5 | 6 | import { GlobalAuthProvider } from "../context/auth/auth"; 7 | import { ModalContext, ModalContextProvider } from '../context/modal/modal'; 8 | 9 | import * as classCss from '../style/modules/main.module.css'; 10 | import {data } from '../../data'; 11 | // material styles 12 | import { makeStyles } from '@material-ui/core/styles'; 13 | import ModalSignIn from '../components/modal/modal'; 14 | import Typography from '@material-ui/core/Typography'; 15 | 16 | 17 | const useStyles = makeStyles((theme) => ({ 18 | imageContainer: { 19 | width: "100%", 20 | height: "600px", 21 | overflow: "hidden", 22 | marginTop: '56px', 23 | }, 24 | large: { 25 | width: theme.spacing(7), 26 | height: theme.spacing(7), 27 | }, 28 | })); 29 | 30 | 31 | 32 | export default function Home() { 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | 42 | 43 | const Body = () => { 44 | const classes = useStyles(); 45 | let { handleOpenLoginModal, blogsHeadingRef}= useContext(ModalContext); 46 | 47 | return ( 48 | <> 49 | 50 |
    51 | Blog post 52 |
    55 | 58 |

    Hi Guys!
    👋

    59 |

    {data.name} here

    60 |
    Future Software Engineer! 🥰🥰 | 61 | Love to Code❤️❤️
    Full Stack Developer✨✨ | MERN Developer ☘️☘️| 62 | Python Developer⭐️⭐️ | Javascript Developer 🔥 🔥
    63 |
    64 |
    65 |
    66 | 67 |
    68 | 69 | ) 70 | } -------------------------------------------------------------------------------- /src/components/Blog/blogs.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { GlobalAuthProvider, AuthContext } from '../../context/auth/auth'; 3 | import { ModalContextProvider} from '../../context/modal/modal'; 4 | 5 | import './blogs.css'; 6 | 7 | import { navigate } from "gatsby"; 8 | import { MARKS } from '@contentful/rich-text-types'; 9 | import { documentToReactComponents } from "@contentful/rich-text-react-renderer"; 10 | 11 | import Header from '../Header/header'; 12 | import { Footer } from '../Footer/footer'; 13 | 14 | import Button from '@material-ui/core/Button'; 15 | import ArrowBackIcon from '@material-ui/icons/ArrowBack'; 16 | 17 | 18 | function Blog(props) { 19 | 20 | const options = { 21 | renderMark: { 22 | [MARKS.CODE]: code =>
    {code}
    23 | }, 24 | renderNode: { 25 | "embedded-asset-block": node => { 26 | const alt = node.data.target.fields.title["en-US"] 27 | const url = node.data.target.fields.file["en-US"].url 28 | return {alt} 29 | }, 30 | }, 31 | } 32 | 33 | let blog = props.pageContext; 34 | return ( 35 | 36 | 37 | 38 | ); 39 | } 40 | 41 | export default Blog; 42 | 43 | 44 | 45 | 46 | 47 | const BlogData = ({ blog, options, ...props }) => { 48 | let { state } = useContext(AuthContext); 49 | 50 | return ( 51 | 52 |
    53 |
    54 | 61 |
    62 | { 63 | state.isAuthenticated && !state.isLoading ? 64 | <> 65 |

    {blog.title}

    66 |

    {blog.publishedDate}

    67 | alter girl 68 |
    {documentToReactComponents(blog.body, options)}
    69 |
    70 | 71 | : 72 | (!state.isAuthenticated && !state.isLoading ?
    Loading...
    :
    Error...
    ) 73 | } 74 |
    75 |
    76 | 77 | ); 78 | } -------------------------------------------------------------------------------- /assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/context/auth/auth.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useEffect, useReducer, useState } from "react"; 2 | import firebase from "firebase"; 3 | import firebaseConfig from "../../firebase/firebaseConfig"; 4 | import { authReducer } from "./authReducer"; 5 | import Snackbar from "@material-ui/core/Snackbar"; 6 | import MuiAlert from "@material-ui/lab/Alert"; 7 | 8 | export const AuthContext = createContext(null); 9 | 10 | let initialState = { 11 | user: null, 12 | isAuthenticated: false, 13 | isLoading: false, 14 | error: null, 15 | }; 16 | 17 | export const GlobalAuthProvider = (props) => { 18 | const [snackBar, setSnackBar] = useState(false); 19 | const [messageSnackBar, setMessageSnackBar] = useState("Log Out Failed!"); 20 | let [state, dispatch] = useReducer(authReducer, initialState); 21 | 22 | const signUpwithGoogle = (callBack) => { 23 | var provider = new firebase.auth.GoogleAuthProvider(); 24 | 25 | firebase 26 | .auth() 27 | .signInWithPopup(provider) 28 | .then(function (result) { 29 | var token = result.credential.accessToken; 30 | var user = result.user; 31 | 32 | dispatch({ 33 | type: "LOGIN_SUCCESSFULLY", 34 | payload: user, 35 | accessToken: token, 36 | }); 37 | callBack(); 38 | }) 39 | .catch(function (error) { 40 | dispatch({ 41 | type: "LOGIN_FAILED", 42 | payload: error, 43 | }); 44 | }); 45 | }; 46 | 47 | const onSignInWithFacebook = (callBack) => { 48 | var provider = new firebase.auth.FacebookAuthProvider(); 49 | 50 | firebase 51 | .auth() 52 | .signInWithPopup(provider) 53 | .then(function (result) { 54 | var token = result.credential.accessToken; 55 | var user = result.user; 56 | dispatch({ 57 | type: "LOGIN_SUCCESSFULLY", 58 | payload: user, 59 | accessToken: token, 60 | }); 61 | callBack(); 62 | }) 63 | .catch(function (error) { 64 | dispatch({ 65 | type: "LOGIN_FAILED", 66 | payload: error, 67 | }); 68 | setMessageSnackBar("Login Failed!"); 69 | setSnackBar(true); 70 | }); 71 | }; 72 | 73 | const getSignedInUser = async () => { 74 | await firebase.auth().onAuthStateChanged(function (user) { 75 | if (user) { 76 | dispatch({ 77 | type: "LOADING", 78 | payload: user, 79 | }); 80 | } else { 81 | console.log("sign out"); 82 | dispatch({ 83 | type: "LOADED", 84 | }); 85 | } 86 | }); 87 | }; 88 | 89 | const signOut = () => { 90 | firebase 91 | .auth() 92 | .signOut() 93 | .then((res) => { 94 | console.log("Log out succesfully", res); 95 | dispatch({ 96 | type: "SIGN_OUT", 97 | }); 98 | }) 99 | .catch((e) => { 100 | setMessageSnackBar("Logout Failed"); 101 | setSnackBar(true); 102 | }); 103 | }; 104 | 105 | useEffect(() => { 106 | getSignedInUser(); 107 | }, []); 108 | 109 | return ( 110 | 113 | {props.children} 114 | setSnackBar(false)} 118 | anchorOrigin={{ vertical: "top", horizontal: "center" }} 119 | > 120 | setSnackBar(false)} severity="error"> 121 | {messageSnackBar} 122 | 123 | 124 | 125 | ); 126 | }; 127 | 128 | function Alert(props) { 129 | return ; 130 | } 131 | -------------------------------------------------------------------------------- /src/components/modal/modal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext} from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Modal from '@material-ui/core/Modal'; 4 | import Backdrop from '@material-ui/core/Backdrop'; 5 | import Fade from '@material-ui/core/Fade'; 6 | import Button from '@material-ui/core/Button'; 7 | import { AuthContext } from '../../context/auth/auth'; 8 | import "./modal.css"; 9 | 10 | 11 | const useStyles = makeStyles((theme) => ({ 12 | modal: { 13 | display: 'flex', 14 | alignItems: 'center', 15 | justifyContent: 'center', 16 | }, 17 | paper: { 18 | textAlign: "center", 19 | width: "500px", 20 | borderRadius: "5px", 21 | margin: '0 15px', 22 | background: "#fff", 23 | boxShadow: theme.shadows[5], 24 | padding: theme.spacing(2, 4, 3), 25 | "&:focus":{ 26 | outline:"none" 27 | } 28 | }, 29 | button: { 30 | width: '-webkit-fill-available', 31 | margin: '20px 0', 32 | display: "flex", 33 | alignItems: 'center', 34 | border: "1px solid lightgrey", 35 | }, 36 | })); 37 | 38 | export default function ModalSignIn({ open, handleClose}) { 39 | const classes = useStyles(); 40 | const { signUpwithGoogle, state,onSignInWithFacebook } = useContext(AuthContext); 41 | 42 | const Signup = async () => { 43 | signUpwithGoogle(handleClose); 44 | } 45 | const SignInFaceBook = () => { 46 | onSignInWithFacebook(handleClose); 47 | } 48 | 49 | return ( 50 | 62 | 63 |
    64 |

    Login to Hina Blogs

    65 |

    Login to read more articles.

    66 |
    80 | } 81 | > 82 | Login with Google 83 | 84 | 94 | 95 |
    96 |
    ); 97 | } -------------------------------------------------------------------------------- /src/components/BlogList/blogList.tsx: -------------------------------------------------------------------------------- 1 | import React,{useContext,useState,useEffect} from 'react'; 2 | import { navigate, useStaticQuery, graphql } from "gatsby"; 3 | import "./blogList.css" 4 | 5 | import { AuthContext } from '../../context/auth/auth'; 6 | 7 | // material styles 8 | import Pagination from '@material-ui/lab/Pagination'; 9 | import Card from '@material-ui/core/Card'; 10 | import CardActionArea from '@material-ui/core/CardActionArea'; 11 | import CardActions from '@material-ui/core/CardActions'; 12 | import CardContent from '@material-ui/core/CardContent'; 13 | import CardMedia from '@material-ui/core/CardMedia'; 14 | import Divider from '@material-ui/core/Divider'; 15 | import Typography from '@material-ui/core/Typography'; 16 | import Skeleton from '@material-ui/lab/Skeleton'; 17 | import { makeStyles } from '@material-ui/core/styles'; 18 | 19 | 20 | const useStyles = makeStyles((theme) => ({ 21 | root: { 22 | display: "flex", 23 | margin: "20px 0", 24 | boxShadow: "none", 25 | alignItems: 'center', 26 | [theme.breakpoints.down('xs')]: { 27 | flexDirection: "column", 28 | textAlign:"center" 29 | }, 30 | }, 31 | media: { 32 | width: "300px", 33 | height: "200px", 34 | }, 35 | imageButton: { 36 | width: '300px', 37 | height:"200px", 38 | margin: "0 30px", 39 | [theme.breakpoints.down('xs')]: { 40 | marginTop: '20px' 41 | }, 42 | }, 43 | readMore: { 44 | color: '#3f51b5', 45 | cursor: "pointer", 46 | fontWeight: 'bold', 47 | fontFamily: "'Karla', serif", 48 | [theme.breakpoints.down('xs')]: { 49 | width: '100%' 50 | }, 51 | }, 52 | cardBody: { 53 | [theme.breakpoints.down('xs')]: { 54 | margin: '20px 30px' 55 | }, 56 | }, 57 | date: { 58 | letterSpacing: '1.3px', 59 | fontFamily: "'Karla', sans-serif" 60 | }, 61 | excerpt: { 62 | fontSize: "15px", 63 | fontFamily: "'Karla', sans-serif" 64 | }, 65 | footer: { 66 | margin: '10px 0', 67 | padding: 0, 68 | }, 69 | pagination: { 70 | padding: "30px 0", 71 | "& > nav > ul": { 72 | justifyContent: 'center' 73 | } 74 | } 75 | })); 76 | 77 | // karla font family 78 | // crimson text font family heading 79 | 80 | export default function Blogs({ handleOpenLoginModal,blogsHeadingRef }) { 81 | const classes = useStyles(); 82 | const { state } = useContext(AuthContext); 83 | const [pageNumber, setPageNumber] = useState(1); 84 | const [imageLoader, setImageLoader] = useState(true); 85 | 86 | 87 | useEffect(() => { 88 | setTimeout(() => setImageLoader(false), 3000); 89 | },[]) 90 | 91 | 92 | const onPageChange = (event, page) => { 93 | setPageNumber(page); 94 | } 95 | 96 | 97 | const onLink = (link) => { 98 | if (state.isAuthenticated && !state.isLoading) { 99 | navigate(link); 100 | } 101 | else { 102 | handleOpenLoginModal(); 103 | } 104 | } 105 | 106 | const data = useStaticQuery( 107 | graphql` 108 | query { 109 | allContentfulBlogModel { 110 | nodes { 111 | title 112 | slug 113 | publishedDate(formatString: "MMMM DD, YYYY") 114 | excerpt { 115 | excerpt 116 | } 117 | featuredImage { 118 | fluid { 119 | src 120 | } 121 | } 122 | body { 123 | json 124 | } 125 | 126 | } 127 | } 128 | } 129 | ` 130 | ); 131 | 132 | 133 | // for pagination 134 | let totalPages = Math.ceil(data.allContentfulBlogModel.nodes.length / 5); 135 | let LastPostIndex = pageNumber * 5; 136 | let FirstPostIndex = LastPostIndex - 5; 137 | const currentBlogs = data.allContentfulBlogModel.nodes.slice(FirstPostIndex,LastPostIndex); 138 | 139 | 140 | return ( 141 |
    142 | 143 |

    Latest Blogs

    144 |
    145 |
    146 | { 147 | currentBlogs.map((blog, index) => 148 | 149 | 150 | { 151 | imageLoader ? 152 | : 153 | 158 | } 159 | 160 | 161 | 162 | {blog.publishedDate} 163 | 164 | onLink("/blog/"+blog.slug)} gutterBottom variant="h5" component="h2"> 165 | {blog.title} 166 | 167 | 168 | {blog.excerpt.excerpt} 169 | 170 | 171 | onLink("/blog/"+blog.slug)} color="textSecondary" component="p"> 172 | Read More 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | ) 183 | } 184 |
    185 |
    186 | {totalPages > 1 ? 187 | : 188 | } 189 |
    190 |
    191 | 192 | ); 193 | } 194 | 195 | -------------------------------------------------------------------------------- /src/components/Header/header.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState,useContext,useEffect, Ref} from "react"; 2 | import { navigate } from 'gatsby'; 3 | 4 | import "./header.css"; 5 | 6 | import ModalSignIn from '../modal/modal'; 7 | 8 | import { makeStyles } from '@material-ui/core/styles'; 9 | import { useTheme } from '@material-ui/core/styles'; 10 | import AppBar from '@material-ui/core/AppBar'; 11 | import Toolbar from '@material-ui/core/Toolbar'; 12 | import Typography from '@material-ui/core/Typography'; 13 | import Button from '@material-ui/core/Button'; 14 | import Menu from '@material-ui/core/Menu'; 15 | import MenuItem from '@material-ui/core/MenuItem'; 16 | import Avatar from '@material-ui/core/Avatar'; 17 | import MenuIcon from '@material-ui/icons/Menu'; 18 | import Drawer from '@material-ui/core/Drawer'; 19 | import List from '@material-ui/core/List' 20 | import ListItem from '@material-ui/core/ListItem'; 21 | import ListItemIcon from '@material-ui/core/ListItemIcon'; 22 | import ListItemText from '@material-ui/core/ListItemText'; 23 | import LibraryBooksIcon from '@material-ui/icons/LibraryBooks'; 24 | import PersonIcon from '@material-ui/icons/Person'; 25 | import ExitToAppIcon from '@material-ui/icons/ExitToApp'; 26 | import ArrowBackIcon from '@material-ui/icons/ArrowBack'; 27 | 28 | import { AuthContext } from '../../context/auth/auth'; 29 | import { ModalContext, ModalContextProvider } from '../../context/modal/modal'; 30 | import { data } from '../../../data'; 31 | 32 | 33 | 34 | const useStyles = makeStyles((theme) => ({ 35 | root: { 36 | flexGrow: 1, 37 | }, 38 | menuButton: { 39 | marginRight: theme.spacing(2), 40 | }, 41 | title: { 42 | flexGrow: 1, 43 | fontFamily: "'Karla', serif", 44 | }, 45 | options: { 46 | fontFamily: "'Karla', serif", 47 | color: '#fff', 48 | fontSize: "1.2em", 49 | margin: '0 20px' 50 | }, 51 | loginBtn: { 52 | margin: '0 15px', 53 | padding: '0', 54 | color: '#fff', 55 | fontSize: '1.2em', 56 | // textTransform:"Capitalize", 57 | fontFamily: "'Karla', serif", 58 | }, 59 | image: { 60 | width: 'inherit', 61 | height:"inherit" 62 | 63 | }, 64 | name: { 65 | margin: "0 10px" 66 | }, 67 | menu: { 68 | cursor: "pointer", 69 | display: "flex", 70 | alignItems: 'center', 71 | textShadow:"0 2px 4px #000" 72 | }, 73 | large: { 74 | width: theme.spacing(7), 75 | height: theme.spacing(7), 76 | [theme.breakpoints.down('xs')]: { 77 | margin: '20px auto', 78 | width: '80px', 79 | height: '80px' 80 | }, 81 | }, 82 | mobileProfile: { 83 | textAlign:"center" 84 | } 85 | 86 | })); 87 | 88 | 89 | export default function Header() { 90 | 91 | const classes = useStyles(); 92 | const theme = useTheme(); 93 | const [resize, setResize] = useState(undefined); 94 | const [anchorEl, setAnchorEl] = useState(false); 95 | const [drawerState, setDrawerState] = useState(false); 96 | const { state, signOut } = useContext(AuthContext); 97 | const { handleOpenLoginModal, blogsHeadingRef,openLoginModal, handleCloseLoginModal} = useContext(ModalContext); 98 | 99 | 100 | const toggleDrawer = (option) => { 101 | if (option === 'close') { 102 | setDrawerState(false); 103 | } 104 | else if (option == null) { 105 | setDrawerState(!drawerState); 106 | } 107 | } 108 | 109 | const onClickDropdownMenu = (event) => { 110 | setAnchorEl(event.currentTarget); 111 | }; 112 | 113 | const onCloseDropdownMenu = () => { 114 | setAnchorEl(false); 115 | }; 116 | 117 | const Signout = () => { 118 | signOut(); 119 | if (window.location.pathname != '/') { 120 | navigate("/"); 121 | } 122 | } 123 | 124 | const onScrollToBlogsPanel = (e) => { 125 | if (e.target.textContent === 'Blogs') { 126 | if (window.location.pathname === '/') { 127 | blogsHeadingRef.current.scrollIntoView({ 128 | behavior: "smooth", block: "start" 129 | }) 130 | } 131 | else { 132 | navigate("/"); 133 | } 134 | } 135 | } 136 | 137 | useEffect(() => { 138 | setResize(window.innerWidth); 139 | window.addEventListener('resize', () => setResize(window.innerWidth)); 140 | return () => window.removeEventListener("resize", () => setResize(window.innerWidth)); 141 | }, []); 142 | 143 | 144 | return ( 145 |
    146 | 147 | 148 | 149 | 150 | { 151 | data.name 152 | } 153 | 154 | 155 | { 156 | theme.breakpoints.values.sm <= resize ? <> 157 | 158 | 161 | { 162 | state.isAuthenticated && !state.isLoading ? 163 | <> 164 |
    165 | 166 |

    {state.user?.displayName}

    167 |
    168 | 175 | {state.user?.email} 176 | Logout 177 | 178 | 179 | : 180 | 181 | 182 | } 183 | : 184 | <> 185 | toggleDrawer(null)} /> 186 | toggleDrawer('close')}> 187 | 188 |
    189 | 190 | { 191 | state.isAuthenticated && !state.isLoading ? 192 |
    193 | 194 |

    {state.user?.displayName}

    195 |

    {state.user?.email}

    196 |
    : 197 | null 198 | } 199 | 200 | {toggleDrawer("close"); onScrollToBlogsPanel(e); }} button key={"blogs"}> 201 | 202 | 203 | 204 | 205 | { 206 | navigate("/about"); 207 | toggleDrawer("close"); 208 | }} button key={"About"}> 209 | 210 | 211 | 212 | 213 | { 214 | state.isAuthenticated && !state.isLoading ? 215 | 216 | 217 | 218 | 219 | : 220 | { handleOpenLoginModal(); setDrawerState(false); }} button key={"Login"}> 221 | 222 | 223 | 224 | } 225 | 226 |
    227 |
    228 |
    229 | 230 | } 231 |
    232 |
    233 |
    234 | ); 235 | } 236 | -------------------------------------------------------------------------------- /assets/netlify.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | --------------------------------------------------------------------------------