├── tina ├── .gitignore ├── config.ts └── tina-lock.json ├── public ├── admin │ └── .gitignore ├── blog-placeholder-1.jpg ├── blog-placeholder-2.jpg ├── blog-placeholder-3.jpg ├── blog-placeholder-4.jpg ├── blog-placeholder-5.jpg ├── fonts │ ├── atkinson-bold.woff │ └── atkinson-regular.woff ├── blog-placeholder-about.jpg └── favicon.svg ├── src ├── env.d.ts ├── consts.ts ├── components │ ├── FormattedDate.astro │ ├── HeaderLink.astro │ ├── BaseHead.astro │ ├── Footer.astro │ └── Header.astro ├── pages │ ├── rss.xml.js │ ├── blog │ │ ├── [...slug].astro │ │ └── index.astro │ ├── index.astro │ └── about.astro ├── layouts │ └── BlogPost.astro └── styles │ └── global.css ├── vercel.json ├── .vscode ├── extensions.json └── launch.json ├── tsconfig.json ├── .env.example ├── astro.config.mjs ├── .gitignore ├── api └── cloudinary │ └── [...media].js ├── package.json ├── LICENSE ├── content └── blogs │ ├── using-mdx.mdx │ ├── first-post.mdx │ ├── second-post.mdx │ ├── third-post.mdx │ └── markdown-style-guide.mdx └── README.md /tina/.gitignore: -------------------------------------------------------------------------------- 1 | __generated__ -------------------------------------------------------------------------------- /public/admin/.gitignore: -------------------------------------------------------------------------------- 1 | index.html 2 | assets/ -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /public/blog-placeholder-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajpatil53/astro-tina-cloudinary-starter/HEAD/public/blog-placeholder-1.jpg -------------------------------------------------------------------------------- /public/blog-placeholder-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajpatil53/astro-tina-cloudinary-starter/HEAD/public/blog-placeholder-2.jpg -------------------------------------------------------------------------------- /public/blog-placeholder-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajpatil53/astro-tina-cloudinary-starter/HEAD/public/blog-placeholder-3.jpg -------------------------------------------------------------------------------- /public/blog-placeholder-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajpatil53/astro-tina-cloudinary-starter/HEAD/public/blog-placeholder-4.jpg -------------------------------------------------------------------------------- /public/blog-placeholder-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajpatil53/astro-tina-cloudinary-starter/HEAD/public/blog-placeholder-5.jpg -------------------------------------------------------------------------------- /public/fonts/atkinson-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajpatil53/astro-tina-cloudinary-starter/HEAD/public/fonts/atkinson-bold.woff -------------------------------------------------------------------------------- /public/blog-placeholder-about.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajpatil53/astro-tina-cloudinary-starter/HEAD/public/blog-placeholder-about.jpg -------------------------------------------------------------------------------- /public/fonts/atkinson-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajpatil53/astro-tina-cloudinary-starter/HEAD/public/fonts/atkinson-regular.woff -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "api/cloudinary/*.js": { 4 | "memory": 256, 5 | "maxDuration": 10 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode", "unifiedjs.vscode-mdx"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "strictNullChecks": true, 5 | "jsx": "react-jsx", 6 | "jsxImportSource": "react" 7 | } 8 | } -------------------------------------------------------------------------------- /src/consts.ts: -------------------------------------------------------------------------------- 1 | // Place any global data in this file. 2 | // You can import this data from anywhere in your site by using the `import` keyword. 3 | 4 | export const SITE_TITLE = 'Astro Blog'; 5 | export const SITE_DESCRIPTION = 'Welcome to my website!'; 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GITHUB_BRANCH= 2 | TINA_TOKEN= 3 | TINA_SEARCH_TOKEN= 4 | PUBLIC_TINA_CLIENT_ID= 5 | CLOUDINARY_CLOUD_NAME= 6 | CLOUDINARY_API_KEY= 7 | CLOUDINARY_API_SECRET= 8 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import mdx from '@astrojs/mdx'; 3 | import sitemap from '@astrojs/sitemap'; 4 | import react from "@astrojs/react"; 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | site: 'https://example.com', 9 | integrations: [mdx(), sitemap(), react()] 10 | }); -------------------------------------------------------------------------------- /src/components/FormattedDate.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | date: string; 4 | } 5 | 6 | const { date } = Astro.props; 7 | --- 8 | 9 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # jetbrains setting folder 24 | .idea/ 25 | 26 | node_modules -------------------------------------------------------------------------------- /src/pages/rss.xml.js: -------------------------------------------------------------------------------- 1 | import rss from '@astrojs/rss'; 2 | import { getCollection } from 'astro:content'; 3 | import { SITE_TITLE, SITE_DESCRIPTION } from '../consts'; 4 | 5 | export async function GET(context) { 6 | const posts = await getCollection('blog'); 7 | return rss({ 8 | title: SITE_TITLE, 9 | description: SITE_DESCRIPTION, 10 | site: context.site, 11 | items: posts.map((post) => ({ 12 | ...post.data, 13 | link: `/blog/${post.slug}/`, 14 | })), 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/HeaderLink.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from 'astro/types'; 3 | 4 | type Props = HTMLAttributes<'a'>; 5 | 6 | const { href, class: className, ...props } = Astro.props; 7 | 8 | const { pathname } = Astro.url; 9 | const subpath = pathname.match(/[^\/]+/g); 10 | const isActive = href === pathname || href === '/' + subpath?.[0]; 11 | --- 12 | 13 | 14 | 15 | 16 | 26 | -------------------------------------------------------------------------------- /src/pages/blog/[...slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BlogPost from '../../layouts/BlogPost.astro'; 3 | import client from '../../../tina/__generated__/client'; 4 | import type { BlogPartsFragment } from '../../../tina/__generated__/types'; 5 | import { TinaMarkdown } from 'tinacms/dist/rich-text'; 6 | 7 | export async function getStaticPaths() { 8 | const {data: {blogConnection:{edges:blogEdges}}} = await client.queries.blogConnection(); 9 | return blogEdges?.map((blogEdge) => ({ 10 | params: { slug: blogEdge!.node!.slug }, 11 | props: blogEdge!.node, 12 | })); 13 | } 14 | 15 | type Props = BlogPartsFragment; 16 | 17 | const blog = Astro.props; 18 | --- 19 | 20 | 21 | ; 22 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /api/cloudinary/[...media].js: -------------------------------------------------------------------------------- 1 | import { 2 | mediaHandlerConfig, 3 | createMediaHandler, 4 | } from "next-tinacms-cloudinary/dist/handlers.js"; 5 | 6 | import pkg from "@tinacms/auth"; 7 | const { isAuthorized } = pkg; 8 | 9 | export const config = mediaHandlerConfig; 10 | 11 | export default createMediaHandler({ 12 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME, 13 | api_key: process.env.CLOUDINARY_API_KEY, 14 | api_secret: process.env.CLOUDINARY_API_SECRET, 15 | authorized: async (req, _res) => { 16 | try { 17 | if (process.env.NODE_ENV == "development") { 18 | return true; 19 | } 20 | 21 | const user = await isAuthorized(req); 22 | 23 | return user?.verified || false; 24 | } catch (e) { 25 | console.error(e); 26 | return false; 27 | } 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-tina-cloudinary-starter", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "tinacms dev -c \"astro dev\"", 7 | "start": "astro dev", 8 | "build": "tinacms build && astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/check": "^0.5.10", 14 | "@astrojs/mdx": "^2.3.0", 15 | "@astrojs/react": "^3.3.0", 16 | "@astrojs/rss": "^4.0.5", 17 | "@astrojs/sitemap": "^3.1.2", 18 | "@tinacms/auth": "^1.0.4", 19 | "@types/react": "^18.2.78", 20 | "@types/react-dom": "^18.2.25", 21 | "astro": "^4.6.1", 22 | "next-tinacms-cloudinary": "^5.0.1", 23 | "react": "^18.2.0", 24 | "react-dom": "^18.2.0", 25 | "tinacms": "^1.6.1", 26 | "typescript": "^5.4.5" 27 | }, 28 | "devDependencies": { 29 | "@tinacms/cli": "^1.5.43", 30 | "@types/node": "^20.12.7" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Raj Patil 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 | -------------------------------------------------------------------------------- /content/blogs/using-mdx.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: using-mdx 3 | title: Using MDX 4 | description: Lorem ipsum dolor sit amet 5 | pubDate: 2022-07-01T18:30:00.000Z 6 | heroImage: /blog-placeholder-5.jpg 7 | --- 8 | 9 | This theme comes with the [@astrojs/mdx](https://docs.astro.build/en/guides/integrations-guide/mdx/) integration installed and configured in your `astro.config.mjs` config file. If you prefer not to use MDX, you can disable support by removing the integration from your config file. 10 | 11 | ## Why MDX? 12 | 13 | MDX is a special flavor of Markdown that supports embedded JavaScript & JSX syntax. This unlocks the ability to [mix JavaScript and UI Components into your Markdown content](https://docs.astro.build/en/guides/markdown-content/#mdx-features) for things like interactive charts or alerts. 14 | 15 | If you have existing content authored in MDX, this integration will hopefully make migrating to Astro a breeze. 16 | 17 | ## Example 18 | 19 | Here is how you import and use a UI component inside of MDX.\ 20 | When you open this page in the browser, you should see the clickable button below. 21 | 22 | 23 | Embedded component in MDX 24 | 25 | 26 | ## More Links 27 | 28 | * [MDX Syntax Documentation](https://mdxjs.com/docs/what-is-mdx) 29 | * [Astro Usage Documentation](https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages) 30 | * **Note:** [Client Directives](https://docs.astro.build/en/reference/directives-reference/#client-directives) are still required to create interactive components. Otherwise, all components in your MDX will render as static HTML (no JavaScript) by default. 31 | -------------------------------------------------------------------------------- /src/components/BaseHead.astro: -------------------------------------------------------------------------------- 1 | --- 2 | // Import the global.css file here so that it is included on 3 | // all pages through the use of the component. 4 | import '../styles/global.css'; 5 | 6 | interface Props { 7 | title: string; 8 | description: string; 9 | image?: string; 10 | } 11 | 12 | const canonicalURL = new URL(Astro.url.pathname, Astro.site); 13 | 14 | const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props; 15 | --- 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {title} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/layouts/BlogPost.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import BaseHead from '../components/BaseHead.astro'; 3 | import Header from '../components/Header.astro'; 4 | import Footer from '../components/Footer.astro'; 5 | import FormattedDate from '../components/FormattedDate.astro'; 6 | import type { BlogPartsFragment } from '../../tina/__generated__/types'; 7 | 8 | type Props = BlogPartsFragment; 9 | 10 | const { title, description, pubDate, updatedDate, heroImage } = Astro.props; 11 | --- 12 | 13 | 14 | 15 | 16 | 55 | 56 | 57 | 58 |
59 |
60 |
61 |
62 | {heroImage && } 63 |
64 |
65 |
66 |
67 | 68 | { 69 | updatedDate && ( 70 |
71 | Last updated on 72 |
73 | ) 74 | } 75 |
76 |

{title}

77 |
78 |
79 | 80 |
81 |
82 |
83 |