upstream #1
							
								
								
									
										15
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								.env.example
									
									
									
									
									
								
							| @@ -3,4 +3,17 @@ NEXT_PUBLIC_GISCUS_REPOSITORY_ID= | ||||
| NEXT_PUBLIC_GISCUS_CATEGORY= | ||||
| NEXT_PUBLIC_GISCUS_CATEGORY_ID= | ||||
| NEXT_PUBLIC_UTTERANCES_REPO= | ||||
| NEXT_PUBLIC_DISQUS_SHORTNAME= | ||||
| NEXT_PUBLIC_DISQUS_SHORTNAME= | ||||
|  | ||||
|  | ||||
| MAILCHIMP_API_KEY= | ||||
| MAILCHIMP_API_SERVER= | ||||
| MAILCHIMP_AUDIENCE_ID= | ||||
|  | ||||
| BUTTONDOWN_API_URL=https://api.buttondown.email/v1/ | ||||
| BUTTONDOWN_API_KEY= | ||||
|  | ||||
| CONVERTKIT_API_URL=https://api.convertkit.com/v3/ | ||||
| CONVERTKIT_API_KEY= | ||||
| // curl https://api.convertkit.com/v3/forms?api_key=<your_public_api_key> to get your form ID  | ||||
| CONVERTKIT_FORM_ID=  | ||||
| @@ -53,6 +53,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea | ||||
| - Blog templates | ||||
| - TOC component | ||||
| - Support for nested routing of blog posts | ||||
| - Newsletter component with support for mailchimp, buttondown and convertkit | ||||
| - Supports [giscus](https://github.com/laymonage/giscus), [utterances](https://github.com/utterance/utterances) or disqus | ||||
| - Projects page | ||||
| - SEO friendly with RSS feed, sitemaps and more! | ||||
| @@ -168,6 +169,8 @@ The easiest way to deploy the template is to use the [Vercel Platform](https://v | ||||
| **Netlify / Github Pages / Firebase etc.**   | ||||
| As the template uses `next/image` for image optimization, additional configurations has to be made to deploy on other popular static hosting websites like [Netlify](https://www.netlify.com/) or [Github Pages](https://pages.github.com/). An alternative image optimization provider such as Imgix, Cloudinary or Akamai has to be used. Alternatively, replace the `next/image` component with a standard `<img>` tag. See [`next/image` documentation](https://nextjs.org/docs/basic-features/image-optimization) for more details. | ||||
|  | ||||
| The API routes used in the newsletter component cannot be used in a static site export. You will need to use a form API endpoint provider and substitute the route in the newsletter component accordingly. Other hosting platforms such as Netlify also offer alternative solutions - please refer to their docs for more information. | ||||
|  | ||||
| ## Support | ||||
|  | ||||
| Using the template? Support this effort by giving a star on Github, sharing your own blog and giving a shoutout on Twitter or be a project [sponsor](https://github.com/sponsors/timlrx). | ||||
|   | ||||
							
								
								
									
										72
									
								
								components/FormSubscribe.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								components/FormSubscribe.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| import { useRef, useState } from 'react' | ||||
|  | ||||
| import siteMetadata from '@/data/siteMetadata' | ||||
|  | ||||
| const FormSubscribe = () => { | ||||
|   const inputEl = useRef(null) | ||||
|   const [message, setMessage] = useState('') | ||||
|   const [subscribed, setSubscribed] = useState(false) | ||||
|  | ||||
|   const subscribe = async (e) => { | ||||
|     e.preventDefault() | ||||
|  | ||||
|     const res = await fetch(`/api/${siteMetadata.newsletter.provider}`, { | ||||
|       body: JSON.stringify({ | ||||
|         email: inputEl.current.value, | ||||
|       }), | ||||
|       headers: { | ||||
|         'Content-Type': 'application/json', | ||||
|       }, | ||||
|       method: 'POST', | ||||
|     }) | ||||
|  | ||||
|     const { error } = await res.json() | ||||
|     if (error) { | ||||
|       setMessage('Your e-mail adress is invalid or you are already subscribed!') | ||||
|       return | ||||
|     } | ||||
|  | ||||
|     inputEl.current.value = '' | ||||
|     setSubscribed(true) | ||||
|     setMessage('Successfully! 🎉 You are now subscribed.') | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div> | ||||
|       <div className="pb-1 text-lg font-semibold text-gray-800 dark:text-gray-100"> | ||||
|         Subscribe to the newsletter | ||||
|       </div> | ||||
|       <form className="flex flex-col sm:flex-row" onSubmit={subscribe}> | ||||
|         <div> | ||||
|           <label className="sr-only" htmlFor="email-input"> | ||||
|             Email address | ||||
|           </label> | ||||
|           <input | ||||
|             autoComplete="email" | ||||
|             className="px-4 py-2 placeholder-gray-500 bg-white border rounded-md appearance-none w-72 border-neutrals-cool-grey-300 text-neutrals-cool-grey-900 dark:bg-black focus:outline-none focus:ring-primary-400 dark:focus:border-primary-600" | ||||
|             id="email-input" | ||||
|             name="email" | ||||
|             placeholder={subscribed ? "You're subscribed !  🎉" : 'Enter your email'} | ||||
|             ref={inputEl} | ||||
|             required | ||||
|             type="email" | ||||
|             disabled={subscribed} | ||||
|           /> | ||||
|         </div> | ||||
|         <div className="flex w-full mt-2 rounded-md shadow-sm sm:mt-0 sm:ml-3"> | ||||
|           <button | ||||
|             className={`w-full bg-primary-500 dark:bg-primary-500 px-4 py-2 border border-transparent rounded-md font-medium text-white ${ | ||||
|               subscribed ? 'cursor-default' : 'hover:bg-primary-700 dark:hover:bg-primary-400' | ||||
|             } focus:outline-none focus:ring-2 focus:ring-offset-2 focus:primary-700`} | ||||
|             type="submit" | ||||
|             disabled={subscribed} | ||||
|           > | ||||
|             {subscribed ? 'Thank you!' : 'Sign up'} | ||||
|           </button> | ||||
|         </div> | ||||
|       </form> | ||||
|     </div> | ||||
|   ) | ||||
| } | ||||
|  | ||||
| export { FormSubscribe } | ||||
| @@ -9,3 +9,9 @@ | ||||
| html { | ||||
|   scroll-behavior: smooth; | ||||
| } | ||||
|  | ||||
| /* https://stackoverflow.com/questions/61083813/how-to-avoid-internal-autofill-selected-style-to-be-applied */ | ||||
| input:-webkit-autofill, | ||||
| input:-webkit-autofill:focus { | ||||
|   transition: background-color 600000s 0s, color 600000s 0s; | ||||
| } | ||||
|   | ||||
| @@ -58,6 +58,7 @@ I wanted it to be nearly as feature-rich as popular blogging templates like [bea | ||||
| - Blog templates | ||||
| - TOC component | ||||
| - Support for nested routing of blog posts | ||||
| - Newsletter component with support for mailchimp, buttondown and convertkit | ||||
| - Supports [giscus](https://github.com/laymonage/giscus), [utterances](https://github.com/utterance/utterances) or disqus | ||||
| - Projects page | ||||
| - SEO friendly with RSS feed, sitemaps and more! | ||||
| @@ -173,6 +174,8 @@ The easiest way to deploy the template is to use the [Vercel Platform](https://v | ||||
| **Netlify / Github Pages / Firebase etc.**   | ||||
| As the template uses `next/image` for image optimization, additional configurations has to be made to deploy on other popular static hosting websites like [Netlify](https://www.netlify.com/) or [Github Pages](https://pages.github.com/). An alternative image optimization provider such as Imgix, Cloudinary or Akamai has to be used. Alternatively, replace the `next/image` component with a standard `<img>` tag. See [`next/image` documentation](https://nextjs.org/docs/basic-features/image-optimization) for more details. | ||||
|  | ||||
| The API routes used in the newsletter component cannot be used in a static site export. You will need to use a form API endpoint provider and substitute the route in the newsletter component accordingly. Other hosting platforms such as Netlify also offer alternative solutions - please refer to their docs for more information. | ||||
|  | ||||
| ## Support | ||||
|  | ||||
| Using the template? Support this effort by giving a star on Github, sharing your own blog and giving a shoutout on Twitter or be a project [sponsor](https://github.com/sponsors/timlrx). | ||||
|   | ||||
| @@ -22,6 +22,11 @@ const siteMetadata = { | ||||
|     simpleAnalytics: false, // true or false | ||||
|     googleAnalyticsId: '', // e.g. UA-000000-2 or G-XXXXXXX | ||||
|   }, | ||||
|   newsletter: { | ||||
|     // supports mailchimp, buttondown, convertkit | ||||
|     // Please add your .env file and modify it according to your selection | ||||
|     provider: 'buttondown', | ||||
|   }, | ||||
|   comment: { | ||||
|     // Select a provider and use the environment variables associated to it | ||||
|     // https://vercel.com/docs/environment-variables | ||||
| @@ -59,7 +64,7 @@ const siteMetadata = { | ||||
|       // theme when dark mode | ||||
|       darkTheme: '', | ||||
|     }, | ||||
|     disqus: { | ||||
|     disqusConfig: { | ||||
|       // https://help.disqus.com/en/articles/1717111-what-s-a-shortname | ||||
|       shortname: process.env.NEXT_PUBLIC_DISQUS_SHORTNAME, | ||||
|     }, | ||||
|   | ||||
| @@ -12,6 +12,7 @@ | ||||
|     "prepare": "husky install" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@mailchimp/mailchimp_marketing": "^3.0.58", | ||||
|     "@tailwindcss/forms": "^0.3.2", | ||||
|     "@tailwindcss/typography": "^0.4.0", | ||||
|     "autoprefixer": "^10.2.5", | ||||
|   | ||||
							
								
								
									
										31
									
								
								pages/api/buttondown.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								pages/api/buttondown.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // eslint-disable-next-line import/no-anonymous-default-export | ||||
| export default async (req, res) => { | ||||
|   const { email } = req.body | ||||
|   if (!email) { | ||||
|     return res.status(400).json({ error: 'Email is required' }) | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     const API_KEY = process.env.BUTTONDOWN_API_KEY | ||||
|     const buttondownRoute = `${process.env.BUTTONDOWN_API_URL}subscribers` | ||||
|     console.log('route : ', buttondownRoute) | ||||
|     const response = await fetch(buttondownRoute, { | ||||
|       body: JSON.stringify({ | ||||
|         email, | ||||
|       }), | ||||
|       headers: { | ||||
|         Authorization: `Token ${API_KEY}`, | ||||
|         'Content-Type': 'application/json', | ||||
|       }, | ||||
|       method: 'POST', | ||||
|     }) | ||||
|  | ||||
|     if (response.status >= 400) { | ||||
|       return res.status(500).json({ error: `There was an error subscribing to the list.` }) | ||||
|     } | ||||
|  | ||||
|     return res.status(201).json({ error: '' }) | ||||
|   } catch (error) { | ||||
|     return res.status(500).json({ error: error.message || error.toString() }) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										36
									
								
								pages/api/convertkit.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								pages/api/convertkit.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| /* eslint-disable import/no-anonymous-default-export */ | ||||
| export default async (req, res) => { | ||||
|   const { email } = req.body | ||||
|   console.log('email : ', email) | ||||
|  | ||||
|   if (!email) { | ||||
|     return res.status(400).json({ error: 'Email is required' }) | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     const FORM_ID = process.env.CONVERTKIT_FORM_ID | ||||
|     const API_KEY = process.env.CONVERTKIT_API_KEY | ||||
|     const API_URL = process.env.CONVERTKIT_API_URL | ||||
|  | ||||
|     // Send request to ConvertKit | ||||
|     const data = { email, api_key: API_KEY } | ||||
|  | ||||
|     const response = await fetch(`${API_URL}forms/${FORM_ID}/subscribe`, { | ||||
|       body: JSON.stringify(data), | ||||
|       headers: { | ||||
|         'Content-Type': 'application/json', | ||||
|       }, | ||||
|       method: 'POST', | ||||
|     }) | ||||
|  | ||||
|     if (response.status >= 400) { | ||||
|       return res.status(400).json({ | ||||
|         error: `There was an error subscribing to the list.`, | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     return res.status(201).json({ error: '' }) | ||||
|   } catch (error) { | ||||
|     return res.status(500).json({ error: error.message || error.toString() }) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										25
									
								
								pages/api/mailchimp.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								pages/api/mailchimp.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import mailchimp from '@mailchimp/mailchimp_marketing' | ||||
|  | ||||
| mailchimp.setConfig({ | ||||
|   apiKey: process.env.MAILCHIMP_API_KEY, | ||||
|   server: process.env.MAILCHIMP_API_SERVER, // E.g. us1 | ||||
| }) | ||||
|  | ||||
| // eslint-disable-next-line import/no-anonymous-default-export | ||||
| export default async (req, res) => { | ||||
|   const { email } = req.body | ||||
|  | ||||
|   if (!email) { | ||||
|     return res.status(400).json({ error: 'Email is required' }) | ||||
|   } | ||||
|  | ||||
|   try { | ||||
|     const test = await mailchimp.lists.addListMember(process.env.MAILCHIMP_AUDIENCE_ID, { | ||||
|       email_address: email, | ||||
|       status: 'subscribed', | ||||
|     }) | ||||
|     return res.status(201).json({ error: '' }) | ||||
|   } catch (error) { | ||||
|     return res.status(500).json({ error: error.message || error.toString() }) | ||||
|   } | ||||
| } | ||||
| @@ -5,6 +5,8 @@ import siteMetadata from '@/data/siteMetadata' | ||||
| import { getAllFilesFrontMatter } from '@/lib/mdx' | ||||
| import formatDate from '@/lib/utils/formatDate' | ||||
|  | ||||
| import { FormSubscribe } from '@/components/FormSubscribe' | ||||
|  | ||||
| const MAX_DISPLAY = 5 | ||||
|  | ||||
| export async function getStaticProps() { | ||||
| @@ -89,6 +91,11 @@ export default function Home({ posts }) { | ||||
|           </Link> | ||||
|         </div> | ||||
|       )} | ||||
|       {siteMetadata.newsletter.provider !== '' && ( | ||||
|         <div className="flex items-center justify-center pt-4"> | ||||
|           <FormSubscribe /> | ||||
|         </div> | ||||
|       )} | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user