├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .prettierignore ├── .prettierrc.js ├── LICENSE.txt ├── README.md ├── commitlint.config.js ├── components ├── screen │ └── Home │ │ ├── Components │ │ ├── CanvasWrapper.tsx │ │ ├── EditorCanvas.tsx │ │ ├── InputBox.tsx │ │ └── index.ts │ │ ├── Data │ │ ├── FontPairs.ts │ │ ├── Patterns.ts │ │ └── index.ts │ │ ├── Home.tsx │ │ └── index.ts └── shared │ └── Header │ ├── Header.tsx │ └── index.ts ├── context ├── index.ts └── inputContext.tsx ├── github └── preview.png ├── hooks ├── index.ts └── useAnalytics.tsx ├── lib └── analytics │ ├── analytics.ts │ └── index.ts ├── next-env.d.ts ├── next-sitemap.config.js ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── _document.tsx └── index.tsx ├── patterns ├── _defaults.js ├── _utils.js ├── anchors-away.js ├── architect.js ├── autumn.js ├── aztec.js ├── bamboo.js ├── bank-note.js ├── bathroom-floor.js ├── bevel-circle.js ├── boxes.js ├── brick-wall.js ├── bubbles.js ├── cage.js ├── charlie-brown.js ├── church-on-sunday.js ├── circles-and-squares.js ├── circuit-board.js ├── connections.js ├── cork-screw.js ├── current.js ├── curtain.js ├── cutout.js ├── death-star.js ├── diagonal-lines.js ├── diagonal-stripes.js ├── dominos.js ├── endless-clouds.js ├── eyes.js ├── falling-triangles.js ├── fancy-rectangles.js ├── flipped-diamonds.js ├── floating-cogs.js ├── floor-tile.js ├── formal-invitation.js ├── four-point-stars.js ├── glamorous.js ├── graph-paper.js ├── groovy.js ├── happy-intersection.js ├── heavy-rain.js ├── hexagons.js ├── hideout.js ├── houndstooth.js ├── i-like-food.js ├── intersecting-circles.js ├── jigsaw.js ├── jupiter.js ├── kiwi.js ├── leaf.js ├── lines-in-motion.js ├── lips.js ├── lisbon.js ├── melt.js ├── moroccan.js ├── morphing-diamonds.js ├── overcast.js ├── overlapping-circles.js ├── overlapping-diamonds.js ├── overlapping-hexagons.js ├── parkay-floor.js ├── piano-man.js ├── pie-factory.js ├── pixel-dots.js ├── plus.js ├── polka-dots.js ├── rails.js ├── rain.js ├── random-shapes.js ├── rounded-plus-connected.js ├── signal.js ├── skulls.js ├── slanted-stars.js ├── squares-in-squares.js ├── squares.js ├── stamp-collection.js ├── steel-beams.js ├── stripes.js ├── temple.js ├── texture.js ├── tic-tac-toe.js ├── tiny-checkers.js ├── topography.js ├── volcano-lamp.js ├── wallpaper.js ├── wiggle.js ├── x-equals.js ├── yyy.js └── zig-zag.js ├── postcss.config.js ├── public ├── android-icon-144x144.png ├── android-icon-192x192.png ├── android-icon-36x36.png ├── android-icon-48x48.png ├── android-icon-72x72.png ├── android-icon-96x96.png ├── apple-icon-114x114.png ├── apple-icon-120x120.png ├── apple-icon-144x144.png ├── apple-icon-152x152.png ├── apple-icon-180x180.png ├── apple-icon-57x57.png ├── apple-icon-60x60.png ├── apple-icon-72x72.png ├── apple-icon-76x76.png ├── apple-icon-precomposed.png ├── apple-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── favicon.ico ├── manifest.json ├── ms-icon-144x144.png ├── ms-icon-150x150.png ├── ms-icon-310x310.png ├── ms-icon-70x70.png ├── robots.txt ├── sitemap-0.xml └── sitemap.xml ├── styles └── globals.css ├── tailwind.config.js ├── tailwind ├── tailwind.settings.fontSizes.js ├── tailwind.settings.js └── tailwind.settings.screens.js ├── tsconfig.json ├── types └── place.txt ├── utils ├── helpers │ └── place.txt └── scripts │ └── place.txt └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | tailwind 4 | tailwind.config.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: [ 5 | 'next', 6 | 'next/core-web-vitals', 7 | 'plugin:react/recommended', 8 | 'plugin:jsx-a11y/recommended', 9 | 'plugin:prettier/recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | ], 12 | plugins: ['react', 'simple-import-sort', 'import', '@typescript-eslint'], 13 | rules: { 14 | 'prettier/prettier': ['warn', {}, { usePrettierrc: true }], 15 | 'react/react-in-jsx-scope': 'off', 16 | 'react/prop-types': 'off', 17 | 'simple-import-sort/imports': 'error', 18 | 'simple-import-sort/exports': 'error', 19 | 'import/first': 'error', 20 | 'import/newline-after-import': 'error', 21 | 'import/no-duplicates': 'error', 22 | 'react/jsx-sort-props': [ 23 | 'error', 24 | { 25 | ignoreCase: true, 26 | reservedFirst: true, 27 | }, 28 | ], 29 | 'jsx-a11y/anchor-is-valid': [ 30 | 'error', 31 | { 32 | components: ['Link'], 33 | specialLink: ['hrefLeft', 'hrefRight'], 34 | aspects: ['invalidHref', 'preferButton'], 35 | }, 36 | ], 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run precommit 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | tabWidth: 2, 4 | printWidth: 80, 5 | singleQuote: true, 6 | trailingComma: 'es5', 7 | jsxBracketSameLine: true, 8 | useTabs: false, 9 | }; 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2022 Vadivazhagan Vadivel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | OpenGrph 3 |

4 | 5 |

Create open graph & cover images just by click of button 🚀

6 | 7 | ![](./github/preview.png) 8 | 9 | ## ⚡ Features 10 | 11 | - ❤️ 21 elegant looking font pairs to match your feel 12 | - ✨ 88 background patterns 13 | - 🎖️ 3 themes, 2 modes 14 | - ⚽ draggable elements in canvas 15 | - 📥 download images in `png` format 16 | 17 | ## ⚒️ Development 18 | 19 | ``` 20 | git clone git@github.com:Grassper/OpenGrph.git 21 | 22 | yarn 23 | 24 | yarn dev // start local server 25 | ``` 26 | 27 | ## 👇 Contribution 28 | 29 | Pull requests are welcome. 30 | 31 | _For major changes, please open an issue first to discuss what you would like to change._ 32 | 33 | ## 🪪 License 34 | 35 | MIT - Vadivazhagan Vadivel 2022 36 | 37 | Don't forget to leave a ⭐ if you found this useful 38 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /components/screen/Home/Components/CanvasWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BsDownload } from 'react-icons/bs'; 3 | 4 | import { InputContext } from '@/root/context'; 5 | 6 | interface CanvasWrapper_ { 7 | children: React.ReactNode; 8 | } 9 | 10 | export const CanvasWrapper: React.FC = (props) => { 11 | const inputState = React.useContext(InputContext); 12 | const editorRef = React.useRef(null); 13 | 14 | const downloadHandler = async () => { 15 | const key = inputState.format as 'Png' | 'Jpeg'; 16 | 17 | const { exportComponentAsJPEG, exportComponentAsPNG } = await import( 18 | 'react-component-export-image' 19 | ); 20 | 21 | const formalFun = { 22 | Png: () => 23 | exportComponentAsPNG(editorRef, { 24 | fileName: 'OpenGrph', 25 | }), 26 | Jpeg: () => 27 | exportComponentAsJPEG(editorRef, { 28 | fileName: 'OpenGrph', 29 | }), 30 | }; 31 | 32 | formalFun[key](); 33 | }; 34 | 35 | return ( 36 |
37 |
38 | 43 |
44 |
50 | {props.children} 51 |
52 |
53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /components/screen/Home/Components/EditorCanvas.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from 'framer-motion'; 2 | import React from 'react'; 3 | 4 | import { fontPairs, patterns } from '@/root/components/screen/Home/Data'; 5 | import { InputContext } from '@/root/context'; 6 | 7 | import { CanvasWrapper } from './CanvasWrapper'; 8 | 9 | export const EditorCanvas = () => { 10 | const inputState = React.useContext(InputContext); 11 | const [backgroundImage, setBackgroundImage] = React.useState(''); 12 | 13 | React.useEffect(() => { 14 | const patternHandler = async () => { 15 | if (inputState.pattern !== 'none') { 16 | const module = await import( 17 | `@/root/patterns/${patterns[inputState.pattern]}` 18 | ); 19 | setBackgroundImage(module[Object.keys(module)[0]]('#fff')); 20 | } else { 21 | setBackgroundImage(''); 22 | } 23 | }; 24 | 25 | patternHandler(); 26 | }, [inputState.pattern]); 27 | 28 | if (inputState.theme === 'Modern') { 29 | return ( 30 | 31 |
32 |
33 | 47 | {inputState.title.substring(0, 70)} 48 | 49 | 65 | {inputState.description.substring(0, 160)} 66 | 67 |
68 |
74 |
75 |
76 | ); 77 | } 78 | 79 | if (inputState.theme === 'Clean') { 80 | return ( 81 | 82 |
83 |
84 | 98 | {inputState.title.substring(0, 70)} 99 | 100 | 116 | {inputState.description.substring(0, 160)} 117 | 118 |
119 |
120 |
121 | ); 122 | } 123 | 124 | return ( 125 | 126 |
127 |
133 |
134 | 147 | {inputState.title.substring(0, 70)} 148 | 149 | 165 | {inputState.description.substring(0, 160)} 166 | 167 |
168 |
169 |
170 | ); 171 | }; 172 | -------------------------------------------------------------------------------- /components/screen/Home/Components/InputBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | fontPairs as fontData, 5 | patterns as patternData, 6 | } from '@/root/components/screen/Home/Data'; 7 | import { InputContext } from '@/root/context'; 8 | 9 | export const InputBox: React.FC = () => { 10 | const inputState = React.useContext(InputContext); 11 | 12 | return ( 13 |
14 |
15 |
16 |

Title

17 |

Max 70 chars

18 |
19 | 20 | inputState.setTitle(e.target.value)} 24 | value={inputState.title} 25 | /> 26 |
27 |
28 |
29 |

Description

30 |

Max 160 chars

31 |
32 | inputState.setDescription(e.target.value)} 36 | value={inputState.description} 37 | /> 38 |
39 |
40 |

Font pairs

41 | 68 |
69 |
70 |
71 |

Theme

72 | 80 |
81 |
82 |

Color

83 | inputState.setColor(e.target.value)} 86 | type="color" 87 | value={inputState.color} 88 | /> 89 |
90 |
91 |
92 |
93 |

Pattern

94 | 189 |
190 | {/*
191 |

Format

192 | 199 |
*/} 200 |
201 |

Mode

202 | 209 |
210 |
211 |
212 | ); 213 | }; 214 | -------------------------------------------------------------------------------- /components/screen/Home/Components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './EditorCanvas'; 2 | export * from './InputBox'; 3 | -------------------------------------------------------------------------------- /components/screen/Home/Data/FontPairs.ts: -------------------------------------------------------------------------------- 1 | // leading-none line-height: 1; 2 | // leading-tight line-height: 1.25; 3 | // leading-snug line-height: 1.375; 4 | // leading-normal line-height: 1.5; 5 | // leading-relaxed line-height: 1.625; 6 | // leading-loose line-height: 2; 7 | 8 | export const fontPairs = { 9 | 'Open Sans + PT Sans': { 10 | title: { 11 | name: 'Open Sans', 12 | size: '45px', 13 | weight: 700, 14 | lineHeight: 1, 15 | fontStyle: 'normal', 16 | textTransform: 'none', 17 | }, 18 | description: { 19 | name: 'PT Sans', 20 | size: '18px', 21 | weight: 400, 22 | lineHeight: 1.625, 23 | fontStyle: 'normal', 24 | textTransform: 'none', 25 | }, 26 | }, 27 | 'Quicksand + Source Sans Pro': { 28 | title: { 29 | name: 'Quicksand', 30 | size: '45px', 31 | weight: 700, 32 | lineHeight: 1, 33 | fontStyle: 'normal', 34 | textTransform: 'none', 35 | }, 36 | description: { 37 | name: 'Source Sans Pro', 38 | size: '18px', 39 | weight: 400, 40 | lineHeight: 1.625, 41 | fontStyle: 'normal', 42 | textTransform: 'none', 43 | }, 44 | }, 45 | 'Yellowtail + Lato': { 46 | title: { 47 | name: 'Yellowtail', 48 | size: '45px', 49 | weight: 700, 50 | lineHeight: 1, 51 | fontStyle: 'normal', 52 | textTransform: 'none', 53 | }, 54 | description: { 55 | name: 'Lato', 56 | size: '18px', 57 | weight: 400, 58 | lineHeight: 1.625, 59 | fontStyle: 'normal', 60 | textTransform: 'none', 61 | }, 62 | }, 63 | 'Yeseva One + Josefin Sans': { 64 | title: { 65 | name: 'Yeseva One', 66 | size: '45px', 67 | weight: 700, 68 | lineHeight: 1, 69 | fontStyle: 'normal', 70 | textTransform: 'none', 71 | }, 72 | description: { 73 | name: 'Josefin Sans', 74 | size: '18px', 75 | weight: 400, 76 | lineHeight: 1.625, 77 | fontStyle: 'normal', 78 | textTransform: 'none', 79 | }, 80 | }, 81 | 'Open Sans Condensed + Lora': { 82 | title: { 83 | name: 'Open Sans Condensed', 84 | size: '45px', 85 | weight: 700, 86 | lineHeight: 1, 87 | fontStyle: 'normal', 88 | textTransform: 'none', 89 | }, 90 | description: { 91 | name: 'Lora', 92 | size: '18px', 93 | weight: 400, 94 | lineHeight: 1.625, 95 | fontStyle: 'normal', 96 | textTransform: 'none', 97 | }, 98 | }, 99 | 'Raleway + Libre Baskerville': { 100 | title: { 101 | name: 'Raleway', 102 | size: '45px', 103 | weight: 700, 104 | lineHeight: 1, 105 | fontStyle: 'normal', 106 | textTransform: 'none', 107 | }, 108 | description: { 109 | name: 'Libre Baskerville', 110 | size: '18px', 111 | weight: 400, 112 | lineHeight: 1.625, 113 | fontStyle: 'normal', 114 | textTransform: 'none', 115 | }, 116 | }, 117 | 'Rubik + Roboto Mono': { 118 | title: { 119 | name: 'Rubik', 120 | size: '45px', 121 | weight: 700, 122 | lineHeight: 1, 123 | fontStyle: 'normal', 124 | textTransform: 'none', 125 | }, 126 | description: { 127 | name: 'Roboto Mono', 128 | size: '18px', 129 | weight: 400, 130 | lineHeight: 1.625, 131 | fontStyle: 'normal', 132 | textTransform: 'none', 133 | }, 134 | }, 135 | 'Playfair Display + Quattrocento Sans': { 136 | title: { 137 | name: 'Playfair Display', 138 | size: '45px', 139 | weight: 700, 140 | lineHeight: 1, 141 | fontStyle: 'normal', 142 | textTransform: 'none', 143 | }, 144 | description: { 145 | name: 'Quattrocento Sans', 146 | size: '18px', 147 | weight: 400, 148 | lineHeight: 1.625, 149 | fontStyle: 'normal', 150 | textTransform: 'none', 151 | }, 152 | }, 153 | 'Merriweather Sans + Merriweather': { 154 | title: { 155 | name: 'Merriweather Sans', 156 | size: '45px', 157 | weight: 700, 158 | lineHeight: 1, 159 | fontStyle: 'normal', 160 | textTransform: 'none', 161 | }, 162 | description: { 163 | name: 'Merriweather', 164 | size: '18px', 165 | weight: 400, 166 | lineHeight: 1.625, 167 | fontStyle: 'normal', 168 | textTransform: 'none', 169 | }, 170 | }, 171 | 'Oswald + Noto Serif': { 172 | title: { 173 | name: 'Oswald', 174 | size: '45px', 175 | weight: 700, 176 | lineHeight: 1, 177 | fontStyle: 'normal', 178 | textTransform: 'none', 179 | }, 180 | description: { 181 | name: 'Noto Serif', 182 | size: '18px', 183 | weight: 400, 184 | lineHeight: 1.625, 185 | fontStyle: 'normal', 186 | textTransform: 'none', 187 | }, 188 | }, 189 | 'Lora + Poppins': { 190 | title: { 191 | name: 'Lora', 192 | size: '45px', 193 | weight: 700, 194 | lineHeight: 1, 195 | fontStyle: 'normal', 196 | textTransform: 'none', 197 | }, 198 | description: { 199 | name: 'Poppins', 200 | size: '18px', 201 | weight: 400, 202 | lineHeight: 1.625, 203 | fontStyle: 'normal', 204 | textTransform: 'none', 205 | }, 206 | }, 207 | 'Lato + Crimson Text': { 208 | title: { 209 | name: 'Lato', 210 | size: '45px', 211 | weight: 700, 212 | lineHeight: 1, 213 | fontStyle: 'normal', 214 | textTransform: 'none', 215 | }, 216 | description: { 217 | name: 'Crimson Text', 218 | size: '18px', 219 | weight: 400, 220 | lineHeight: 1.625, 221 | fontStyle: 'normal', 222 | textTransform: 'none', 223 | }, 224 | }, 225 | 'Great Vibes + Montserrat': { 226 | title: { 227 | name: 'Great Vibes', 228 | size: '45px', 229 | weight: 700, 230 | lineHeight: 1, 231 | fontStyle: 'normal', 232 | textTransform: 'none', 233 | }, 234 | description: { 235 | name: 'Montserrat', 236 | size: '18px', 237 | weight: 400, 238 | lineHeight: 1.625, 239 | fontStyle: 'normal', 240 | textTransform: 'none', 241 | }, 242 | }, 243 | 'Mulish + Space Mono': { 244 | title: { 245 | name: 'Mulish', 246 | size: '45px', 247 | weight: 700, 248 | lineHeight: 1, 249 | fontStyle: 'normal', 250 | textTransform: 'none', 251 | }, 252 | description: { 253 | name: 'Space Mono', 254 | size: '18px', 255 | weight: 400, 256 | lineHeight: 1.625, 257 | fontStyle: 'normal', 258 | textTransform: 'none', 259 | }, 260 | }, 261 | 'Fredoka One + ABeeZee': { 262 | title: { 263 | name: 'Fredoka One', 264 | size: '45px', 265 | weight: 700, 266 | lineHeight: 1, 267 | fontStyle: 'normal', 268 | textTransform: 'none', 269 | }, 270 | description: { 271 | name: 'ABeeZee', 272 | size: '18px', 273 | weight: 400, 274 | lineHeight: 1.625, 275 | fontStyle: 'normal', 276 | textTransform: 'none', 277 | }, 278 | }, 279 | 'Fjalla One + Nunito': { 280 | title: { 281 | name: 'Fjalla One', 282 | size: '45px', 283 | weight: 700, 284 | lineHeight: 1, 285 | fontStyle: 'normal', 286 | textTransform: 'none', 287 | }, 288 | description: { 289 | name: 'Nunito', 290 | size: '18px', 291 | weight: 400, 292 | lineHeight: 1.625, 293 | fontStyle: 'normal', 294 | textTransform: 'none', 295 | }, 296 | }, 297 | 'Teko + Montserrat': { 298 | title: { 299 | name: 'Teko', 300 | size: '45px', 301 | weight: 700, 302 | lineHeight: 1, 303 | fontStyle: 'normal', 304 | textTransform: 'none', 305 | }, 306 | description: { 307 | name: 'Montserrat', 308 | size: '18px', 309 | weight: 400, 310 | lineHeight: 1.625, 311 | fontStyle: 'normal', 312 | textTransform: 'none', 313 | }, 314 | }, 315 | 'Bangers + Gudea': { 316 | title: { 317 | name: 'Bangers', 318 | size: '45px', 319 | weight: 700, 320 | lineHeight: 1, 321 | fontStyle: 'normal', 322 | textTransform: 'none', 323 | }, 324 | description: { 325 | name: 'Gudea', 326 | size: '18px', 327 | weight: 400, 328 | lineHeight: 1.625, 329 | fontStyle: 'normal', 330 | textTransform: 'none', 331 | }, 332 | }, 333 | 'Copse + Mulish': { 334 | title: { 335 | name: 'Copse', 336 | size: '45px', 337 | weight: 700, 338 | lineHeight: 1, 339 | fontStyle: 'normal', 340 | textTransform: 'none', 341 | }, 342 | description: { 343 | name: 'Mulish', 344 | size: '18px', 345 | weight: 400, 346 | lineHeight: 1.625, 347 | fontStyle: 'normal', 348 | textTransform: 'none', 349 | }, 350 | }, 351 | 'Anton + Roboto': { 352 | title: { 353 | name: 'Anton', 354 | size: '45px', 355 | weight: 700, 356 | lineHeight: 1, 357 | fontStyle: 'normal', 358 | textTransform: 'none', 359 | }, 360 | description: { 361 | name: 'Roboto', 362 | size: '18px', 363 | weight: 400, 364 | lineHeight: 1.625, 365 | fontStyle: 'normal', 366 | textTransform: 'none', 367 | }, 368 | }, 369 | }; 370 | -------------------------------------------------------------------------------- /components/screen/Home/Data/Patterns.ts: -------------------------------------------------------------------------------- 1 | export const patterns = { 2 | none: '', 3 | jigsaw: 'jigsaw', 4 | overcast: 'overcast', 5 | formalInvitation: 'formal-invitation', 6 | topography: 'topography', 7 | texture: 'texture', 8 | jupiter: 'jupiter', 9 | architect: 'architect', 10 | cutout: 'cutout', 11 | hideout: 'hideout', 12 | graphPaper: 'graph-paper', 13 | yyy: 'yyy', 14 | squares: 'squares', 15 | fallingTriangles: 'falling-triangles', 16 | pianoMan: 'piano-man', 17 | pieFactory: 'pie-factory', 18 | dominos: 'dominos', 19 | hexagons: 'hexagons', 20 | charlieBrown: 'charlie-brown', 21 | autumn: 'autumn', 22 | temple: 'temple', 23 | stampCollection: 'stamp-collection', 24 | deathStar: 'death-star', 25 | churchOnSunday: 'church-on-sunday', 26 | iLikeFood: 'i-like-food', 27 | overlappingHexagons: 'overlapping-hexagons', 28 | fourPointStars: 'four-point-stars', 29 | bamboo: 'bamboo', 30 | bathroomFloor: 'bathroom-floor', 31 | corkScrew: 'cork-screw', 32 | happyIntersection: 'happy-intersection', 33 | kiwi: 'kiwi', 34 | lips: 'lips', 35 | lisbon: 'lisbon', 36 | randomShapes: 'random-shapes', 37 | steelBeams: 'steel-beams', 38 | tinyCheckers: 'tiny-checkers', 39 | xEquals: 'x-equals', 40 | anchorsAway: 'anchors-away', 41 | bevelCircle: 'bevel-circle', 42 | brickWall: 'brick-wall', 43 | fancyRectangles: 'fancy-rectangles', 44 | heavyRain: 'heavy-rain', 45 | overlappingCircles: 'overlapping-circles', 46 | plus: 'plus', 47 | roundedPlusConnected: 'rounded-plus-connected', 48 | volcanoLamp: 'volcano-lamp', 49 | wiggle: 'wiggle', 50 | bubbles: 'bubbles', 51 | cage: 'cage', 52 | connections: 'connections', 53 | current: 'current', 54 | diagonalStripes: 'diagonal-stripes', 55 | flippedDiamonds: 'flipped-diamonds', 56 | floatingCogs: 'floating-cogs', 57 | glamorous: 'glamorous', 58 | houndstooth: 'houndstooth', 59 | leaf: 'leaf', 60 | linesInMotion: 'lines-in-motion', 61 | moroccan: 'moroccan', 62 | morphingDiamonds: 'morphing-diamonds', 63 | rails: 'rails', 64 | rain: 'rain', 65 | skulls: 'skulls', 66 | squaresInSquares: 'squares-in-squares', 67 | stripes: 'stripes', 68 | ticTacToe: 'tic-tac-toe', 69 | zigZag: 'zig-zag', 70 | aztec: 'aztec', 71 | bankNote: 'bank-note', 72 | boxes: 'boxes', 73 | circlesAndSquares: 'circles-and-squares', 74 | circuitBoard: 'circuit-board', 75 | curtain: 'curtain', 76 | diagonalLines: 'diagonal-lines', 77 | endlessClouds: 'endless-clouds', 78 | eyes: 'eyes', 79 | floorTile: 'floor-tile', 80 | groovy: 'groovy', 81 | intersectingCircles: 'intersecting-circles', 82 | melt: 'melt', 83 | overlappingDiamonds: 'overlapping-diamonds', 84 | parkayFloor: 'parkay-floor', 85 | pixelDots: 'pixel-dots', 86 | polkaDots: 'polka-dots', 87 | signal: 'signal', 88 | slantedStars: 'slanted-stars', 89 | wallpaper: 'wallpaper', 90 | }; 91 | -------------------------------------------------------------------------------- /components/screen/Home/Data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FontPairs'; 2 | export * from './Patterns'; 3 | -------------------------------------------------------------------------------- /components/screen/Home/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Header } from '@/root/components/shared/Header'; 4 | import { InputContextProvider } from '@/root/context'; 5 | 6 | import { EditorCanvas, InputBox } from './Components'; 7 | 8 | export const Home: React.FC = () => { 9 | return ( 10 |
11 |
12 | 13 |
14 |
15 | 16 |
17 | 18 |
19 |
20 |

Graph editor is accessible only in bigger screen above 1440px

21 |
22 |
23 |
24 | ); 25 | }; 26 | 27 | export default Home; 28 | -------------------------------------------------------------------------------- /components/screen/Home/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Home'; 2 | -------------------------------------------------------------------------------- /components/shared/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/img-redundant-alt */ 2 | /* eslint-disable @next/next/no-img-element */ 3 | import Link from 'next/link'; 4 | import React from 'react'; 5 | import { AiFillGithub, AiFillStar } from 'react-icons/ai'; 6 | import { BiDna } from 'react-icons/bi'; 7 | 8 | export const Header: React.FC = () => { 9 | return ( 10 |
11 |
12 |
13 |
14 | 15 | 16 | OpenGrph 17 | 18 |
19 |
20 | 23 | 24 | OpenGrph - Super fast open graph & cover image creator | Product Hunt 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /components/shared/Header/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Header'; 2 | -------------------------------------------------------------------------------- /context/index.ts: -------------------------------------------------------------------------------- 1 | export * from './inputContext'; 2 | -------------------------------------------------------------------------------- /context/inputContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | fontPairs as fontData, 5 | patterns as patternData, 6 | } from '@/root/components/screen/Home/Data'; 7 | 8 | interface PropsTypes { 9 | children: React.ReactNode; 10 | } 11 | 12 | interface ContextValues { 13 | urlFetch: string; 14 | setUrlFetch: React.Dispatch>; 15 | title: string; 16 | setTitle: React.Dispatch>; 17 | description: string; 18 | setDescription: React.Dispatch>; 19 | theme: string; 20 | setTheme: React.Dispatch>; 21 | color: string; 22 | setColor: React.Dispatch>; 23 | pattern: keyof typeof patternData; 24 | setPattern: React.Dispatch>; 25 | format: string; 26 | setFormat: React.Dispatch>; 27 | mode: string; 28 | setMode: React.Dispatch>; 29 | fontPairs: keyof typeof fontData; 30 | setFontPairs: React.Dispatch>; 31 | } 32 | 33 | export const InputContext = React.createContext({ 34 | urlFetch: '', 35 | setUrlFetch: () => undefined, 36 | title: '', 37 | setTitle: () => undefined, 38 | description: '', 39 | setDescription: () => undefined, 40 | theme: 'Minimal', 41 | setTheme: () => undefined, 42 | color: '#0B5351', 43 | setColor: () => undefined, 44 | pattern: 'jupiter', 45 | setPattern: () => undefined, 46 | format: '1200 x 630', 47 | setFormat: () => undefined, 48 | mode: 'Dark', 49 | setMode: () => undefined, 50 | fontPairs: 'Bangers + Gudea', 51 | setFontPairs: () => undefined, 52 | }); 53 | 54 | export const InputContextProvider: React.FC = ({ children }) => { 55 | const [urlFetch, setUrlFetch] = React.useState(''); 56 | const [title, setTitle] = React.useState('Hello buddy, checkout opengrph!'); 57 | const [description, setDescription] = React.useState( 58 | 'Now, You have an ability to create opengraph cover just by click of buttton' 59 | ); 60 | const [theme, setTheme] = React.useState('Minimal'); 61 | const [color, setColor] = React.useState('#0B5351'); 62 | const [mode, setMode] = React.useState('Dark'); 63 | const [pattern, setPattern] = 64 | React.useState('jupiter'); 65 | const [format, setFormat] = React.useState('Png'); 66 | const [fontPairs, setFontPairs] = 67 | React.useState('Bangers + Gudea'); 68 | 69 | const contextValues: ContextValues = { 70 | urlFetch, 71 | setUrlFetch, 72 | title, 73 | setTitle, 74 | description, 75 | setDescription, 76 | theme, 77 | setTheme, 78 | color, 79 | setColor, 80 | pattern, 81 | setPattern, 82 | format, 83 | setFormat, 84 | fontPairs, 85 | setFontPairs, 86 | mode, 87 | setMode, 88 | }; 89 | 90 | return ( 91 | 92 | {children} 93 | 94 | ); 95 | }; 96 | -------------------------------------------------------------------------------- /github/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grassper/OpenGrph/7347c0086ff6c911a18a1c7a63aae7ff9b44f045/github/preview.png -------------------------------------------------------------------------------- /hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useAnalytics'; 2 | -------------------------------------------------------------------------------- /hooks/useAnalytics.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import { useEffect } from 'react'; 3 | 4 | import { pageview } from '@/root/lib/analytics'; 5 | 6 | function handleRouteChange(url: URL) { 7 | if (process.env.NODE_ENV !== 'production') return; 8 | pageview(url); 9 | } 10 | 11 | export const useAnalytics = (): void => { 12 | const router = useRouter(); 13 | 14 | useEffect(() => { 15 | router.events.on('routeChangeComplete', handleRouteChange); 16 | 17 | return function cleanup() { 18 | router.events.off('routeChangeComplete', handleRouteChange); 19 | }; 20 | }, [router.events]); 21 | }; 22 | -------------------------------------------------------------------------------- /lib/analytics/analytics.ts: -------------------------------------------------------------------------------- 1 | interface GTagEvent { 2 | action: string; 3 | category: string; 4 | label: string; 5 | value: number; 6 | } 7 | 8 | export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID as string; 9 | 10 | export const pageview = (url: URL): void => { 11 | window.gtag('config', GA_TRACKING_ID, { page_path: url }); 12 | }; 13 | 14 | export const event = ({ action, category, label, value }: GTagEvent): void => { 15 | window.gtag('event', action, { 16 | event_category: category, 17 | event_label: label, 18 | value: value, 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/analytics/index.ts: -------------------------------------------------------------------------------- 1 | export * from './analytics'; 2 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next-sitemap.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteUrl: 'https://www.opengrph.blockscribers.com', // replace example with your domain 3 | generateRobotsTxt: true, 4 | sitemapSize: 7000, 5 | }; 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | swcMinify: true, 3 | reactStrictMode: true, 4 | webpack: (config) => { 5 | Object.assign(config.resolve.alias, { 6 | react: 'preact/compat', 7 | 'react-dom/test-utils': 'preact/test-utils', 8 | 'react-dom': 'preact/compat', 9 | 'react/jsx-runtime': 'preact/jsx-runtime', 10 | }); 11 | 12 | return config; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-typescript-starter-kit", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "postbuild": "next-sitemap --config ./next-sitemap.config.js", 9 | "start": "next start", 10 | "prepare": "husky install", 11 | "lint": "eslint \"**/*.{js,jsx,ts,tsx}\"", 12 | "lint-fix": "eslint \"**/*.{js,jsx,ts,tsx}\" --fix", 13 | "precommit": "lint-staged && pretty-quick --staged", 14 | "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\" --config ./.prettierrc.js" 15 | }, 16 | "dependencies": { 17 | "@fontsource/abeezee": "^4.5.8", 18 | "@fontsource/anton": "^4.5.7", 19 | "@fontsource/bangers": "^4.5.7", 20 | "@fontsource/copse": "^4.5.8", 21 | "@fontsource/crimson-text": "^4.5.2", 22 | "@fontsource/fjalla-one": "^4.5.7", 23 | "@fontsource/fredoka-one": "^4.5.7", 24 | "@fontsource/great-vibes": "^4.5.8", 25 | "@fontsource/gudea": "^4.5.7", 26 | "@fontsource/josefin-sans": "^4.5.6", 27 | "@fontsource/lato": "^4.5.5", 28 | "@fontsource/libre-baskerville": "^4.5.5", 29 | "@fontsource/lora": "^4.5.6", 30 | "@fontsource/merriweather": "^4.5.8", 31 | "@fontsource/merriweather-sans": "^4.5.5", 32 | "@fontsource/montserrat": "^4.5.7", 33 | "@fontsource/montserrat-alternates": "^4.5.5", 34 | "@fontsource/mulish": "^4.5.7", 35 | "@fontsource/noto-serif": "^4.5.7", 36 | "@fontsource/nunito": "^4.5.8", 37 | "@fontsource/open-sans": "^4.5.8", 38 | "@fontsource/open-sans-condensed": "^4.5.5", 39 | "@fontsource/oswald": "^4.5.7", 40 | "@fontsource/playfair-display": "^4.5.6", 41 | "@fontsource/poppins": "^4.5.5", 42 | "@fontsource/pt-sans": "^4.5.5", 43 | "@fontsource/quattrocento-sans": "^4.5.5", 44 | "@fontsource/quicksand": "^4.5.6", 45 | "@fontsource/raleway": "^4.5.5", 46 | "@fontsource/roboto": "^4.5.5", 47 | "@fontsource/roboto-mono": "^4.5.7", 48 | "@fontsource/rubik": "^4.5.6", 49 | "@fontsource/source-code-pro": "^4.5.6", 50 | "@fontsource/source-sans-pro": "^4.5.6", 51 | "@fontsource/space-mono": "^4.5.5", 52 | "@fontsource/teko": "^4.5.5", 53 | "@fontsource/yellowtail": "^4.5.7", 54 | "@fontsource/yeseva-one": "^4.5.5", 55 | "framer-motion": "^6.3.3", 56 | "next": "12.1.5", 57 | "next-seo": "^5.4.0", 58 | "next-themes": "^0.1.1", 59 | "preact": "^10.7.1", 60 | "react": "18.1.0", 61 | "react-component-export-image": "^1.0.6", 62 | "react-dom": "18.1.0", 63 | "react-icons": "^4.3.1" 64 | }, 65 | "devDependencies": { 66 | "@commitlint/cli": "^16.2.4", 67 | "@commitlint/config-conventional": "^16.2.4", 68 | "@types/gtag.js": "^0.0.10", 69 | "@types/nprogress": "^0.2.0", 70 | "@types/react": "18.0.8", 71 | "@typescript-eslint/eslint-plugin": "^5.21.0", 72 | "@typescript-eslint/parser": "^5.21.0", 73 | "autoprefixer": "^10.4.5", 74 | "eslint": "8.14.0", 75 | "eslint-config-next": "12.1.5", 76 | "eslint-config-prettier": "^8.3.0", 77 | "eslint-plugin-import": "^2.23.4", 78 | "eslint-plugin-jsx-a11y": "^6.5.1", 79 | "eslint-plugin-prettier": "^4.0.0", 80 | "eslint-plugin-react": "^7.29.4", 81 | "eslint-plugin-react-hooks": "^4.5.0", 82 | "eslint-plugin-simple-import-sort": "^7.0.0", 83 | "husky": "^7.0.4", 84 | "lint-staged": "^12.4.1", 85 | "next-sitemap": "^2.5.20", 86 | "postcss": "^8.4.12", 87 | "prettier": "^2.6.2", 88 | "pretty-quick": "^3.1.3", 89 | "tailwindcss": "^3.0.24", 90 | "typescript": "4.6.3" 91 | }, 92 | "lint-staged": { 93 | "**/*.{js,jsx,ts,tsx}": "eslint --fix" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | import '@fontsource/open-sans'; 3 | import '@fontsource/pt-sans'; 4 | import '@fontsource/quicksand'; 5 | import '@fontsource/source-sans-pro'; 6 | import '@fontsource/yellowtail'; 7 | import '@fontsource/lato'; 8 | import '@fontsource/yeseva-one'; 9 | import '@fontsource/josefin-sans'; 10 | import '@fontsource/open-sans-condensed'; 11 | import '@fontsource/lora'; 12 | import '@fontsource/raleway'; 13 | import '@fontsource/libre-baskerville'; 14 | import '@fontsource/rubik'; 15 | import '@fontsource/roboto-mono'; 16 | import '@fontsource/playfair-display'; 17 | import '@fontsource/quattrocento-sans'; 18 | import '@fontsource/merriweather-sans'; 19 | import '@fontsource/merriweather'; 20 | import '@fontsource/oswald'; 21 | import '@fontsource/noto-serif'; 22 | import '@fontsource/poppins'; 23 | import '@fontsource/crimson-text'; 24 | import '@fontsource/great-vibes'; 25 | import '@fontsource/montserrat'; 26 | import '@fontsource/mulish'; 27 | import '@fontsource/space-mono'; 28 | import '@fontsource/fredoka-one'; 29 | import '@fontsource/abeezee'; 30 | import '@fontsource/fjalla-one'; 31 | import '@fontsource/nunito'; 32 | import '@fontsource/teko'; 33 | import '@fontsource/bangers'; 34 | import '@fontsource/gudea'; 35 | import '@fontsource/copse'; 36 | import '@fontsource/anton'; 37 | import '@fontsource/roboto'; 38 | import '@fontsource/montserrat-alternates/400.css'; 39 | import '@fontsource/montserrat-alternates/500.css'; 40 | import '@fontsource/montserrat-alternates/600.css'; 41 | import '@fontsource/montserrat-alternates/700.css'; 42 | import '@fontsource/montserrat-alternates/800.css'; 43 | 44 | import type { AppProps } from 'next/app'; 45 | import { DefaultSeo, NextSeo } from 'next-seo'; 46 | import { ThemeProvider } from 'next-themes'; 47 | 48 | import { useAnalytics } from '@/root/hooks/useAnalytics'; 49 | 50 | const MyApp: React.FC = ({ Component, pageProps }) => { 51 | useAnalytics(); 52 | 53 | return ( 54 | <> 55 | 60 | 173 | 174 | 175 | 176 | 177 | ); 178 | }; 179 | export default MyApp; 180 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { 2 | DocumentContext, 3 | DocumentInitialProps, 4 | Head, 5 | Html, 6 | Main, 7 | NextScript, 8 | } from 'next/document'; 9 | 10 | import { GA_TRACKING_ID } from '@/root/lib/analytics'; 11 | 12 | export default class MyDocument extends Document { 13 | static async getInitialProps( 14 | ctx: DocumentContext 15 | ): Promise { 16 | const initialProps = await Document.getInitialProps(ctx); 17 | return initialProps; 18 | } 19 | 20 | render(): JSX.Element { 21 | return ( 22 | 23 | 24 | {process.env.NODE_ENV === 'production' && ( 25 | <> 26 | {/* Google analytics tracking */} 27 |