import React, { useState, useEffect } from "react" import MarkdownPreview from "../components/markdownPreview" import Markdown from "../components/markdown" // import Header from "../components/header" import Title from "../components/title" import Subtitle from "../components/subtitle" import Work from "../components/work" import Social from "../components/social" import Addons from "../components/addons" import Skills from "../components/skills" import Donate from "../components/donate" import { initialSkillState } from "../constants/skills" import gsap from "gsap" import Loader from "../components/loader" // import Footer from "../components/footer" import "./index.css" import { ArrowLeftIcon, CopyIcon, DownloadIcon, EyeIcon, CheckIcon, MarkdownIcon, FileCodeIcon, } from "@primer/octicons-react" import SEO from "../components/seo" import { isGitHubUsernameValid, isMediumUsernameValid, } from "../utils/validation" import Layout from "../components/layout" const DEFAULT_PREFIX = { title: "Hi šŸ‘‹, I'm", currentWork: "šŸ”­ I’m currently working on", currentLearn: "🌱 I’m currently learning", collaborateOn: "šŸ‘Æ I’m looking to collaborate on", helpWith: "šŸ¤ I’m looking for help with", ama: "šŸ’¬ Ask me about", contact: "šŸ“« How to reach me", funFact: "⚔ Fun fact", portfolio: "šŸ‘Øā€šŸ’» All of my projects are available at", blog: "šŸ“ I regulary write articles on", } const DEFAULT_DATA = { title: "", subtitle: "A passionate frontend developer from India", currentWork: "", currentLearn: "", collaborateOn: "", helpWith: "", ama: "", contact: "", funFact: "", visitorsBadge: false, githubStats: false, topLanguages: false, devDynamicBlogs: false, mediumDynamicBlogs: false, rssDynamicBlogs: false, } const DEFAULT_LINK = { currentWork: "", collaborateOn: "", helpWith: "", portfolio: "", blog: "", } const DEFAULT_SOCIAL = { github: "", dev: "", linkedin: "", codepen: "", stackoverflow: "", kaggle: "", codesandbox: "", fb: "", instagram: "", twitter: "", dribbble: "", behance: "", medium: "", youtube: "", codechef: "", hackerrank: "", codeforces: "", leetcode: "", topcoder: "", hackerearth: "", geeks_for_geeks: "", rssurl: "", } const KeepCacheUpdated = ({ prefix, data, link, social, skills }) => { useEffect(() => { localStorage.setItem( "cache", JSON.stringify({ prefix, data, link, social, skills, }) ) }, [prefix, data, link, social, skills]) } const DEFAULT_SKILLS = initialSkillState const IndexPage = () => { const [prefix, setPrefix] = useState(DEFAULT_PREFIX) const [data, setData] = useState(DEFAULT_DATA) const [link, setLink] = useState(DEFAULT_LINK) const [social, setSocial] = useState(DEFAULT_SOCIAL) const [skills, setSkills] = useState(DEFAULT_SKILLS) const [restore, setRestore] = useState("") const [generatePreview, setGeneratePreview] = useState(false) const [generateMarkdown, setGenerateMarkdown] = useState(false) const [displayLoader, setDisplayLoader] = useState(false) const [showConfig, setShowConfig] = useState(true) const [copyObj, setcopyObj] = useState({ isCopied: false, copiedText: "copy-markdown", }) const [previewMarkdown, setPreviewMarkdown] = useState({ isPreview: false, buttonText: "preview", }) const handleSkillsChange = field => { let change = { ...skills } change[field] = !change[field] setSkills(change) } const handlePrefixChange = (field, e) => { let change = { ...prefix } change[field] = e.target.value setPrefix(change) } const handleDataChange = (field, e) => { let change = { ...data } change[field] = e.target.value setData(change) } const handleLinkChange = (field, e) => { let change = { ...link } change[field] = e.target.value setLink(change) } const handleSocialChange = (field, e) => { let change = { ...social } change[field] = e.target.value.toLowerCase() setSocial(change) } const handleCheckChange = field => { let change = { ...data } change[field] = !change[field] setData(change) } const generate = () => { setShowConfig(false) var tl = new gsap.timeline() tl.to(".generate", { scale: 0, duration: 0.5, ease: "Linear.easeNone", }) tl.set("#form", { display: "none" }) setDisplayLoader(true) setTimeout(() => { setDisplayLoader(false) setGenerateMarkdown(!generateMarkdown) gsap.fromTo( "#markdown-box", { scale: 0.2, }, { scale: 1, duration: 0.5, ease: "Linear.easeNone", } ) gsap.fromTo( "#support", { autoAlpha: 0, }, { autoAlpha: 1, duration: 2, ease: "Linear.easeNone", } ) document.body.scrollTop = 0 // For Safari document.documentElement.scrollTop = 0 // For Chrome, Firefox, IE and Opera }, 3000) } const trimDataValues = (item, setItem) => { const dataObj = { ...item } Object.keys(dataObj).forEach(k => typeof dataObj[k] === "string" ? (dataObj[k] = dataObj[k].trim()) : null ) setItem(dataObj) } const handleGenerate = () => { trimDataValues(data, setData) trimDataValues(social, setSocial) trimDataValues(link, setLink) resetCopyMarkdownButton() if (data.visitorsBadge || data.githubStats || data.topLanguages) { if (social.github && isGitHubUsernameValid(social.github)) { generate() } } else if (social.github) { if (isGitHubUsernameValid(social.github)) { generate() } } else { generate() } } const handleGeneratePreview = () => { setGenerateMarkdown(!generateMarkdown) setGeneratePreview(!generatePreview) if (!generatePreview) { gsap.set("#copy-button, #download-md-button, #download-json-button", { visibility: "hidden", }) setPreviewMarkdown({ isPreview: true, buttonText: "markdown", }) } else { gsap.set("#copy-button, #download-md-button, #download-json-button", { visibility: "visible", }) gsap.to("#copy-button", { border: "2px solid #3b3b4f", duration: 1, }) setPreviewMarkdown({ isPreview: false, buttonText: "preview", }) resetCopyMarkdownButton() } } const resetCopyMarkdownButton = () => { var el = document.getElementById("copy-markdown") if (el) { gsap.set("#copy-markdown", { color: "#0a0a23", }) } setcopyObj({ isCopied: false, copiedText: "copy-markdown", }) } const setCopyMarkdownButton = () => { var el = document.getElementById("copy-markdown") if (el) { gsap.set("#copy-markdown", { color: "#00471b", }) } gsap.fromTo( "#copy-button", { scale: 0.5, }, { scale: 1, ease: "elastic.in", border: "2px solid #00471b", duration: 0.5, } ) setcopyObj({ isCopied: true, copiedText: "copied", }) } const handleCopyToClipboard = () => { var range = document.createRange() range.selectNode(document.getElementById("markdown-content")) window.getSelection().removeAllRanges() // clear current selection window.getSelection().addRange(range) // to select text document.execCommand("copy") window.getSelection().removeAllRanges() setCopyMarkdownButton() } const handleDownloadMarkdown = () => { var markdownContent = document.getElementById("markdown-content") var tempElement = document.createElement("a") tempElement.setAttribute( "href", "data:text/markdown;charset=utf-8," + encodeURIComponent(markdownContent.innerText) ) tempElement.setAttribute("download", "README.md") tempElement.style.display = "none" document.body.appendChild(tempElement) tempElement.click() document.body.removeChild(tempElement) } const handleDownloadJson = () => { var tempElement = document.createElement("a") tempElement.setAttribute( "href", `data:text/json;charset=utf-8,${encodeURIComponent( JSON.stringify({ prefix, data, link, social, skills }) )}` ) tempElement.setAttribute("download", "data.json") tempElement.style.display = "none" document.body.appendChild(tempElement) tempElement.click() document.body.removeChild(tempElement) } const handleBackToEdit = () => { setGeneratePreview(false) setGenerateMarkdown(false) setShowConfig(true) gsap.set("#form", { display: "", }) gsap.to(".generate", { scale: 1, }) } const setInitialValues = () => { const cache = JSON.parse(localStorage.getItem("cache")) if (!cache) { return } setPrefix(cache.prefix || DEFAULT_PREFIX) setData(cache.data || DEFAULT_DATA) setLink(cache.link || DEFAULT_LINK) setSocial(cache.social || DEFAULT_SOCIAL) const cacheSkills = mergeDefaultWithNewDataSkills( DEFAULT_SKILLS, cache.skills ) setSkills(cacheSkills || DEFAULT_SKILLS) } useEffect(() => { gsap.fromTo( ".generate", { boxShadow: "0 0 0 0px rgba(59, 59, 79, 0.4)", }, { boxShadow: "0 0 0 10px rgba(59, 59, 79, 0)", repeat: -1, duration: 1, } ) // set initial values setInitialValues() }, []) // keep cache updated KeepCacheUpdated({ prefix, data, link, social, skills }) const handleResetForm = () => { setPrefix(DEFAULT_PREFIX) setData(DEFAULT_DATA) setLink(DEFAULT_LINK) setSocial(DEFAULT_SOCIAL) setSkills(DEFAULT_SKILLS) } const mergeDefaultWithNewDataSkills = (defaultSkills, newSkills) => { return Object.keys(defaultSkills).reduce((previous, currentKey) => { let currentSelected = false if (newSkills[currentKey]) { currentSelected = true } return { ...previous, [currentKey]: currentSelected, } }, {}) } const handleRestore = () => { try { const restoreData = JSON.parse(restore) if (!restoreData) { return } setPrefix(restoreData.prefix || DEFAULT_PREFIX) setData(restoreData.data || DEFAULT_DATA) setLink(restoreData.link || DEFAULT_LINK) setSocial(restoreData.social || DEFAULT_SOCIAL) const restoreDataSkills = mergeDefaultWithNewDataSkills( DEFAULT_SKILLS, restoreData.skills ) setSkills(restoreDataSkills || DEFAULT_SKILLS) } catch (error) { } finally { setRestore("") } } return (
<Subtitle data={data} handleDataChange={handleDataChange} /> <Work prefix={prefix} data={data} link={link} handlePrefixChange={handlePrefixChange} handleLinkChange={handleLinkChange} handleDataChange={handleDataChange} /> <Skills skills={skills} handleSkillsChange={handleSkillsChange} /> <Social social={social} handleSocialChange={handleSocialChange} /> <Addons data={data} social={social} handleCheckChange={handleCheckChange} /> <div className="section"> {(data.visitorsBadge || data.githubStats || data.topLanguages) && !social.github ? ( <div className="warning"> * Please add github username to use these add-ons </div> ) : ( "" )} {social.github && !isGitHubUsernameValid(social.github) ? ( <div className="warning"> * GitHub username is invalid, please add a valid username </div> ) : ( "" )} {social.medium && !isMediumUsernameValid(social.medium) ? ( <div className="warning"> * Medium username is invalid, please add a valid username (with @) </div> ) : ( "" )} {data.mediumDynamicBlogs && !social.medium ? ( <div className="warning"> * Please add medium username to display latest blogs dynamically </div> ) : ( "" )} {data.devDynamicBlogs && !social.dev ? ( <div className="warning"> * Please add dev.to username to display latest blogs dynamically </div> ) : ( "" )} {data.rssDynamicBlogs && !social.rssurl ? ( <div className="warning"> * Please add your rss feed url to display latest blogs dynamically from your personal blog </div> ) : ( "" )} </div> <div className="flex items-center justify-center w-full"> <div className="text-xs sm:text-xl font-medium border-2 border-solid border-gray-900 bg-gray-100 flex items-center justify-center py-1 sm:py-2 px-2 sm:px-4 generate" tabIndex="0" role="button" onClick={handleGenerate} > Generate README </div> </div> </div> {displayLoader ? <Loader /> : ""} {generateMarkdown || generatePreview ? ( <div className="markdown-section p-4 sm:py-4 sm:px-10"> <div className="w-full flex justify-between items-center"> <div className="cursor-pointer text-base w-1/6 border-2 border-solid border-gray-900 bg-gray-100 flex items-center justify-center p-1" tabIndex="0" role="button" onClick={handleBackToEdit} > <ArrowLeftIcon size={16} /> <span className="hidden sm:block"> back to edit</span> </div> <div className="cursor-pointer text-base w-1/6 border-2 border-solid border-gray-900 bg-gray-100 flex items-center justify-center p-1" tabIndex="0" id="copy-button" role="button" onClick={handleCopyToClipboard} > {copyObj.isCopied === true ? ( <CheckIcon size={24} /> ) : ( <CopyIcon size={24} /> )} <span className="hidden sm:block" id="copy-markdown"> {copyObj.copiedText} </span> </div> <div className="cursor-pointer text-base w-1/6 border-2 border-solid border-gray-900 bg-gray-100 flex items-center justify-center p-1" tabIndex="0" id="download-md-button" role="button" onClick={handleDownloadMarkdown} > <DownloadIcon size={24} /> <span className="hidden sm:block" id="download-markdown"> download markdown </span> </div> <div className="cursor-pointer text-base w-1/6 border-2 border-solid border-gray-900 bg-gray-100 flex items-center justify-center p-1" tabIndex="0" id="download-json-button" role="button" onClick={handleDownloadJson} > <FileCodeIcon size={24} /> <span className="hidden sm:block" id="download-json"> download backup </span> </div> <div className="cursor-pointer text-base w-1/6 border-2 border-solid border-gray-900 bg-gray-100 flex items-center justify-center p-1" tabIndex="0" role="button" onClick={handleGeneratePreview} > {previewMarkdown.isPreview ? ( <MarkdownIcon size={16} /> ) : ( <EyeIcon size={16} /> )} <span className="hidden sm:block ml-1" id="preview-markdown"> {previewMarkdown.buttonText} </span> </div> </div> <div className="w-full flex justify-center items-center"> <div className="w-full text-sm text-gray-900 shadow-xl mt-2 p-4 bg-gray-100 border-2 border-solid border-gray-800" id="markdown-box" > {generatePreview ? ( <MarkdownPreview prefix={prefix} data={data} link={link} social={social} skills={skills} /> ) : ( "" )} {generateMarkdown ? ( <Markdown prefix={prefix} data={data} link={link} social={social} skills={skills} /> ) : ( "" )} </div> </div> <div className="mt-10" id="support"> <Donate /> </div> </div> ) : ( "" )} <div className={ "w-full shadow flex flex-col justify-center items-start mt-16 border-2 border-solid border-gray-600 py-2 px-4 " + (!showConfig ? "hidden" : "block") } > <div className="flex justify-between items-center w-full"> <div className="text-lg sm:text-2xl font-bold font-title mt-2 mb-2"> Config options <span className="bg-green-800 text-white text-xs sm:text-sm p-1 ml-1"> new feature </span> </div> <div className="text-xxs sm:text-sm border-2 w-auto px-2 border-solid border-gray-900 bg-gray-100 flex items-center justify-center" role="button" tabIndex="0" onClick={handleResetForm} > Reset form </div> </div> <div className="w-full flex justify-start items-center my-4"> <input type="text" className="outline-none w-1/2 mr-6 border-t-0 border-l-0 border-r-0 border solid border-gray-900 py-1 px-2 focus:border-blue-700 prefix" placeholder="JSON Backup" value={restore} onChange={e => setRestore(e.target.value)} /> <div className="text-xxs sm:text-sm border-2 w-32 border-solid border-gray-900 bg-gray-100 flex items-center justify-center py-1" role="button" tabIndex="0" onClick={handleRestore} > Restore </div> </div> <div className="flex flex-col items-start justify-center"> <div className="text-green-700 font-medium">Tips</div> <div className="text-sm sm:text-lg text-gray-700"> * Enter the downloaded JSON text to restore. </div> <div className="text-sm sm:text-lg text-gray-700"> * Press reset to reset the form. </div> </div> </div> </div> </Layout> ) } export default IndexPage