├── next.config.js ├── .prettierrc ├── screenshot.png ├── public ├── favicon.ico ├── loading.gif ├── tentocats.jpg ├── topguntocat.png ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png ├── mstile-150x150.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── browserconfig.xml ├── site.webmanifest └── powered-by-vercel.svg ├── .editorconfig ├── src ├── styles │ ├── index.css │ └── App.css ├── pages │ ├── api │ │ └── v1 │ │ │ └── [username].js │ ├── _app.js │ ├── _document.js │ └── index.js ├── utils │ ├── api │ │ ├── alerts.js │ │ └── fetch.js │ └── export.js └── components │ └── themes.js ├── .gitignore ├── CONTRIBUTING.md ├── package.json ├── LICENSE ├── README.md └── yarn.lock /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "trailingComma": "none" 4 | } 5 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/screenshot.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/public/loading.gif -------------------------------------------------------------------------------- /public/tentocats.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/public/tentocats.jpg -------------------------------------------------------------------------------- /public/topguntocat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/public/topguntocat.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sahandmohammadrehzaii/github-contributions-chart/HEAD/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /src/styles/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 62.5%; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | padding: 0; 8 | font-family: 'IBM Plex Mono', monospace; 9 | font-size: 1.6em; 10 | line-height: 1.6; 11 | } 12 | 13 | * { 14 | box-sizing: border-box; 15 | } 16 | -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/api/v1/[username].js: -------------------------------------------------------------------------------- 1 | import { fetchDataForAllYears } from '../../../utils/api/fetch' 2 | 3 | export default async (req, res) => { 4 | const { username, format } = req.query; 5 | const data = await fetchDataForAllYears(username, format); 6 | res.setHeader('Cache-Control', 's-maxage=3600, stale-while-revalidate') 7 | res.json(data); 8 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | .next 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 1. Fork it! 4 | 2. Create your feature branch: `git checkout -b my-new-feature` 5 | 3. Commit your changes: `git commit -m 'Add some feature'` 6 | 4. Push to the branch: `git push origin my-new-feature` 7 | 8 | *Remember that we have a pre-push hook with steps that analyzes and prevents mistakes.* 9 | 10 | **After your pull request is merged**, you can safely delete your branch. 11 | -------------------------------------------------------------------------------- /src/utils/api/alerts.js: -------------------------------------------------------------------------------- 1 | export const error = { 2 | profileDisabled: "Visiting the profile has failed.", 3 | imageInvalid: "No valid image uri has been specified.", 4 | imageUploadFailed: "Uploading the image to Twitter has failed." 5 | } 6 | 7 | export const success = { 8 | serverOn: "The server has started." 9 | } 10 | 11 | const alerts = { 12 | error, 13 | success 14 | }; 15 | 16 | export default alerts; 17 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/_app.js: -------------------------------------------------------------------------------- 1 | import { Toaster } from "react-hot-toast"; 2 | import { Analytics } from "@vercel/analytics/react"; 3 | import "normalize.css/normalize.css"; 4 | import "../styles/index.css"; 5 | import "../styles/App.css"; 6 | 7 | import Head from "next/head"; 8 | 9 | const App = ({ Component, pageProps }) => ( 10 | <> 11 | 12 | GitHub Contributions Chart Generator 13 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | 24 | export default App; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-contribution-chart", 3 | "description": "Generate an image of all your GitHub contributions", 4 | "version": "2.0.0", 5 | "engines": { 6 | "node": ">=16" 7 | }, 8 | "dependencies": { 9 | "@vercel/analytics": "^0.1.6", 10 | "cheerio": "^1.0.0-rc.3", 11 | "github-contributions-canvas": "^0.7.0", 12 | "lodash": "^4.17.19", 13 | "next": "^13.1.1", 14 | "normalize.css": "^8.0.1", 15 | "prop-types": "^15.7.2", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "react-hot-toast": "^2.4.0", 19 | "react-icons": "^4.7.1" 20 | }, 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/sallar/github-contributions-chart/issues" 24 | }, 25 | "author": "Sallar Kaboli ", 26 | "scripts": { 27 | "start": "next start", 28 | "build": "next build", 29 | "dev": "next dev" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sallar Kaboli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/themes.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { themes } from "github-contributions-canvas"; 4 | 5 | const availableThemes = { 6 | standard: "GitHub", 7 | classic: "GitHub Classic", 8 | githubDark: "GitHub Dark", 9 | halloween: "Halloween", 10 | teal: "Teal", 11 | leftPad: "@left_pad", 12 | dracula: "Dracula", 13 | blue: "Blue", 14 | panda: "Panda 🐼", 15 | sunny: "Sunny", 16 | pink: "Pink", 17 | YlGnBu: "YlGnBu", 18 | solarizedDark: 'Solarized Dark', 19 | solarizedLight: 'Solarized Light' 20 | }; 21 | 22 | const Preview = ({ themeName }) => ( 23 |
27 | 28 | 29 | 30 | 31 |
32 | ); 33 | 34 | const ThemeSelector = ({ currentTheme, onChangeTheme }) => ( 35 |
36 |
37 | Select a theme: 38 |
39 |
40 | {Object.keys(availableThemes).map((themeName) => ( 41 | 52 | ))} 53 |
54 |
55 | ); 56 | 57 | ThemeSelector.propTypes = { 58 | onChangeTheme: PropTypes.func.isRequired, 59 | currentTheme: PropTypes.string.isRequired 60 | }; 61 | 62 | export default ThemeSelector; 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :octocat: GitHub Contribution Chart Generator [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/sallar/github-contributions-canvas/blob/master/LICENSE) 2 | 3 | Generates an image of all your **GitHub** contributions since you have signed up, so you can use it in social media. 4 | 5 | The API for this project lives in the `src/pages/api` directory since GitHub doesn't provide a way to access user statistics through it's official API. 6 | 7 | The drawing mechanism lives in [the sallar/github-contributions-canvas repository](https://github.com/sallar/github-contributions-canvas). 8 | 9 | ## Requirements 10 | 11 | - A valid github account. 12 | - Open activity in setting (`Settings` > `Public profile` > `Contributions & Activity`). 13 | - [ ] Make profile private and hide activity 14 | 15 | ## Install 16 | 17 | Install the packages using [NPM](https://nodejs.org/en/): 18 | 19 | ``` 20 | $ npm install 21 | ``` 22 | 23 | ## How to run 24 | 25 | Running locally: 26 | 27 | ``` 28 | $ npm run dev 29 | ``` 30 | 31 | ## Deployment 32 | 33 | This project is deployed on [Vercel](https://vercel.com/). 34 | 35 | ## Adding themes 36 | 37 | Add your theme to [sallar/github-contribution-canvas](https://github.com/sallar/github-contributions-canvas) repo and also send a PR here to add the name of the theme to the list. 38 | 39 | ## Example 40 | 41 |
42 | 43 |
44 | 45 | ## Contributing 46 | 47 | Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 48 | 49 | ## Changelog 50 | 51 | Every release, along with the migration instructions, is documented on the GitHub [Releases](https://github.com/sallar/github-contributions-chart/releases) page. 52 | 53 | ## License 54 | 55 | [MIT license](https://opensource.org/licenses/MIT) 56 | 57 | [![Powered by Vercel](/public/powered-by-vercel.svg)](https://vercel.com/?utm_source=github-contributions-chart&utm_campaign=oss) 58 | -------------------------------------------------------------------------------- /src/pages/_document.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Document, { Html, Head, Main, NextScript } from "next/document"; 3 | 4 | const GA_TRACKING_ID = "UA-118649449-1"; 5 | 6 | export default class extends Document { 7 | render() { 8 | return ( 9 | 10 | 11 | 68 | 69 | 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/export.js: -------------------------------------------------------------------------------- 1 | import { toast } from "react-hot-toast"; 2 | 3 | const API_URL = "/api/v1/"; 4 | 5 | export function fetchData(username) { 6 | return fetch(API_URL + username).then((res) => res.json()); 7 | } 8 | 9 | export function download(canvas) { 10 | try { 11 | const dataUrl = canvas.toDataURL(); 12 | const a = document.createElement("a"); 13 | document.body.insertAdjacentElement("beforeend", a); 14 | a.download = "contributions.png"; 15 | a.href = dataUrl; 16 | a.click(); 17 | document.body.removeChild(a); 18 | } catch (err) { 19 | console.error(err); 20 | } 21 | } 22 | 23 | export function downloadJSON(data) { 24 | try { 25 | const dataString = JSON.stringify(data); 26 | const dataUrl = 27 | "data:text/json;charset=utf-8," + encodeURIComponent(dataString); 28 | const a = document.createElement("a"); 29 | document.body.insertAdjacentElement("beforeend", a); 30 | a.download = "contributions.json"; 31 | a.href = dataUrl; 32 | a.click(); 33 | document.body.removeChild(a); 34 | } catch (err) { 35 | console.error(err); 36 | } 37 | } 38 | 39 | export async function share(canvas) { 40 | try { 41 | canvas.toBlob(async (blob) => { 42 | navigator 43 | .share({ 44 | title: "GitHub Contributions", 45 | text: "Check out my #GitHubContributions history over time. A free tool by @sallar and friends. https://github-contributions.vercel.app", 46 | files: [ 47 | new File([blob], "contributions.png", { 48 | type: blob.type 49 | }) 50 | ] 51 | }) 52 | .catch(() => { 53 | // do nothing 54 | }); 55 | }, "image/png"); 56 | } catch (err) { 57 | console.error(err); 58 | } 59 | } 60 | 61 | export async function copyToClipboard(canvas) { 62 | if ("ClipboardItem" in window) { 63 | // https://bugs.webkit.org/show_bug.cgi?id=222262 64 | // https://web.dev/async-clipboard/ 65 | const item = new ClipboardItem({ 66 | "image/png": new Promise((resolve) => { 67 | canvas.toBlob(resolve, "image/png"); 68 | }) 69 | }); 70 | navigator.clipboard 71 | .write([item]) 72 | .then(() => toast("🎉 Copied image!")) 73 | .catch((err) => { 74 | toast("Sorry, copying image is not supported on this browser"); 75 | console.error("failed to copy"); 76 | }); 77 | } else { 78 | toast("Sorry, copying image is not supported on this browser"); 79 | console.error("failed to copy"); 80 | } 81 | } 82 | 83 | export function cleanUsername(username) { 84 | return username.replace(/^(http|https):\/\/(?!www\.)github\.com\//, ""); 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/api/fetch.js: -------------------------------------------------------------------------------- 1 | import cheerio from "cheerio"; 2 | import _ from "lodash"; 3 | 4 | const COLOR_MAP = { 5 | 0: "#ebedf0", 6 | 1: "#9be9a8", 7 | 2: "#40c463", 8 | 3: "#30a14e", 9 | 4: "#216e39" 10 | }; 11 | 12 | async function fetchYears(username) { 13 | const data = await fetch(`https://github.com/${username}?tab=contributions`, { 14 | headers: { 15 | "x-requested-with": "XMLHttpRequest" 16 | } 17 | }); 18 | const body = await data.text(); 19 | const $ = cheerio.load(body); 20 | return $(".js-year-link") 21 | .get() 22 | .map((a) => { 23 | const $a = $(a); 24 | const href = $a.attr("href"); 25 | const githubUrl = new URL(`https://github.com${href}`); 26 | githubUrl.searchParams.set("tab", "contributions"); 27 | const formattedHref = `${githubUrl.pathname}${githubUrl.search}`; 28 | 29 | return { 30 | href: formattedHref, 31 | text: $a.text().trim() 32 | }; 33 | }); 34 | } 35 | 36 | async function fetchDataForYear(url, year, format) { 37 | const data = await fetch(`https://github.com${url}`, { 38 | headers: { 39 | "x-requested-with": "XMLHttpRequest" 40 | } 41 | }); 42 | const $ = cheerio.load(await data.text()); 43 | const $days = $( 44 | "table.ContributionCalendar-grid td.ContributionCalendar-day" 45 | ); 46 | 47 | const contribText = $(".js-yearly-contributions h2") 48 | .text() 49 | .trim() 50 | .match(/^([0-9,]+)\s/); 51 | let contribCount; 52 | if (contribText) { 53 | [contribCount] = contribText; 54 | contribCount = parseInt(contribCount.replace(/,/g, ""), 10); 55 | } 56 | 57 | return { 58 | year, 59 | total: contribCount || 0, 60 | range: { 61 | start: $($days.get(0)).attr("data-date"), 62 | end: $($days.get($days.length - 1)).attr("data-date") 63 | }, 64 | contributions: (() => { 65 | const parseDay = (day, index) => { 66 | const $day = $(day); 67 | const date = $day 68 | .attr("data-date") 69 | .split("-") 70 | .map((d) => parseInt(d, 10)); 71 | const color = COLOR_MAP[$day.attr("data-level")]; 72 | const value = { 73 | date: $day.attr("data-date"), 74 | count: index === 0 ? contribCount : 0, 75 | color, 76 | intensity: $day.attr("data-level") || 0 77 | }; 78 | return { date, value }; 79 | }; 80 | 81 | if (format !== "nested") { 82 | return $days.get().map((day, index) => parseDay(day, index).value); 83 | } 84 | 85 | return $days.get().reduce((o, day, index) => { 86 | const { date, value } = parseDay(day, index); 87 | const [y, m, d] = date; 88 | if (!o[y]) o[y] = {}; 89 | if (!o[y][m]) o[y][m] = {}; 90 | o[y][m][d] = value; 91 | return o; 92 | }, {}); 93 | })() 94 | }; 95 | } 96 | 97 | export async function fetchDataForAllYears(username, format) { 98 | const years = await fetchYears(username); 99 | return Promise.all( 100 | years.map((year) => fetchDataForYear(year.href, year.text, format)) 101 | ).then((resp) => { 102 | return { 103 | years: (() => { 104 | const obj = {}; 105 | const arr = resp.map((year) => { 106 | const { contributions, ...rest } = year; 107 | _.setWith(obj, [rest.year], rest, Object); 108 | return rest; 109 | }); 110 | return format === "nested" ? obj : arr; 111 | })(), 112 | contributions: 113 | format === "nested" 114 | ? resp.reduce((acc, curr) => _.merge(acc, curr.contributions)) 115 | : resp 116 | .reduce((list, curr) => [...list, ...curr.contributions], []) 117 | .sort((a, b) => { 118 | if (a.date < b.date) return 1; 119 | else if (a.date > b.date) return -1; 120 | return 0; 121 | }) 122 | }; 123 | }); 124 | } 125 | -------------------------------------------------------------------------------- /public/powered-by-vercel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/styles/App.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg: #fff; 3 | --header-bg: #f4f5f6; 4 | --button-bg: #9b4dca; 5 | --button-disabled: #cfd0d4; 6 | --button-disabled-border: #cfd0d4; 7 | --button-text: #fff; 8 | --text: #606c76; 9 | --loading-text: #606c76; 10 | --input-border: #d1d1d1; 11 | --input-text: #000; 12 | } 13 | 14 | @media (prefers-color-scheme: dark) { 15 | :root { 16 | --bg: #111517; 17 | --header-bg: rgb(22, 28, 30); 18 | --text: rgb(164, 157, 145); 19 | --input-border: rgb(55, 68, 75); 20 | --button-disabled: rgb(42, 52, 57); 21 | --button-disabled-border: var(--input-border); 22 | --loading-text: #rgb(164, 157, 145); 23 | --input-text: #fff; 24 | } 25 | } 26 | 27 | html { 28 | color: var(--text); 29 | background-color: var(--bg); 30 | } 31 | 32 | .App { 33 | display: grid; 34 | grid-template-columns: 40rem 1fr; 35 | height: 100vh; 36 | } 37 | 38 | .App-header { 39 | background-color: var(--header-bg); 40 | min-height: 40rem; 41 | color: var(--text); 42 | padding: 1rem; 43 | overflow-y: scroll; 44 | } 45 | 46 | .App-content { 47 | overflow-y: scroll; 48 | padding: 1rem; 49 | text-align: center; 50 | display: flex; 51 | } 52 | 53 | .App-centered { 54 | flex: 1; 55 | display: flex; 56 | justify-content: center; 57 | align-items: center; 58 | padding: 2rem 0; 59 | } 60 | 61 | .App-title { 62 | font-size: 1.5em; 63 | } 64 | 65 | .App-loading { 66 | color: var(--loading-text); 67 | } 68 | 69 | .App-result { 70 | max-width: 676px; 71 | margin: 0 auto; 72 | flex: 1; 73 | } 74 | 75 | .App-result canvas { 76 | width: 100%; 77 | height: auto; 78 | } 79 | 80 | .App-logo { 81 | text-align: center; 82 | padding: 2rem 1rem 0; 83 | } 84 | 85 | .App-powered { 86 | margin-top: 1rem; 87 | } 88 | 89 | .App-powered img { 90 | width: auto; 91 | height: 3rem; 92 | } 93 | 94 | h1, 95 | h4 { 96 | font-weight: 300; 97 | margin: 0; 98 | padding: 0; 99 | } 100 | 101 | h1 { 102 | font-size: 3rem; 103 | line-height: 1.2; 104 | } 105 | 106 | h4 { 107 | margin-top: 1em; 108 | } 109 | 110 | h6 { 111 | text-transform: uppercase; 112 | font-weight: 600; 113 | margin: 2rem 0 0 0; 114 | } 115 | 116 | form { 117 | margin: 2em 0 1em; 118 | display: flex; 119 | justify-content: center; 120 | align-items: center; 121 | } 122 | 123 | input { 124 | border: 0.1rem solid var(--input-border); 125 | border-right: 0; 126 | border-radius: 0.4rem; 127 | padding: 0.6rem 1rem; 128 | height: 4rem; 129 | outline: none; 130 | color: var(--input-text); 131 | background-color: var(--bg); 132 | } 133 | 134 | button { 135 | height: 4rem; 136 | line-height: 4rem; 137 | padding: 0 1rem; 138 | white-space: nowrap; 139 | background-color: var(--button-bg); 140 | border: 0.1rem solid var(--button-bg); 141 | border-radius: 0.4rem; 142 | color: var(--button-text); 143 | font-size: 0.8em; 144 | outline: none; 145 | cursor: pointer; 146 | } 147 | 148 | .App-content button { 149 | height: 3rem; 150 | padding: 0 2rem; 151 | } 152 | 153 | button:disabled { 154 | background-color: var(--button-disabled); 155 | border: 0.1rem solid var(--button-disabled-border); 156 | cursor: not-allowed; 157 | } 158 | 159 | form input { 160 | width: 30rem; 161 | border-radius: 0.4rem 0 0 0.4rem; 162 | } 163 | 164 | form button { 165 | border-radius: 0 0.4rem 0.4rem 0; 166 | } 167 | 168 | .App-buttons { 169 | display: flex; 170 | align-items: center; 171 | justify-content: center; 172 | margin-bottom: 2rem; 173 | } 174 | 175 | .App-download-button { 176 | min-height: 2rem; 177 | line-height: 2rem; 178 | margin: 0 1rem; 179 | margin-right: 0.5rem; 180 | display: flex; 181 | justify-content: space-between; 182 | align-items: center; 183 | gap: 7px; 184 | } 185 | 186 | .App-themes label { 187 | display: flex; 188 | justify-content: flex-start; 189 | align-items: center; 190 | } 191 | 192 | .App-themes { 193 | border: 1px solid var(--button-disabled-border); 194 | border-radius: 0.4rem; 195 | margin-bottom: 1rem; 196 | padding: 1rem 1rem 2rem; 197 | } 198 | 199 | .App-themes h6 { 200 | margin: -2rem 0 1rem; 201 | } 202 | 203 | .App-themes h6 span { 204 | background-color: var(--header-bg); 205 | padding: 0 1rem; 206 | } 207 | 208 | .App-themes label { 209 | margin: 0 1rem; 210 | cursor: pointer; 211 | } 212 | 213 | .App-themes label input { 214 | margin-right: 0.5rem; 215 | } 216 | 217 | .Theme-preview { 218 | width: 22px; 219 | height: 22px; 220 | display: flex; 221 | flex-wrap: wrap; 222 | padding: 2px; 223 | border-radius: 2px; 224 | margin: 0 1rem; 225 | box-shadow: 0 0 3px rgba(0, 0, 0, 0.2); 226 | } 227 | 228 | .Theme-preview span { 229 | display: block; 230 | width: 9px; 231 | height: 9px; 232 | } 233 | 234 | footer { 235 | font-size: 1.2rem; 236 | } 237 | 238 | footer a { 239 | color: var(--text); 240 | text-decoration: none; 241 | } 242 | 243 | @media all and (max-width: 500px) { 244 | .App-header { 245 | padding: 1rem; 246 | font-size: 0.9em; 247 | } 248 | 249 | form { 250 | flex-direction: column; 251 | margin-bottom: 2rem; 252 | } 253 | 254 | form input, 255 | form button { 256 | width: 100%; 257 | height: 4rem; 258 | line-height: 4rem; 259 | padding: 0; 260 | border-radius: 0.4rem; 261 | } 262 | 263 | form input { 264 | margin-bottom: 0.5rem; 265 | text-align: center; 266 | } 267 | 268 | .App-buttons button { 269 | padding: 0 0.5rem; 270 | } 271 | } 272 | 273 | @media all and (max-width: 970px) { 274 | .App { 275 | display: block; 276 | text-align: center; 277 | } 278 | 279 | form input { 280 | flex: 1; 281 | } 282 | 283 | .App-themes-list { 284 | display: flex; 285 | flex-wrap: wrap; 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import { TbBrandTwitter, TbShare, TbDownload, TbCopy } from "react-icons/tb"; 2 | import React, { useRef, useState, useEffect } from "react"; 3 | import { 4 | download, 5 | fetchData, 6 | downloadJSON, 7 | cleanUsername, 8 | share, 9 | copyToClipboard 10 | } from "../utils/export"; 11 | import ThemeSelector from "../components/themes"; 12 | 13 | const App = () => { 14 | const inputRef = useRef(); 15 | const canvasRef = useRef(); 16 | const contentRef = useRef(); 17 | const [loading, setLoading] = useState(false); 18 | const [username, setUsername] = useState(""); 19 | const [theme, setTheme] = useState("standard"); 20 | const [data, setData] = useState(null); 21 | const [error, setError] = useState(null); 22 | 23 | useEffect(() => { 24 | if (!data) { 25 | return; 26 | } 27 | draw(); 28 | }, [data, theme]); 29 | 30 | const handleSubmit = (e) => { 31 | e.preventDefault(); 32 | 33 | setUsername(cleanUsername(username)); 34 | setLoading(true); 35 | setError(null); 36 | setData(null); 37 | 38 | fetchData(cleanUsername(username)) 39 | .then((data) => { 40 | setLoading(false); 41 | 42 | if (data.years.length === 0) { 43 | setError("Could not find your profile"); 44 | } else { 45 | setData(data); 46 | } 47 | }) 48 | .catch((err) => { 49 | console.log(err); 50 | setLoading(false); 51 | setError("I could not check your profile successfully..."); 52 | }); 53 | }; 54 | 55 | const onDownload = (e) => { 56 | e.preventDefault(); 57 | download(canvasRef.current); 58 | }; 59 | 60 | const onCopy = (e) => { 61 | e.preventDefault(); 62 | copyToClipboard(canvasRef.current); 63 | }; 64 | 65 | const onDownloadJson = (e) => { 66 | e.preventDefault(); 67 | if (data != null) { 68 | downloadJSON(data); 69 | } 70 | }; 71 | 72 | const onShare = (e) => { 73 | e.preventDefault(); 74 | share(canvasRef.current); 75 | }; 76 | 77 | const draw = async () => { 78 | if (!canvasRef.current || !data) { 79 | setError("Something went wrong... Check back later."); 80 | return; 81 | } 82 | 83 | const { drawContributions } = await import("github-contributions-canvas"); 84 | 85 | drawContributions(canvasRef.current, { 86 | data, 87 | username: username, 88 | themeName: theme, 89 | footerText: "Made by @sallar & friends - github-contributions.vercel.app" 90 | }); 91 | contentRef.current.scrollIntoView({ 92 | behavior: "smooth" 93 | }); 94 | }; 95 | 96 | const _renderGithubButton = () => { 97 | return ( 98 |
99 | 106 | Star 107 | 108 |
109 | ); 110 | }; 111 | 112 | const _renderLoading = () => { 113 | return ( 114 |
115 |
116 | Loading... 117 |

Please wait, I’m visiting your profile...

118 |
119 |
120 | ); 121 | }; 122 | 123 | const _renderGraphs = () => { 124 | return ( 125 |
129 |

Your chart is ready!

130 | 131 | {data !== null && ( 132 | <> 133 |
134 | 142 | 150 | {global.navigator && "share" in navigator && ( 151 | 159 | )} 160 |
161 | 162 | 163 | 164 | )} 165 |
166 | ); 167 | }; 168 | 169 | const _renderForm = () => { 170 | return ( 171 |
172 | setUsername(e.target.value)} 176 | value={username} 177 | id="username" 178 | autoCorrect="off" 179 | autoCapitalize="none" 180 | autoFocus 181 | /> 182 | 188 |
189 | ); 190 | }; 191 | 192 | const _renderError = () => { 193 | return ( 194 |
195 |

{error}

196 |
197 | ); 198 | }; 199 | 200 | const _renderDownloadAsJSON = () => { 201 | if (data === null) return; 202 | return ( 203 | 204 | 205 | 📊 206 | {" "} 207 | Download data as JSON for your own visualizations 208 | 209 | ); 210 | }; 211 | 212 | return ( 213 |
214 |
215 |
216 | Topguntocat 217 |

GitHub Contributions Chart Generator

218 |

All your contributions in one image!

219 |
220 | {_renderForm()} 221 | setTheme(themeName)} 224 | /> 225 | {_renderGithubButton()} 226 | 244 |
245 |
246 | {loading && _renderLoading()} 247 | {error !== null && _renderError()} 248 | {_renderGraphs()} 249 |
250 |
251 | ); 252 | }; 253 | 254 | export default App; 255 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@next/env@13.1.1": 6 | version "13.1.1" 7 | resolved "https://registry.npmjs.org/@next/env/-/env-13.1.1.tgz" 8 | integrity sha512-vFMyXtPjSAiOXOywMojxfKIqE3VWN5RCAx+tT3AS3pcKjMLFTCJFUWsKv8hC+87Z1F4W3r68qTwDFZIFmd5Xkw== 9 | 10 | "@next/swc-android-arm-eabi@13.1.1": 11 | version "13.1.1" 12 | resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.1.1.tgz#b5c3cd1f79d5c7e6a3b3562785d4e5ac3555b9e1" 13 | integrity sha512-qnFCx1kT3JTWhWve4VkeWuZiyjG0b5T6J2iWuin74lORCupdrNukxkq9Pm+Z7PsatxuwVJMhjUoYz7H4cWzx2A== 14 | 15 | "@next/swc-android-arm64@13.1.1": 16 | version "13.1.1" 17 | resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-13.1.1.tgz#e2ca9ccbba9ef770cb19fbe96d1ac00fe4cb330d" 18 | integrity sha512-eCiZhTzjySubNqUnNkQCjU3Fh+ep3C6b5DCM5FKzsTH/3Gr/4Y7EiaPZKILbvnXmhWtKPIdcY6Zjx51t4VeTfA== 19 | 20 | "@next/swc-darwin-arm64@13.1.1": 21 | version "13.1.1" 22 | resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.1.tgz#4af00877332231bbd5a3703435fdd0b011e74767" 23 | integrity sha512-9zRJSSIwER5tu9ADDkPw5rIZ+Np44HTXpYMr0rkM656IvssowPxmhK0rTreC1gpUCYwFsRbxarUJnJsTWiutPg== 24 | 25 | "@next/swc-darwin-x64@13.1.1": 26 | version "13.1.1" 27 | resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.1.tgz#bf4cb09e7e6ec6d91e031118dde2dd17078bcbbc" 28 | integrity sha512-qWr9qEn5nrnlhB0rtjSdR00RRZEtxg4EGvicIipqZWEyayPxhUu6NwKiG8wZiYZCLfJ5KWr66PGSNeDMGlNaiA== 29 | 30 | "@next/swc-freebsd-x64@13.1.1": 31 | version "13.1.1" 32 | resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.1.1.tgz#6933ea1264328e8523e28818f912cd53824382d4" 33 | integrity sha512-UwP4w/NcQ7V/VJEj3tGVszgb4pyUCt3lzJfUhjDMUmQbzG9LDvgiZgAGMYH6L21MoyAATJQPDGiAMWAPKsmumA== 34 | 35 | "@next/swc-linux-arm-gnueabihf@13.1.1": 36 | version "13.1.1" 37 | resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.1.1.tgz#b5896967aaba3873d809c3ad2e2039e89acde419" 38 | integrity sha512-CnsxmKHco9sosBs1XcvCXP845Db+Wx1G0qouV5+Gr+HT/ZlDYEWKoHVDgnJXLVEQzq4FmHddBNGbXvgqM1Gfkg== 39 | 40 | "@next/swc-linux-arm64-gnu@13.1.1": 41 | version "13.1.1" 42 | resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.1.1.tgz#91b3e9ea8575b1ded421c0ea0739b7bccf228469" 43 | integrity sha512-JfDq1eri5Dif+VDpTkONRd083780nsMCOKoFG87wA0sa4xL8LGcXIBAkUGIC1uVy9SMsr2scA9CySLD/i+Oqiw== 44 | 45 | "@next/swc-linux-arm64-musl@13.1.1": 46 | version "13.1.1" 47 | resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.1.1.tgz#83149ea05d7d55f3664d608dbe004c0d125f9147" 48 | integrity sha512-GA67ZbDq2AW0CY07zzGt07M5b5Yaq5qUpFIoW3UFfjOPgb0Sqf3DAW7GtFMK1sF4ROHsRDMGQ9rnT0VM2dVfKA== 49 | 50 | "@next/swc-linux-x64-gnu@13.1.1": 51 | version "13.1.1" 52 | resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.1.1.tgz#d7d0777b56de0dd82b78055772e13e18594a15ca" 53 | integrity sha512-nnjuBrbzvqaOJaV+XgT8/+lmXrSCOt1YYZn/irbDb2fR2QprL6Q7WJNgwsZNxiLSfLdv+2RJGGegBx9sLBEzGA== 54 | 55 | "@next/swc-linux-x64-musl@13.1.1": 56 | version "13.1.1" 57 | resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.1.1.tgz#41655722b127133cd95ab5bc8ca1473e9ab6876f" 58 | integrity sha512-CM9xnAQNIZ8zf/igbIT/i3xWbQZYaF397H+JroF5VMOCUleElaMdQLL5riJml8wUfPoN3dtfn2s4peSr3azz/g== 59 | 60 | "@next/swc-win32-arm64-msvc@13.1.1": 61 | version "13.1.1" 62 | resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.1.1.tgz#f10da3dfc9b3c2bbd202f5d449a9b807af062292" 63 | integrity sha512-pzUHOGrbgfGgPlOMx9xk3QdPJoRPU+om84hqVoe6u+E0RdwOG0Ho/2UxCgDqmvpUrMab1Deltlt6RqcXFpnigQ== 64 | 65 | "@next/swc-win32-ia32-msvc@13.1.1": 66 | version "13.1.1" 67 | resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.1.1.tgz#4c0102b9b18ece15c818056d07e3917ee9dade78" 68 | integrity sha512-WeX8kVS46aobM9a7Xr/kEPcrTyiwJqQv/tbw6nhJ4fH9xNZ+cEcyPoQkwPo570dCOLz3Zo9S2q0E6lJ/EAUOBg== 69 | 70 | "@next/swc-win32-x64-msvc@13.1.1": 71 | version "13.1.1" 72 | resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.1.1.tgz" 73 | integrity sha512-mVF0/3/5QAc5EGVnb8ll31nNvf3BWpPY4pBb84tk+BfQglWLqc5AC9q1Ht/YMWiEgs8ALNKEQ3GQnbY0bJF2Gg== 74 | 75 | "@swc/helpers@0.4.14": 76 | version "0.4.14" 77 | resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz" 78 | integrity sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw== 79 | dependencies: 80 | tslib "^2.4.0" 81 | 82 | "@vercel/analytics@^0.1.6": 83 | version "0.1.6" 84 | resolved "https://registry.npmjs.org/@vercel/analytics/-/analytics-0.1.6.tgz" 85 | integrity sha512-zNd5pj3iDvq8IMBQHa1YRcIteiw6ZiPB8AsONHd8ieFXlNpLqhXfIYnf4WvTfZ7S1NSJ++mIM14aJnNac/VMXQ== 86 | 87 | boolbase@^1.0.0: 88 | version "1.0.0" 89 | resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" 90 | integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== 91 | 92 | caniuse-lite@^1.0.30001406: 93 | version "1.0.30001441" 94 | resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz" 95 | integrity sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg== 96 | 97 | cheerio-select-tmp@^0.1.0: 98 | version "0.1.1" 99 | resolved "https://registry.npmjs.org/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz" 100 | integrity sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ== 101 | dependencies: 102 | css-select "^3.1.2" 103 | css-what "^4.0.0" 104 | domelementtype "^2.1.0" 105 | domhandler "^4.0.0" 106 | domutils "^2.4.4" 107 | 108 | cheerio@^1.0.0-rc.3: 109 | version "1.0.0-rc.5" 110 | resolved "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.5.tgz" 111 | integrity sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw== 112 | dependencies: 113 | cheerio-select-tmp "^0.1.0" 114 | dom-serializer "~1.2.0" 115 | domhandler "^4.0.0" 116 | entities "~2.1.0" 117 | htmlparser2 "^6.0.0" 118 | parse5 "^6.0.0" 119 | parse5-htmlparser2-tree-adapter "^6.0.0" 120 | 121 | client-only@0.0.1: 122 | version "0.0.1" 123 | resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" 124 | integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== 125 | 126 | css-select@^3.1.2: 127 | version "3.1.2" 128 | resolved "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz" 129 | integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA== 130 | dependencies: 131 | boolbase "^1.0.0" 132 | css-what "^4.0.0" 133 | domhandler "^4.0.0" 134 | domutils "^2.4.3" 135 | nth-check "^2.0.0" 136 | 137 | css-what@^4.0.0: 138 | version "4.0.0" 139 | resolved "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz" 140 | integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A== 141 | 142 | date-fns@^2.29.3: 143 | version "2.29.3" 144 | resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" 145 | integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== 146 | 147 | dom-serializer@^1.0.1, dom-serializer@~1.2.0: 148 | version "1.2.0" 149 | resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz" 150 | integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA== 151 | dependencies: 152 | domelementtype "^2.0.1" 153 | domhandler "^4.0.0" 154 | entities "^2.0.0" 155 | 156 | domelementtype@^2.0.1, domelementtype@^2.1.0: 157 | version "2.1.0" 158 | resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz" 159 | integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== 160 | 161 | domhandler@^4.0.0: 162 | version "4.0.0" 163 | resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz" 164 | integrity sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA== 165 | dependencies: 166 | domelementtype "^2.1.0" 167 | 168 | domutils@^2.4.3, domutils@^2.4.4: 169 | version "2.5.0" 170 | resolved "https://registry.npmjs.org/domutils/-/domutils-2.5.0.tgz" 171 | integrity sha512-Ho16rzNMOFk2fPwChGh3D2D9OEHAfG19HgmRR2l+WLSsIstNsAYBzePH412bL0y5T44ejABIVfTHQ8nqi/tBCg== 172 | dependencies: 173 | dom-serializer "^1.0.1" 174 | domelementtype "^2.0.1" 175 | domhandler "^4.0.0" 176 | 177 | entities@^2.0.0: 178 | version "2.2.0" 179 | resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" 180 | integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== 181 | 182 | entities@~2.1.0: 183 | version "2.1.0" 184 | resolved "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz" 185 | integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== 186 | 187 | github-contributions-canvas@^0.7.0: 188 | version "0.7.0" 189 | resolved "https://registry.yarnpkg.com/github-contributions-canvas/-/github-contributions-canvas-0.7.0.tgz#03d5eeb8b8942beaebaad63ac59fdee1266932a7" 190 | integrity sha512-4dUBdWEq+yriCDp5XTKe2ETvTiaZbOSLYpG9r3SoKnnvLU2LrXb14Xgyg07UvrCspCGKRcp+pGwxG9zpqhGg1w== 191 | dependencies: 192 | date-fns "^2.29.3" 193 | 194 | goober@^2.1.10: 195 | version "2.1.12" 196 | resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.12.tgz#6c1645314ac9a68fe76408e1f502c63df8a39042" 197 | integrity sha512-yXHAvO08FU1JgTXX6Zn6sYCUFfB/OJSX8HHjDSgerZHZmFKAb08cykp5LBw5QnmyMcZyPRMqkdyHUSSzge788Q== 198 | 199 | htmlparser2@^6.0.0: 200 | version "6.0.1" 201 | resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.0.1.tgz" 202 | integrity sha512-GDKPd+vk4jvSuvCbyuzx/unmXkk090Azec7LovXP8as1Hn8q9p3hbjmDGbUqqhknw0ajwit6LiiWqfiTUPMK7w== 203 | dependencies: 204 | domelementtype "^2.0.1" 205 | domhandler "^4.0.0" 206 | domutils "^2.4.4" 207 | entities "^2.0.0" 208 | 209 | "js-tokens@^3.0.0 || ^4.0.0": 210 | version "4.0.0" 211 | resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" 212 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 213 | 214 | lodash@^4.17.19: 215 | version "4.17.21" 216 | resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" 217 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 218 | 219 | loose-envify@^1.1.0, loose-envify@^1.4.0: 220 | version "1.4.0" 221 | resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" 222 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 223 | dependencies: 224 | js-tokens "^3.0.0 || ^4.0.0" 225 | 226 | nanoid@^3.3.4: 227 | version "3.3.4" 228 | resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz" 229 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== 230 | 231 | next@^13.1.1: 232 | version "13.1.1" 233 | resolved "https://registry.npmjs.org/next/-/next-13.1.1.tgz" 234 | integrity sha512-R5eBAaIa3X7LJeYvv1bMdGnAVF4fVToEjim7MkflceFPuANY3YyvFxXee/A+acrSYwYPvOvf7f6v/BM/48ea5w== 235 | dependencies: 236 | "@next/env" "13.1.1" 237 | "@swc/helpers" "0.4.14" 238 | caniuse-lite "^1.0.30001406" 239 | postcss "8.4.14" 240 | styled-jsx "5.1.1" 241 | optionalDependencies: 242 | "@next/swc-android-arm-eabi" "13.1.1" 243 | "@next/swc-android-arm64" "13.1.1" 244 | "@next/swc-darwin-arm64" "13.1.1" 245 | "@next/swc-darwin-x64" "13.1.1" 246 | "@next/swc-freebsd-x64" "13.1.1" 247 | "@next/swc-linux-arm-gnueabihf" "13.1.1" 248 | "@next/swc-linux-arm64-gnu" "13.1.1" 249 | "@next/swc-linux-arm64-musl" "13.1.1" 250 | "@next/swc-linux-x64-gnu" "13.1.1" 251 | "@next/swc-linux-x64-musl" "13.1.1" 252 | "@next/swc-win32-arm64-msvc" "13.1.1" 253 | "@next/swc-win32-ia32-msvc" "13.1.1" 254 | "@next/swc-win32-x64-msvc" "13.1.1" 255 | 256 | normalize.css@^8.0.1: 257 | version "8.0.1" 258 | resolved "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz" 259 | integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== 260 | 261 | nth-check@^2.0.0: 262 | version "2.1.1" 263 | resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" 264 | integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== 265 | dependencies: 266 | boolbase "^1.0.0" 267 | 268 | object-assign@^4.1.1: 269 | version "4.1.1" 270 | resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" 271 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 272 | 273 | parse5-htmlparser2-tree-adapter@^6.0.0: 274 | version "6.0.1" 275 | resolved "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz" 276 | integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== 277 | dependencies: 278 | parse5 "^6.0.1" 279 | 280 | parse5@^6.0.0, parse5@^6.0.1: 281 | version "6.0.1" 282 | resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" 283 | integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== 284 | 285 | picocolors@^1.0.0: 286 | version "1.0.0" 287 | resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" 288 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 289 | 290 | postcss@8.4.14: 291 | version "8.4.14" 292 | resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz" 293 | integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== 294 | dependencies: 295 | nanoid "^3.3.4" 296 | picocolors "^1.0.0" 297 | source-map-js "^1.0.2" 298 | 299 | prop-types@^15.7.2: 300 | version "15.7.2" 301 | resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz" 302 | integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== 303 | dependencies: 304 | loose-envify "^1.4.0" 305 | object-assign "^4.1.1" 306 | react-is "^16.8.1" 307 | 308 | react-dom@^18.2.0: 309 | version "18.2.0" 310 | resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" 311 | integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== 312 | dependencies: 313 | loose-envify "^1.1.0" 314 | scheduler "^0.23.0" 315 | 316 | react-hot-toast@^2.4.0: 317 | version "2.4.0" 318 | resolved "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.0.tgz" 319 | integrity sha512-qnnVbXropKuwUpriVVosgo8QrB+IaPJCpL8oBI6Ov84uvHZ5QQcTp2qg6ku2wNfgJl6rlQXJIQU5q+5lmPOutA== 320 | dependencies: 321 | goober "^2.1.10" 322 | 323 | react-icons@^4.7.1: 324 | version "4.7.1" 325 | resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.7.1.tgz#0f4b25a5694e6972677cb189d2a72eabea7a8345" 326 | integrity sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw== 327 | 328 | react-is@^16.8.1: 329 | version "16.13.1" 330 | resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" 331 | integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== 332 | 333 | react@^18.2.0: 334 | version "18.2.0" 335 | resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" 336 | integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== 337 | dependencies: 338 | loose-envify "^1.1.0" 339 | 340 | scheduler@^0.23.0: 341 | version "0.23.0" 342 | resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz" 343 | integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== 344 | dependencies: 345 | loose-envify "^1.1.0" 346 | 347 | source-map-js@^1.0.2: 348 | version "1.0.2" 349 | resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" 350 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 351 | 352 | styled-jsx@5.1.1: 353 | version "5.1.1" 354 | resolved "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz" 355 | integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw== 356 | dependencies: 357 | client-only "0.0.1" 358 | 359 | tslib@^2.4.0: 360 | version "2.4.1" 361 | resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz" 362 | integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== 363 | --------------------------------------------------------------------------------