-
32 |
- Published on 33 |
- 34 | 35 | 36 |
{a.name}
51 |52 | {a.description} 53 |
54 |83 | 84 | {project.title} 85 | 86 |
87 |93 | {tag} 94 |
95 | ))} 96 |├── .github └── ISSUE_TEMPLATE │ └── add-my-project.md ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── app ├── analytics │ ├── chart.tsx │ └── page.tsx ├── components │ ├── banner.tsx │ ├── mdx.tsx │ ├── nav.tsx │ └── time.tsx ├── head.tsx ├── layout.tsx ├── page.tsx └── projects │ └── [slug] │ ├── head.tsx │ └── page.tsx ├── content ├── authors │ ├── chronark.mdx │ ├── domeccleston.mdx │ ├── hassan.mdx │ ├── hqasmei.mdx │ ├── shadcn.mdx │ └── steventey.mdx ├── projects │ ├── dub.mdx │ ├── envshare.mdx │ ├── extrapolate.mdx │ ├── precedent.mdx │ ├── restorephotos.mdx │ ├── sharegpt.mdx │ ├── starlog.mdx │ ├── thankfulthoughts.mdx │ ├── twitterbio.mdx │ └── ui-shadcn.mdx └── tech │ ├── upstash.mdx │ └── vercel.mdx ├── contentlayer.config.js ├── image └── starlog.png ├── lib ├── formatDate.ts └── getAllArticles.ts ├── middleware.ts ├── next.config.mjs ├── package.json ├── pages └── api │ └── v1 │ └── og.tsx ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── favicon.ico ├── fonts │ ├── PangeaAfrikanTrial-Bold.woff2 │ ├── PangeaAfrikanTrial-Light.woff2 │ ├── PangeaAfrikanTrial-Medium.woff2 │ ├── PangeaAfrikanTrial-Regular.woff2 │ ├── PangeaAfrikanTrial-SemiBold.woff │ └── PangeaAfrikanTrial-SemiBold.woff2 ├── next.svg ├── thirteen.svg └── vercel.svg ├── rome.json ├── styles └── mdx.css ├── tailwind.config.js └── tsconfig.json /.github/ISSUE_TEMPLATE/add-my-project.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Add my project 3 | about: Suggest an open source template or project to be added 4 | title: '' 5 | labels: new project 6 | assignees: chronark 7 | 8 | --- 9 | 10 | **What is your project about** 11 | 12 | **GitHub Link** 13 | 14 | **Public URL** 15 | 16 | **Your twitter handle** 17 | 18 | 19 | Please make sure your repository has a readme with instructions how to run and deploy your project. 20 | -------------------------------------------------------------------------------- /.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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | .contentlayer/ 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/.pnpm/typescript@4.9.4/node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Andreas Thomas 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
65 | Powered by{" "} 66 | 67 | Upstash Redis 68 | {" "} 69 | and{" "} 70 | 71 | Tremor 72 | {" "} 73 |
74 |*]:text-slate-600", className)} 43 | {...props} 44 | /> 45 | ), 46 | img: ({ className, alt, ...props }: React.ImgHTMLAttributes) => ( 47 | // eslint-disable-next-line @next/next/no-img-element 48 | 49 | ), 50 | hr: ({ ...props }) =>
, 51 | table: ({ className, ...props }: React.HTMLAttributes) => ( 52 | 53 |55 | ), 56 | tr: ({ className, ...props }: React.HTMLAttributes54 |
) => ( 57 | 58 | ), 59 | th: ({ className, ...props }) => ( 60 | 67 | ), 68 | td: ({ className, ...props }) => ( 69 | 76 | ), 77 | pre: ({ className, ...props }) => ( 78 | 79 | ), 80 | code: ({ className, ...props }) => ( 81 | 88 | ), 89 | Image, 90 | }; 91 | 92 | interface MdxProps { 93 | code: string; 94 | } 95 | 96 | export function Mdx({ code }: MdxProps) { 97 | const Component = useMDXComponent(code); 98 | 99 | return ( 100 |
101 |103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /app/components/nav.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { ArrowLeftIcon } from "@heroicons/react/24/outline"; 3 | import Link from "next/link"; 4 | import React, { useEffect, useState } from "react"; 5 | import { usePathname } from "next/navigation"; 6 | 7 | export const Navigation: React.FC = () => { 8 | const pathname = usePathname(); 9 | const [isScrolled, setIsScrolled] = useState(false); 10 | 11 | useEffect(() => { 12 | function onScroll() { 13 | setIsScrolled(window.scrollY > 0); 14 | } 15 | onScroll(); 16 | window.addEventListener("scroll", onScroll, { passive: true }); 17 | return () => { 18 | window.removeEventListener("scroll", onScroll); 19 | }; 20 | }, []); 21 | return ( 22 |102 | 27 |49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /app/components/time.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | 5 | type Props = { 6 | time: Date | string | number; 7 | format?: "date" | "time"; 8 | }; 9 | export const Time: React.FC28 |48 |29 | 33 | Submit A Project 34 | 35 | 36 | Analytics 37 | 38 | 39 | GitHub 40 | 41 |42 | {pathname !== "/" ? ( 43 | 44 |45 | 46 | ) : null} 47 | = ({ time, format }) => { 10 | const date = typeof time === "string" || typeof time === "number" ? new Date(time) : time; 11 | 12 | let s = date.toLocaleString(); 13 | if (format === "date") { 14 | s = date.toLocaleDateString(); 15 | } 16 | if (format === "time") { 17 | s = date.toLocaleTimeString(); 18 | } 19 | return ; 20 | }; 21 | -------------------------------------------------------------------------------- /app/head.tsx: -------------------------------------------------------------------------------- 1 | export default function Head() { 2 | const title = "starlog.dev"; 3 | const subtitle = 4 | "A showcase of awesome open source projects and templates to help you build your next serverless app."; 5 | 6 | const baseUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000"; 7 | 8 | const url = new URL("/api/v1/og", baseUrl); 9 | url.searchParams.set("title", title); 10 | url.searchParams.set("subtitle", subtitle); 11 | 12 | return ( 13 | <> 14 | 72 | 73 |starlog.dev 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | > 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "tailwindcss/tailwind.css"; 2 | import { Inter } from "@next/font/google"; 3 | import font from "@next/font/local"; 4 | import Link from "next/link"; 5 | import "@tremor/react/dist/esm/tremor.css"; 6 | 7 | const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }); 8 | const pangea = font({ 9 | src: [ 10 | { 11 | path: "../public/fonts/PangeaAfrikanTrial-Light.woff2", 12 | weight: "100", 13 | style: "normal", 14 | }, 15 | { 16 | path: "../public/fonts/PangeaAfrikanTrial-Regular.woff2", 17 | weight: "300", 18 | style: "normal", 19 | }, 20 | { 21 | path: "../public/fonts/PangeaAfrikanTrial-Medium.woff2", 22 | weight: "500", 23 | style: "normal", 24 | }, 25 | { 26 | path: "../public/fonts/PangeaAfrikanTrial-SemiBold.woff2", 27 | weight: "700", 28 | style: "normal", 29 | }, 30 | { 31 | path: "../public/fonts/PangeaAfrikanTrial-Bold.woff2", 32 | weight: "900", 33 | style: "normal", 34 | }, 35 | ], 36 | variable: "--font-pangea", 37 | }); 38 | 39 | const footerNav = [ 40 | { 41 | name: "Twitter", 42 | href: "https://twitter.com/chronark_", 43 | icon: ( 44 | 47 | ), 48 | }, 49 | { 50 | name: "GitHub", 51 | href: "https://github.com/chronark/starlog", 52 | icon: ( 53 | 60 | ), 61 | }, 62 | ]; 63 | 64 | export default function RootLayout({ 65 | children, 66 | }: { 67 | children: React.ReactNode; 68 | }) { 69 | return ( 70 | 71 |{children} 74 | 90 | 91 | 92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { allProjects, allAuthors, Project } from "contentlayer/generated"; 3 | import { Time } from "./components/time"; 4 | import mql from "@microlink/mql"; 5 | import { Navigation } from "./components/nav"; 6 | 7 | export const revalidate = 360; 8 | 9 | function asyncComponent(fn: (arg: T) => Promise ): (arg: T) => R { 10 | return fn as (arg: T) => R; 11 | } 12 | 13 | const Project = asyncComponent(async ({ project }: { project: Project }) => { 14 | const image = await mql(project.url, { 15 | screenshot: true, 16 | waitForTimeout: 2000, 17 | apiKey: process.env.MICROLINK_TOKEN, 18 | }).catch(() => null); 19 | 20 | const stars = await fetch(`https://api.github.com/repos/${project.repository}`, { 21 | headers: { 22 | Authorization: `Bearer: ${process.env.GITHUB_TOKEN}`, 23 | }, 24 | }) 25 | .then((res) => res.json()) 26 | .then((json) => json.stargazers_count as number); 27 | 28 | return ( 29 | 30 | 112 | ); 113 | }); 114 | 115 | export default async function BlogPage() { 116 | const projects = allProjects.sort((a, b) => { 117 | return new Date(b.date).getTime() - new Date(a.date).getTime(); 118 | }); 119 | 120 | return ( 121 |31 |111 |32 |
37 |- Published on
33 |- 34 | 35 |
36 |38 |110 |39 |100 |40 |74 | 75 |41 | {allAuthors 42 | .filter((a) => project.authors.includes(a.slug)) 43 | .map((a) => ( 44 | 45 |59 |46 |56 | 57 | ))} 58 |47 |49 |48 |
50 |55 |{a.name}
51 |52 | {a.description} 53 |
54 |60 | {stars ? ( 61 | 66 | 67 | {Intl.NumberFormat("en-US", { notation: "compact" }).format(stars)} 68 | {" "} 69 | Stars on GitHub 70 | 71 | ) : null}{" "} 72 |73 |76 | 77 | {image?.data?.screenshot?.url ? ( 78 |82 |79 | ) : null} 80 | 81 |
83 | 84 | {project.title} 85 | 86 |
87 |88 | {project.tech?.map((tag) => ( 89 |97 | 98 |93 | {tag} 94 |
95 | ))} 96 |{project.description}99 |101 | 106 | Read more → 107 | 108 |109 |122 |196 | ); 197 | } 198 | -------------------------------------------------------------------------------- /app/projects/[slug]/head.tsx: -------------------------------------------------------------------------------- 1 | import { allProjects } from "@/.contentlayer/generated"; 2 | 3 | export default function Head(props: { params: { slug: string } }) { 4 | let title = "starlog.dev"; 5 | let subtitle = "A showcase of awesome open source projects and templates to help you build your next serverless app."; 6 | let authors = ""; 7 | 8 | const project = allProjects.find((project) => project.slug === props.params.slug); 9 | if (project) { 10 | title = project.title; 11 | subtitle = project.description!; 12 | authors = project.authors.join(","); 13 | } 14 | 15 | const baseUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000"; 16 | 17 | const url = new URL("/api/v1/og", baseUrl); 18 | url.searchParams.set("title", title); 19 | url.searchParams.set("subtitle", subtitle); 20 | url.searchParams.set("authors", authors); 21 | 22 | return ( 23 | <> 24 |123 | 124 | 125 | 178 |126 |177 |127 |176 |128 |175 |129 |174 |130 | starlog.dev 131 |
132 |133 | A showcase of awesome open source projects and templates to help you build your next serverless app. 134 |
135 | 136 |137 |152 | 153 | {/*Featuring
138 |139 | {allAuthors 140 | .sort((a, b) => Math.random() - 0.5) 141 | .map((author) => ( 142 | 143 |
151 |148 | 149 | ))} 150 |
154 | 158 | Get started{' '} 159 | 163 | 162 | 167 | Live demo{' '} 168 | 172 | 171 |*/} 173 |179 | 195 |180 | {projects.map((p) => { 181 | return ( 182 |
194 |- 187 |
191 | ); 192 | })} 193 |188 |190 |189 | starlog.dev 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | > 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /app/projects/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation"; 2 | import { allAuthors, allProjects } from "contentlayer/generated"; 3 | import { Mdx } from "@/app/components/mdx"; 4 | import { Navigation } from "@/app/components/nav"; 5 | import "@/styles/mdx.css"; 6 | import Link from "next/link"; 7 | 8 | interface PostPageProps { 9 | params: { 10 | slug: string; 11 | }; 12 | } 13 | 14 | export async function generateStaticParams(): Promise{ 15 | return allProjects.map((p) => ({ 16 | slug: p.slug, 17 | })); 18 | } 19 | 20 | export default async function PostPage({ params }: PostPageProps) { 21 | const slug = params?.slug; 22 | const project = allProjects.find((project) => project.slug === slug); 23 | 24 | if (!project) { 25 | notFound(); 26 | } 27 | 28 | const authors = project.authors.map((slug) => allAuthors.find((a) => a.slug === slug)!); 29 | 30 | return ( 31 | 32 |93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /content/authors/chronark.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Andreas Thomas 3 | description: Senior Software Engineer at Upstash 4 | slug: chronark 5 | avatar: https://pbs.twimg.com/profile_images/1437670380957835264/gu8S0olw_400x400.jpg 6 | twitter: 7 | username: chronark_ 8 | url: https://out.starlog.dev/chronark-twitter 9 | --- -------------------------------------------------------------------------------- /content/authors/domeccleston.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dom Eccleston 3 | description: Sales Engineer at Vercel 4 | slug: domeccleston 5 | avatar: https://pbs.twimg.com/profile_images/1569623910752165892/4FwUTWki_400x400.jpg 6 | twitter: 7 | username: dom__inic 8 | url: https://out.starlog.dev/dom-twitter 9 | --- -------------------------------------------------------------------------------- /content/authors/hassan.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Hassan El Mghari 3 | description: Senior Developer Advocate at Vercel 4 | slug: hassan 5 | avatar: https://pbs.twimg.com/profile_images/1256749436941881344/5WV3lmDm_400x400.jpg 6 | twitter: 7 | username: nutlope 8 | url: https://out.starlog.dev/hassan-twitter 9 | --- -------------------------------------------------------------------------------- /content/authors/hqasmei.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Hosna Qasmei 3 | slug: hqasmei 4 | avatar: https://pbs.twimg.com/profile_images/1609488634977349632/-BCfZKJw_400x400.jpg 5 | twitter: 6 | username: hqasmei 7 | url: https://twitter.com/hqasmei 8 | --- -------------------------------------------------------------------------------- /content/authors/shadcn.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: shadcn 3 | slug: shadcn 4 | avatar: https://pbs.twimg.com/profile_images/1593304942210478080/TUYae5z7_400x400.jpg 5 | twitter: 6 | username: shadcn 7 | url: https://out.starlog.dev/ui-shadcn 8 | --- -------------------------------------------------------------------------------- /content/authors/steventey.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Steven Tey 3 | description: Senior Developer Advocate at Vercel 4 | slug: steventey 5 | avatar: https://pbs.twimg.com/profile_images/1506792347840888834/dS-r50Je_400x400.jpg 6 | twitter: 7 | username: steventey 8 | url: https://out.starlog.dev/steven-twitter 9 | --- -------------------------------------------------------------------------------- /content/projects/dub.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: dub.sh 3 | description: Dub is an open-source link management tool for modern marketing teams to create, share, and track short links. 4 | date: "2022-09-22" 5 | dubUrl: https://out.starlog.dev/dub 6 | url: https://dub.sh 7 | repository: steven-tey/dub 8 | authors: 9 | - steventey 10 | tech: 11 | - vercel 12 | - upstash 13 | - planetscale 14 | --- 15 | 16 | 17 | ## Introduction 18 | 19 | Dub is an open-source link management tool for modern marketing teams to create, share, and track short links. Built with [Vercel Edge Functions](http://vercel.com/edge), [Upstash Redis](https://docs.upstash.com/redis), and [Planetscale MySQL](https://planetscale.com/). 20 | 21 | Here are some of the features that Dub provides out-of-the-box: 22 | 23 | ### Built-in Analytics 24 | 25 | Dub provides a powerful analytics dashboard for your links, including geolocation, device, and browser information. 26 | 27 |  28 | 29 | ### Custom domains 30 | 31 | You can easily configure custom domains on Dub – just add an A/CNAME record to your DNS provider and you're good to go. This is built on the [Vercel Domains API](https://domains-api.vercel.app/). 32 | 33 |  34 | 35 | ### QR Code Generator 36 | 37 | You can easily generate and customize QR codes for your links, which can be used for flyers, posters, powerpoint presentations, etc. 38 | 39 |  40 | 41 | ### OG Image Proxy 42 | 43 | Add a custom OG image in front of your target URL. Bots like Twitter/Facebook will be served this image, while users will be redirected to your target URL. 44 | 45 |  46 | 47 | ## Deploy Your Own 48 | 49 | > Note: one-click deployment is a bit broken at the moment – you'll need to change some of the hard-coded values in the codebase to get it working. We're working on fixing this. 50 | 51 | You can deploy your own hosted version of Dub for greater privacy & control. Just click the link below to deploy a ready-to-go version of Dub to Vercel. 52 | 53 | [](https://dub.sh/deploy) 54 | 55 | ## Tech Stack 56 | 57 | - [Next.js](https://nextjs.org/) – framework 58 | - [Typescript](https://www.typescriptlang.org/) – language 59 | - [Tailwind](https://tailwindcss.com/) – CSS 60 | - [Upstash](https://upstash.com/) – redis 61 | - [Tinybird](https://tinybird.com/) – analytics 62 | - [Planetscale](https://planetscale.com/) – database 63 | - [NextAuth.js](https://next-auth.js.org/) – auth 64 | - [Vercel](https://vercel.com/) – hosting 65 | - [Stripe](https://stripe.com/) – payments 66 | 67 | ## Implementation 68 | 69 | Dub is built as a standard Next.js application with [Middleware](https://nextjs.org/docs/advanced-features/middleware) to handle multi-tenancy, inspired by [the Vercel Platforms Starter Kit](https://github.com/vercel/platforms). 70 | 71 | [Redis](https://redis.io/) is used as the caching layer for all short links. 72 | 73 | [Clickhouse](https://clickhouse.com/) ([Tinybird](https://tinybird.com/)) is used as the analytics database for storing link click data. 74 | 75 | [MySQL](https://www.mysql.com/) is used as the database for storing user data, project data, and link metadata. You can refer to the Prisma schema [here](/prisma/schema.prisma). 76 | 77 | ## Contributing 78 | 79 | We love our contributors! Here's how you can contribute: 80 | 81 | - [Open an issue](https://github.com/steven-tey/dub/issues) if you believe you've encountered a bug. 82 | - Make a [pull request](https://github.com/steven-tey/dub/pull) to add new features/make quality-of-life improvements/fix bugs. 83 | 84 | 85 |33 | 34 | 87 |35 |70 |36 |69 |37 |68 |38 | {project.title} 39 |
40 |{project.description}
41 | 42 |43 | {/*67 |Created by
*/} 44 |45 | {authors.map((author) => ( 46 | 52 |
66 |57 |
58 |63 | 64 | ))} 65 |{author.name}
59 |60 | {author.description} 61 |
62 |71 | 76 | Website 77 | 78 | 83 | Repository 84 | 85 |86 |88 | 92 |89 | 91 |90 | 86 | 87 | 88 | ## Author 89 | 90 | - Steven Tey ([@steventey](https://twitter.com/steventey)) 91 | 92 | ## License 93 | 94 | Inspired by [Plausible](https://plausible.io/), Dub is open-source under the GNU Affero General Public License Version 3 (AGPLv3) or any later version. You can [find it here](https://github.com/steven-tey/dub/blob/main/LICENSE.md). -------------------------------------------------------------------------------- /content/projects/envshare.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: envshare.dev 3 | description: EnvShare is a simple tool to share environment variables securely. It uses AES-GCM to encrypt your data before sending it to the server. The encryption key never leaves your browser. 4 | date: "2023-01-16" 5 | dubUrl: https://out.starlog.dev/envshare 6 | url: https://envshare.dev 7 | repository: chronark/envshare 8 | authors: 9 | - chronark 10 | tech: 11 | - vercel 12 | - upstash 13 | 14 | --- 15 | 16 | EnvShare is a simple tool to share environment variables securely. It uses 17 | **AES-GCM** to encrypt your data before sending it to the server. The encryption 18 | key never leaves your browser. 19 | 20 | ## Features 21 | 22 | - **Shareable Links:** Share your environment variables securely by sending a 23 | link 24 | - **End-to-End Encryption:** AES-GCM encryption is used to encrypt your data 25 | before sending it to the server 26 | - **Limit number of reads:** Limit the number of times a link can be read 27 | - **Auto Expire:** Automatically expire links and delete data after a certain 28 | time 29 | 30 | 31 | 32 |  33 | 34 | ## Built with 35 | 36 | - [Next.js](https://nextjs.org) 37 | - [tailwindcss](https://tailwindcss.com) 38 | - Deployed on [Vercel](https://vercel.com?utm_source=envshare) 39 | - Data stored on [Upstash](https://upstash.com?utm_source=envshare) 40 | 41 | ## Deploy your own 42 | 43 | Detailed instructions can be found [here](https://envshare.dev/deploy) 44 | 45 | All you need is a Redis database on Upstash and a Vercel account. Click the 46 | button below to clone and deploy: 47 | 48 | [](https://vercel.com/new/clone?demo-title=EnvShare&demo-description=Simple%20Next.js%20%2B%20Upstash%20app%20to%20share%20environment%20variables%20securely%20using%20AES-GCM%20encryption.&demo-url=https%3A%2F%2Fenvshare.dev%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F5SaFBHXp5FBFJbsTzVqIJ3%2Ff0f8382369b7642fd8103debb9025c11%2Fenvshare.png&project-name=EnvShare&repository-name=envshare&repository-url=https%3A%2F%2Fgithub.com%2Fchronark%2Fenvshare&from=templates&integration-ids=oac_V3R1GIpkoJorr6fqyiwdhl17) 49 | 50 | 51 | 52 | ## Configuration 53 | 54 | ### Environment Variables 55 | 56 | `ENABLE_VERCEL_ANALYTICS` Any truthy value will enable Vercel Analytics. This is turned off by default 57 | 58 | ## Contributing 59 | 60 | This repository uses `pnpm` to manage dependencies. Install it using 61 | `npm install -g pnpm` 62 | 63 | Please run `pnpm fmt` before committing to format the code. 64 | 65 | ## Docs 66 | 67 | Docs in the README are temporary and will be moved to the website soon. 68 | 69 | ### API 70 | 71 | #### Store a secret 72 | 73 | **PLEASE NEVER EVER UPLOAD UNENCRYPTED SECRETS.** 74 | 75 | This endpoint is only meant to store **already encrypted** secrets. The 76 | encrypted secrets are stored in plain text. 77 | 78 | ```sh-session 79 | $ curl -XPOST -s https://envshare.dev/api/v1/secret -d "already-encrypted-secret" 80 | ``` 81 | 82 | You can add optional headers to configure the ttl and number of reads. 83 | 84 | ```sh-session 85 | $ curl -XPOST -s https://envshare.dev/api/v1/secret -d "already-encrypted-secret" -H "envshare-ttl: 3600" -H "envshare-reads: 10" 86 | ``` 87 | 88 | - Omitting the `envshare-ttl` header will set a default of 30 days. Disable the 89 | ttl by setting it to 0. (`envshare-ttl: 0`) 90 | - Omitting the `envshare-reads` header will simply disable it and allow reading 91 | for an unlimited number of times. 92 | 93 | This endpoint returns a JSON response with the secret id: 94 | 95 | ```json 96 | { 97 | "data": { 98 | "id": "HdPbXgpvUvNk43oxSdK97u", 99 | "ttl": 86400, 100 | "reads": 2, 101 | "expiresAt": "2023-01-19T20:47:28.383Z", 102 | "url": "http://envshare.dev/api/v1/secret/HdPbXgpvUvNk43oxSdK97u" 103 | } 104 | } 105 | ``` 106 | 107 | #### Retrieve a secret 108 | 109 | You need an id to retrieve a secret. The id is returned when you store a secret. 110 | 111 | ```sh-session 112 | $ curl -s https://envshare.dev/api/v1/secret/HdPbXgpvUvNk43oxSdK97u 113 | ``` 114 | 115 | ```json 116 | { 117 | "data": { 118 | "secret": "Hello", 119 | "remainingReads": 1 120 | } 121 | } 122 | ``` -------------------------------------------------------------------------------- /content/projects/extrapolate.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: extrapolate.app 3 | description: See how well you age by transforming your face using AI – 100% free and privacy-friendly. 4 | date: "2023-01-20" 5 | dubUrl: https://out.starlog.dev/extrapolate 6 | url: https://extrapolate.app 7 | repository: steven-tey/extrapolate 8 | authors: 9 | - steventey 10 | tech: 11 | - vercel 12 | - upstash 13 | - replicate 14 | - Cloudflare R2 15 | --- 16 | 17 | 18 | ## Introduction 19 | 20 | Extrapolate is an app for you to see how well you age by transforming your face with Artificial Intelligence. 100% free and privacy friendly. 21 | 22 | 23 |  24 | 25 | 26 | ## Features 27 | 28 | - 3s GIF of your face as it ages through time 🧓 29 | - Email notification when the GIF is ready (via [Replicate](https://replicate.com) webhook callback) 30 | - Store & retrieve photos from [Cloudflare R2](https://www.cloudflare.com/lp/pg-r2/) using Workers 31 | - Photos auto-delete after 24 hours (via [Upstash](https://upstash.com) qStash) 32 | 33 | ## One-click Deploy 34 | 35 | You can deploy this template to Vercel with the button below: 36 | 37 | [](https://vercel.com/new/clone?demo-title=Extrapolate%20%E2%80%93%C2%A0See%20how%20well%20you%20age%20with%20AI&demo-description=Age%20transformation%20AI%20app%20powered%20by%20Next.js%2C%20Replicate%2C%20Upstash%2C%20and%20Cloudflare%20R2%20%2B%20Workers.&demo-url=https%3A%2F%2Fextrapolate.app%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F4B2RUQ7DTvPgpf3Ra9jSC2%2Fda2571b055081a670ac9649d3ac0ac7a%2FCleanShot_2023-01-20_at_12.04.08.png&project-name=Extrapolate%20%E2%80%93%C2%A0See%20how%20well%20you%20age%20with%20AI&repository-name=extrapolate&repository-url=https%3A%2F%2Fgithub.com%2Fsteven-tey%2Fextrapolate&from=templates&integration-ids=oac_V3R1GIpkoJorr6fqyiwdhl17&env=REPLICATE_API_KEY%2CREPLICATE_WEBHOOK_TOKEN%2CCLOUDFLARE_WORKER_SECRET%2CPOSTMARK_TOKEN&envDescription=How%20to%20get%20these%20env%20variables%3A%20&envLink=https%3A%2F%2Fgithub.com%2Fsteven-tey%2Fextrapolate%2Fblob%2Fmain%2F.env.example) 38 | 39 | Note that you'll need to set up a [ReplicateHQ](https://replicate.com) account and [Upstash](https://upstash.com) account to get the required environment variables. 40 | 41 | You'll also need to create a [Cloudflare R2 instance](https://www.cloudflare.com/lp/pg-r2/) and set up a Cloudflare Worker to handle uploads & reads (will add more instructions on this soon). 42 | 43 | ## Author 44 | 45 | - Steven Tey ([@steventey](https://twitter.com/steventey)) -------------------------------------------------------------------------------- /content/projects/precedent.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: precedent.dev 3 | description: An opinionated collection of components, hooks, and utilities for your Next.js project. 4 | date: "2023-01-13" 5 | dubUrl: https://out.starlog.dev/precedent 6 | url: https://precedent.vercel.app 7 | repository: steven-tey/precedent 8 | authors: 9 | - steventey 10 | tech: 11 | - vercel 12 | - upstash 13 | - planetscale 14 | - tailwindcss 15 | - authjs 16 | - prisma 17 | - framer 18 | --- 19 | 20 | 21 | ## Introduction 22 | 23 | Precedent is an opinionated collection of components, hooks, and utilities for your Next.js project. 24 | 25 | ## One-click Deploy 26 | 27 | You can deploy this template to Vercel with the button below: 28 | 29 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsteven-tey%2Fprecedent&project-name=precedent&repository-name=precedent&demo-title=Precedent&demo-description=An%20opinionated%20collection%20of%20components%2C%20hooks%2C%20and%20utilities%20for%20your%20Next%20project.&demo-url=https%3A%2F%2Fprecedent.dev&demo-image=https%3A%2F%2Fprecedent.dev%2Fapi%2Fog&env=DATABASE_URL,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,NEXTAUTH_SECRET&envDescription=How%20to%20get%20these%20env%20variables%3A&envLink=https%3A%2F%2Fgithub.com%2Fsteven-tey%2Fprecedent%2Fblob%2Fmain%2F.env.example) 30 | 31 | You can also clone & create this repo locally with the following command: 32 | 33 | ```bash 34 | npx create-next-app precedent --example "https://github.com/steven-tey/precedent" 35 | ``` 36 | 37 | ## Tech Stack + Features 38 | 39 | https://user-images.githubusercontent.com/28986134/212368288-12f41e37-aa8c-4e0a-a542-cf6d23410a65.mp4 40 | 41 | ### Frameworks 42 | 43 | - [Next.js](https://nextjs.org/) – React framework for building performant apps with the best developer experience 44 | - [Auth.js](https://authjs.dev/) – Handle user authentication with ease with providers like Google, Twitter, GitHub, etc. 45 | - [Prisma](https://www.prisma.io/) – Typescript-first ORM for Node.js 46 | 47 | ### Platforms 48 | 49 | - [Vercel](https://vercel.com/) – Easily preview & deploy changes with git 50 | - [Railway](https://railway.app/) – Easily provision a PostgreSQL database (no login required) 51 | 52 | ### UI 53 | 54 | - [Tailwind CSS](https://tailwindcss.com/) – Utility-first CSS framework for rapid UI development 55 | - [Radix](https://www.radix-ui.com/) – Primitives like modal, popover, etc. to build a stellar user experience 56 | - [Framer Motion](https://framer.com/motion) – Motion library for React to animate components with ease 57 | - [Lucide](https://lucide.dev/) – Beautifully simple, pixel-perfect icons 58 | - [`@next/font`](https://nextjs.org/docs/basic-features/font-optimization) – Optimize custom fonts and remove external network requests for improved performance 59 | - [`@vercel/og`](https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation) – Generate dynamic Open Graph images on the edge 60 | - [`react-wrap-balancer`](https://github.com/shuding/react-wrap-balancer) – Simple React component that makes titles more readable 61 | 62 | ### Hooks and Utilities 63 | 64 | - `useIntersectionObserver` – React hook to observe when an element enters or leaves the viewport 65 | - `useLocalStorage` – Persist data in the browser's local storage 66 | - `useScroll` – React hook to observe scroll position ([example](https://github.com/steven-tey/precedent/blob/main/components/layout/index.tsx#L25)) 67 | - `nFormatter` – Format numbers with suffixes like `1.2k` or `1.2M` 68 | - `capitalize` – Capitalize the first letter of a string 69 | - `truncate` – Truncate a string to a specified length 70 | - [`use-debounce`](https://www.npmjs.com/package/use-debounce) – Debounce a function call / state update 71 | 72 | ### Code Quality 73 | 74 | - [TypeScript](https://www.typescriptlang.org/) – Static type checker for end-to-end typesafety 75 | - [Prettier](https://prettier.io/) – Opinionated code formatter for consistent code style 76 | - [ESLint](https://eslint.org/) – Pluggable linter for Next.js and TypeScript 77 | 78 | ### Miscellaneous 79 | 80 | - [Vercel Analytics](https://vercel.com/analytics) – Track unique visitors, pageviews, and more in a privacy-friendly way 81 | 82 | ## Author 83 | 84 | - Steven Tey ([@steventey](https://twitter.com/steventey)) -------------------------------------------------------------------------------- /content/projects/restorephotos.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: restorephotos.io 3 | description: Have old and blurry face photos? Let our AI restore them so those memories can live on. 100% free – restore your photos today. 4 | date: "2023-01-09" 5 | dubUrl: https://out.starlog.dev/restorephotos 6 | url: https://restorephotos.io 7 | repository: nutlope/restorephotos 8 | 9 | authors: 10 | - hassan 11 | tech: 12 | - vercel 13 | - replicate 14 | - upstash 15 | 16 | --- 17 | 18 | 19 | This project restores old face photos using AI. Watch the [4 minute explainer video](https://twitter.com/nutlope/status/1614794731396931585) to see how I built this or see the [15 second demo](https://twitter.com/nutlope/status/1612488923716136962). 20 | 21 | [](https://restorephotos.io/) 22 | 23 | ## How it works 24 | 25 | It uses an ML model from the Applied Research Center called [GFPGAN](https://github.com/TencentARC/GFPGAN) on [Replicate](https://replicate.com/) to restore face photos. This application gives you the ability to upload any photo, which will send it through this ML Model using a Next.js API route, and return your restored photo. 26 | 27 | ## Running Locally 28 | 29 | After cloning the repo, go to [Replicate](https://replicate.com/) to make an account and put your API key in a file called `.env`. If you'd also like to do rate limiting, create an account on UpStash, create a Redis database, and populate the two environment variables in `.env` as well. If you don't want to do rate limiting, you don't need to make any changes. 30 | 31 | Then, run the application in the command line and it will be available at `http://localhost:3000`. 32 | 33 | ```bash 34 | npm run dev 35 | ``` 36 | 37 | ## One-Click Deploy 38 | 39 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples): 40 | 41 | [](https://vercel.com/new/clone?repository-url=https://github.com/Nutlope/restorePhotos&env=REPLICATE_API_KEY&project-name=face-photo-restorer&repo-name=restore-photos) -------------------------------------------------------------------------------- /content/projects/sharegpt.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: sharegpt.com 3 | description: Share your wildest ChatGPT conversations with one click. 4 | date: "2022-12-05" 5 | dubUrl: https://out.starlog.dev/sharegpt 6 | url: https://sharegpt.com 7 | repository: domeccleston/sharegpt 8 | authors: 9 | - domeccleston 10 | - steventey 11 | tech: 12 | - vercel 13 | - upstash 14 | - planetscale 15 | 16 | --- 17 | 18 | ## Introduction 19 | 20 | ShareGPT is an open-source Chrome Extension for you to share your wildest ChatGPT conversations with one click. 21 | 22 | ### Features 23 | 24 | - Share your ChatGPT conversations with one-click 25 | - Browse examples on sharegpt.com/explore 26 | - Save your favorite conversations for later 27 | - Leave comments on conversations 28 | 29 | ## Tech Stack 30 | 31 | ShareGPT is built with the following stack: 32 | 33 | - [Next.js](https://nextjs.org/) – framework 34 | - [Typescript](https://www.typescriptlang.org/) – language 35 | - [Tailwind](https://tailwindcss.com/) – CSS 36 | - [Upstash](https://upstash.com/) – redis 37 | - [Planetscale](https://planetscale.com/) – database 38 | - [NextAuth.js](https://next-auth.js.org/) – auth 39 | - [Vercel](https://vercel.com/) – hosting 40 | 41 | ## REST API 42 | 43 | The ShareGPT API is a REST-styled API that allows you to write and read conversations from our database, exposed as HTTP endpoints. 44 | 45 | ### Conversations Endpoint 46 | 47 | #### POST: `https://sharegpt.com/api/conversations` 48 | 49 | You can use this endpoint to add new conversations to our database. 50 | 51 |
52 |107 | 108 | Then, send a POST request to the endpoint above with the following payload and request headers: 109 | 110 | ```ts 111 | const res = await fetch("https://sharegpt.com/api/conversations", { 112 | body: JSON.stringify(conversationData), 113 | headers: { 114 | "Content-Type": "application/json", 115 | }, 116 | method: "POST", 117 | }); 118 | ``` 119 | 120 | This will return an object with an `id` attribute which will be the unique identifier for the generated post: 121 | 122 | ```ts 123 | const { id } = await res.json(); 124 | const url = `https://shareg.pt/${id}`; // short link to the ShareGPT post 125 | ``` 126 | 127 | #### GET: `https://sharegpt.com/api/conversations` 128 | 129 | This endpoint takes 3 optional query parameters: 130 | 131 | - `type`: 132 | - Used for sorting the results. 133 | - Takes 2 string values: `"new" | "top"` 134 | - `"new"` sorts conversations by creation time 135 | - `"top"` sorts conversations by number of views 136 | - If `undefined`, defaults to `"top"` 137 | - `page`: 138 | - Used for pagination 139 | - Takes an integer value as a factor of the `PAGINATION_LIMIT`, which is set to 50. 140 | - E.g. to get posts 100 - 150, set `page` to `3` 141 | - If `undefined`, defaults to `1` 142 | - `search` 143 | - Used for filtering records by title. 144 | - E.g. `search = "python"` returns all records with the word "python" in the title 145 | - If `undefined`, search results are not filtered 146 | 147 | Example: 148 | 149 | ```ts 150 | await fetch( 151 | "https://sharegpt.com/api/conversations?type=new&page=2&search=python" 152 | ); 153 | ``` 154 | 155 | This returns a list of conversations with the following type: 156 | 157 | ```ts 158 | interface ConversationMeta { 159 | id: string; // unique id for the conversation 160 | title: string; // title of the conversation (first user prompt) 161 | avatar: string; // base64 encoded URI of the user's avatar 162 | saves: number; // number of times the conversation is saved on ShareGPT 163 | comments: number; // number of comments the conversation has on ShareGPT 164 | views: number; // number of times the conversation has been viewed on ShareGPT 165 | createdAt: Date; // timestamp when the conversation was creataed 166 | } 167 | []; 168 | ``` -------------------------------------------------------------------------------- /content/projects/starlog.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: starlog.dev 3 | description: A showcase of awesome open source projects and templates to help you build your next serverless app. 4 | date: "2023-01-23" 5 | dubUrl: https://out.starlog.dev/starlog 6 | url: https://starlog.dev 7 | repository: chronark/starlog 8 | authors: 9 | - chronark 10 | tech: 11 | - vercel 12 | - tailwindcss 13 | 14 | --- 15 | 16 | 17 | 18 |  19 | 20 | Starlog is a blog starter kit for your next serverless app. It's also a collection of cool open source apps and templates. 21 | 22 | 23 | ## Features 24 | 25 | - Next.js 13 with app directory 26 | - Page view analytics built with [Upstash](https://upstash.com?ref=starlog) and [Tremor](https://tremor.so?ref=starlog) 27 | - Automatic screenshots of websites with [Microlink](https://microlink.io?ref=starlog) 28 | 29 | 30 | 31 | ## Built with 32 | - [Next.js 13](https://nextjs.org) 33 | - [tailwindcss](https://tailwindcss.com) 34 | - [Contentlayer](https://www.contentlayer.dev) 35 | - [Tremor](https://tremor.so) 36 | - [Upstash Redis](https://upstash.com) 37 | - [Vercel](https://vercel.com) 38 | 39 | 40 | 41 | ## Deploy your own 42 | 43 | 44 | All you need is an Upstash and a Vercel account. Click the button below to clone and deploy: 45 | 46 | [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fchronark%2Fstarlog&project-name=starlog&repository-name=starlog&demo-title=Starlog.dev&demo-url=https%3A%2F%2Fstarlog.dev&demo-image=https%3A%2F%2Fgithub.com%2Fchronark%2Fstarlog%2Fblob%2Fmain%2Fimage%2Fstarlog.png%3Fraw%3Dtrue&integration-ids=oac_V3R1GIpkoJorr6fqyiwdhl17) -------------------------------------------------------------------------------- /content/projects/thankfulthoughts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ThankfulThoughts.io 3 | description: ThankfulThoughts is a web application that allows users to input a person, place, or thing, and generates a sentence of gratitude in return. It's a simple yet powerful tool to help users express gratitude and positive thoughts towards the things they are thankful for. 4 | date: "2023-01-22" 5 | dubUrl: https://thankfulthoughts.io/ 6 | url: https://thankfulthoughts.io/ 7 | repository: hqasmei/thankful-thoughts 8 | authors: 9 | - hqasmei 10 | tech: 11 | - openai 12 | - nextjs 13 | - vercel 14 | --- 15 | 16 | 17 | ThankfulThoughts is a web application that allows users to input a person, place, or thing, and generates a sentence of gratitude in return. It's a simple yet powerful tool to help users express gratitude and positive thoughts towards the things they are thankful for. 18 | 19 | [](https://www.thankfulthoughts.io) 20 | 21 | 22 | ## How it works 23 | 24 | This project uses the [OpenAI GPT-3 API](https://openai.com/api/) (specifically, text-davinci-003). It asks a user to select from a dropdown a person, place or thing as an input. Then asks from the name. Then sends it to the GPT-3 API and sends the response back to the application. 25 | 26 | ## Running Locally 27 | 28 | After cloning the repo, go to [OpenAI](https://beta.openai.com/account/api-keys) to make an account and put your API key in a file called `.env`. 29 | 30 | Then, run the application in the command line and it will be available at `http://localhost:3000`. 31 | 32 | ```bash 33 | npm install 34 | npm run dev 35 | ``` 36 | 37 | ## One-Click Deploy 38 | 39 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples): 40 | 41 | [](https://vercel.com/new/clone?repository-url=https://github.com/hqasmei/thankful-thoughts&env=OPENAI_API_KEY&project-name=thankful-thoughts&repo-name=thankful-thoughts) 42 | 43 | * Make sure to include environment variable in Vercel Environmental Veriables -------------------------------------------------------------------------------- /content/projects/twitterbio.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: twitterbio.com 3 | description: Generate your next Twitter bio in seconds 4 | date: "2023-01-16" 5 | dubUrl: https://out.starlog.dev/twitterbio 6 | url: https://twitterbio.com 7 | repository: nutlope/twitterbio 8 | 9 | authors: 10 | - hassan 11 | tech: 12 | - vercel 13 | - openai 14 | --- 15 | 16 | 17 | This project generates Twitter bios for you using AI. 18 | 19 | [](https://www.twitterbio.com) 20 | 21 | ## How it works 22 | 23 | This project uses the [OpenAI GPT-3 API](https://openai.com/api/) (specifically, text-davinci-003) and [Vercel Edge functions](https://vercel.com/features/edge-functions) with streaming. It constructs a prompt based on the form and user input, sends it to the GPT-3 API via a Vercel Edge function, then streams the response back to the application. 24 | 25 | Video and blog post coming soon on how to build apps with OpenAI and Vercel Edge functions! 26 | 27 | ## Running Locally 28 | 29 | After cloning the repo, go to [OpenAI](https://beta.openai.com/account/api-keys) to make an account and put your API key in a file called `.env`. 30 | 31 | Then, run the application in the command line and it will be available at `http://localhost:3000`. 32 | 33 | ```bash 34 | npm run dev 35 | ``` 36 | 37 | ## One-Click Deploy 38 | 39 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples): 40 | 41 | [](https://vercel.com/new/clone?repository-url=https://github.com/Nutlope/twitterbio&env=OPENAI_API_KEY&project-name=twitter-bio-generator&repo-name=twitterbio) -------------------------------------------------------------------------------- /content/projects/ui-shadcn.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ui 3 | description: Beautifully designed components built with Radix UI and Tailwind CSS. 4 | date: "2023-01-24" 5 | dubUrl: https://out.starlog.dev/ui-shadcn 6 | url: https://ui.shadcn.com 7 | repository: shadcn/ui 8 | authors: 9 | - shadcn 10 | tech: 11 | - tailwindcss 12 | - radix-ui 13 | --- 14 | 15 | 16 | Beautifully designed components built with Radix UI and Tailwind CSS. 17 | 18 |  19 | 20 | ## Roadmap 21 | 22 | > **Warning** 23 | > This is work in progress. I'm building this in public. You can follow the progress on Twitter [@shadcn](https://twitter.com/shadcn). 24 | 25 | - [ ] Toast 26 | - [ ] Toggle 27 | - [ ] Toggle Group 28 | - [ ] Toolbar 29 | - [ ] Navigation Menu 30 | - [ ] Figma? 31 | 32 | ## Get Started 33 | 34 | Starting a new project? Check out the Next.js template. 35 | 36 | ```bash 37 | npx create-next-app -e https://github.com/shadcn/next-template 38 | ``` 39 | 40 | ### Features 41 | 42 | - Radix UI Primitives 43 | - Tailwind CSS 44 | - Fonts with `@next/font` 45 | - Icons from [Lucide](https://lucide.dev) 46 | - Dark mode with `next-themes` 47 | - Automatic import sorting with `@ianvs/prettier-plugin-sort-imports` 48 | 49 | ### Tailwind CSS Features 50 | 51 | - Class merging with `tailwind-merge` 52 | - Animation with `tailwindcss-animate` 53 | - Conditional classes with `clsx` 54 | - Variants with `class-variance-authority` 55 | - Automatic class sorting with `eslint-plugin-tailwindcss` 56 | 57 | ## License 58 | 59 | Licensed under the [MIT license](https://github.com/shadcn/ui/blob/main/LICENSE.md). -------------------------------------------------------------------------------- /content/tech/upstash.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Upstash 3 | description: Serverless Data for Redis and Kafka 4 | slug: upstash 5 | logo: https://upstash.com/static/logo/logo-dark.svg 6 | --- -------------------------------------------------------------------------------- /content/tech/vercel.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Vercel 3 | description: Develop. Preview. Ship. 4 | slug: vercel 5 | logo: https://upload.wikimedia.org/wikipedia/commons/thumb/5/5e/Vercel_logo_black.svg/2560px-Vercel_logo_black.svg.png 6 | --- -------------------------------------------------------------------------------- /contentlayer.config.js: -------------------------------------------------------------------------------- 1 | import { defineDocumentType, makeSource } from "contentlayer/source-files"; 2 | import remarkGfm from "remark-gfm"; 3 | import rehypePrettyCode from "rehype-pretty-code"; 4 | import rehypeSlug from "rehype-slug"; 5 | import rehypeAutolinkHeadings from "rehype-autolink-headings"; 6 | 7 | /** @type {import('contentlayer/source-files').ComputedFields} */ 8 | const computedFields = { 9 | path: { 10 | type: "string", 11 | resolve: (doc) => `/${doc._raw.flattenedPath}`, 12 | }, 13 | slug: { 14 | type: "string", 15 | resolve: (doc) => doc._raw.flattenedPath.split("/").slice(1).join("/"), 16 | }, 17 | }; 18 | 19 | export const Project = defineDocumentType(() => ({ 20 | name: "Project", 21 | filePathPattern: "projects/**/*.mdx", 22 | contentType: "mdx", 23 | 24 | fields: { 25 | title: { 26 | type: "string", 27 | required: true, 28 | }, 29 | description: { 30 | type: "string", 31 | }, 32 | date: { 33 | type: "date", 34 | required: true, 35 | }, 36 | dubUrl: { 37 | type: "string", 38 | required: true, 39 | }, 40 | url: { 41 | type: "string", 42 | required: true, 43 | }, 44 | tech: { 45 | type: "list", 46 | of: { type: "string" }, 47 | }, 48 | repository: { 49 | type: "string", 50 | required: true, 51 | }, 52 | authors: { 53 | // Reference types are not embedded. 54 | // Until this is fixed, we can use a simple list. 55 | // type: "reference", 56 | // of: Author, 57 | type: "list", 58 | of: { type: "string" }, 59 | required: true, 60 | }, 61 | }, 62 | computedFields, 63 | })); 64 | 65 | export const Author = defineDocumentType(() => ({ 66 | name: "Author", 67 | filePathPattern: "authors/**/*.mdx", 68 | contentType: "mdx", 69 | fields: { 70 | slug: { 71 | type: "string", 72 | required: true, 73 | }, 74 | name: { 75 | type: "string", 76 | required: true, 77 | }, 78 | description: { 79 | type: "string", 80 | }, 81 | twitter: { 82 | type: "nested", 83 | required: true, 84 | of: defineDocumentType(() => ({ 85 | name: "Twitter", 86 | fields: { 87 | username: { 88 | type: "string", 89 | required: true, 90 | }, 91 | url: { 92 | type: "string", 93 | required: true, 94 | }, 95 | }, 96 | })), 97 | }, 98 | avatar: { 99 | type: "string", 100 | required: true, 101 | }, 102 | }, 103 | computedFields, 104 | })); 105 | 106 | export const Tech = defineDocumentType(() => ({ 107 | name: "Tech", 108 | filePathPattern: "tech/**/*.mdx", 109 | contentType: "mdx", 110 | fields: { 111 | slug: { 112 | type: "string", 113 | required: true, 114 | }, 115 | name: { 116 | type: "string", 117 | required: true, 118 | }, 119 | description: { 120 | type: "string", 121 | }, 122 | 123 | logo: { 124 | type: "string", 125 | required: true, 126 | }, 127 | }, 128 | computedFields, 129 | })); 130 | 131 | export const Page = defineDocumentType(() => ({ 132 | name: "Page", 133 | filePathPattern: "pages/**/*.mdx", 134 | contentType: "mdx", 135 | fields: { 136 | title: { 137 | type: "string", 138 | required: true, 139 | }, 140 | description: { 141 | type: "string", 142 | }, 143 | }, 144 | computedFields, 145 | })); 146 | 147 | export default makeSource({ 148 | contentDirPath: "./content", 149 | documentTypes: [Page, Project, Author, Tech], 150 | mdx: { 151 | remarkPlugins: [remarkGfm], 152 | rehypePlugins: [ 153 | rehypeSlug, 154 | [ 155 | rehypePrettyCode, 156 | { 157 | theme: "github-dark", 158 | onVisitLine(node) { 159 | // Prevent lines from collapsing in `display: grid` mode, and allow empty 160 | // lines to be copy/pasted 161 | if (node.children.length === 0) { 162 | node.children = [{ type: "text", value: " " }]; 163 | } 164 | }, 165 | onVisitHighlightedLine(node) { 166 | node.properties.className.push("line--highlighted"); 167 | }, 168 | onVisitHighlightedWord(node) { 169 | node.properties.className = ["word--highlighted"]; 170 | }, 171 | }, 172 | ], 173 | [ 174 | rehypeAutolinkHeadings, 175 | { 176 | properties: { 177 | className: ["subheading-anchor"], 178 | ariaLabel: "Link to section", 179 | }, 180 | }, 181 | ], 182 | ], 183 | }, 184 | }); 185 | -------------------------------------------------------------------------------- /image/starlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chronark/starlog/48e27a6f7a9e1c74c3dd055d8841a4e7919580c6/image/starlog.png -------------------------------------------------------------------------------- /lib/formatDate.ts: -------------------------------------------------------------------------------- 1 | export function formatDate(dateString: string): string { 2 | return new Date(`${dateString}T00:00:00Z`).toLocaleDateString("en-US", { 3 | day: "numeric", 4 | month: "long", 5 | year: "numeric", 6 | timeZone: "UTC", 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /lib/getAllArticles.ts: -------------------------------------------------------------------------------- 1 | import glob from "fast-glob"; 2 | import * as path from "path"; 3 | 4 | async function importArticle(projectFilename: string) { 5 | const { meta, default: component } = await import(`../pages/projects/${projectFilename}`); 6 | return { 7 | slug: projectFilename.replace(/(\/index)?\.mdx$/, ""), 8 | ...meta, 9 | component, 10 | }; 11 | } 12 | 13 | export async function getAllArticles() { 14 | const projectFilenames = await glob(["*.mdx", "*/index.mdx"], { 15 | cwd: path.join(process.cwd(), "pages", "projects"), 16 | }); 17 | 18 | const articles = await Promise.all(projectFilenames.map(importArticle)); 19 | 20 | return articles.sort((a, z) => new Date(z.date).getTime() - new Date(a.date).getTime()); 21 | } 22 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextFetchEvent, NextResponse } from "next/server"; 2 | import { Redis } from "@upstash/redis"; 3 | const redis = Redis.fromEnv(); 4 | 5 | export const config = { 6 | matcher: [ 7 | /* 8 | * Match all request paths except for the ones starting with: 9 | * - _next/static (static files) 10 | * - _next/image (image optimization files) 11 | * - favicon.ico (favicon file) 12 | */ 13 | "/((?!_next/static|_next/image|favicon.ico).*)", 14 | ], 15 | }; 16 | 17 | export default function middleware(req: Request, event: NextFetchEvent): NextResponse { 18 | const url = new URL(req.url); 19 | 20 | /** 21 | * Divide time into 1 hour buckets 22 | */ 23 | const bucket = new Date().setMinutes(0, 0, 0).toString(); 24 | 25 | /** 26 | * Increment the pageview counter for this URL but outside of the critical path 27 | * so that it doesn't block the response. 28 | */ 29 | event.waitUntil(redis.zincrby(["pageviews", bucket].join(":"), 1, url.pathname)); 30 | 31 | return NextResponse.next(); 32 | } 33 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | import { withContentlayer } from "next-contentlayer"; 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | reactStrictMode: true, 6 | images: { 7 | domains: ["avatars.githubusercontent.com", "pbs.twimg.com"], 8 | }, 9 | experimental: { 10 | appDir: true, 11 | serverComponentsExternalPackages: ["@tremor/react"], 12 | }, 13 | }; 14 | 15 | export default withContentlayer(nextConfig); 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shiplog", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "fmt": "pnpm rome check . --apply-suggested && pnpm rome format . --write" 11 | }, 12 | "dependencies": { 13 | "@heroicons/react": "^2.0.13", 14 | "@mapbox/rehype-prism": "^0.8.0", 15 | "@microlink/mql": "^0.10.32", 16 | "@next/font": "13.1.3", 17 | "@next/mdx": "^13.1.3", 18 | "@tailwindcss/typography": "^0.5.9", 19 | "@tremor/react": "^1.6.0", 20 | "@types/node": "18.11.18", 21 | "@types/react": "18.0.27", 22 | "@types/react-dom": "18.0.10", 23 | "@upstash/redis": "^1.19.3", 24 | "@vercel/analytics": "^0.1.8", 25 | "@vercel/og": "^0.0.27", 26 | "clsx": "^1.2.1", 27 | "contentlayer": "^0.3.0", 28 | "fast-glob": "^3.2.12", 29 | "focus-visible": "^5.2.0", 30 | "next": "13.1.3", 31 | "next-contentlayer": "^0.3.0", 32 | "postcss-focus-visible": "^7.1.0", 33 | "react": "18.2.0", 34 | "react-dom": "18.2.0", 35 | "rehype-autolink-headings": "^6.1.1", 36 | "rehype-pretty-code": "^0.9.2", 37 | "rehype-slug": "^5.1.0", 38 | "remark-gfm": "^3.0.1", 39 | "typescript": "4.9.4" 40 | }, 41 | "devDependencies": { 42 | "autoprefixer": "^10.4.13", 43 | "postcss": "^8.4.21", 44 | "rome": "^11.0.0", 45 | "tailwindcss": "^3.2.4" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pages/api/v1/og.tsx: -------------------------------------------------------------------------------- 1 | import { ImageResponse } from "@vercel/og"; 2 | import { NextRequest } from "next/server"; 3 | import { allAuthors } from "@/.contentlayer/generated"; 4 | 5 | export const config = { 6 | runtime: "edge", 7 | }; 8 | 9 | export default async function handler(req: NextRequest) { 10 | try { 11 | const { searchParams } = new URL(req.url); 12 | // Redundant fallback alternate tagline 13 | const title = searchParams.get("title") ?? "starlog.dev"; 14 | const subtitle = 15 | searchParams.get("subtitle") ?? 16 | "A showcase of awesome open source projects and templates to help you build your next serverless app."; 17 | const authorsParam = searchParams.get("authors"); 18 | const authors = (authorsParam ? authorsParam.split(",") : []).map( 19 | (slug) => allAuthors.find((a) => a.slug === slug)!, 20 | ); 21 | 22 | const font = await fetch(new URL("../../../public/fonts/PangeaAfrikanTrial-SemiBold.woff", import.meta.url)).then( 23 | (res) => res.arrayBuffer(), 24 | ); 25 | 26 | // TODO: Fix tailwind classes on this route 27 | return new ImageResponse( 28 |First, if you haven't already, process the ShareGPT conversation using the following code:
53 | 54 | ```ts 55 | function conversationData() { 56 | const threadContainer = document.querySelector( 57 | "#__next main div:nth-of-type(1) div:nth-of-type(1) div:nth-of-type(1) div:nth-of-type(1)" 58 | ); 59 | 60 | var result = { 61 | avatarUrl: getAvatarImage(), 62 | items: [], 63 | }; 64 | 65 | for (const node of threadContainer.children) { 66 | const markdownContent = node.querySelector(".markdown"); 67 | 68 | // tailwind class indicates human or gpt 69 | if ([...node.classList].includes("dark:bg-gray-800")) { 70 | result.items.push({ 71 | from: "human", 72 | value: node.textContent, 73 | }); 74 | // if it's a GPT response, it might contain code blocks 75 | } else if ([...node.classList].includes("bg-gray-50")) { 76 | result.items.push({ 77 | from: "gpt", 78 | value: markdownContent.outerHTML, 79 | }); 80 | } 81 | } 82 | 83 | return result; 84 | } 85 | 86 | function getAvatarImage() { 87 | // Create a canvas element 88 | const canvas = document.createElement("canvas"); 89 | 90 | const image = document.querySelectorAll("img")[1]; 91 | 92 | // Set the canvas size to 30x30 pixels 93 | canvas.width = 30; 94 | canvas.height = 30; 95 | 96 | // Draw the img onto the canvas 97 | canvas.getContext("2d").drawImage(image, 0, 0); 98 | 99 | // Convert the canvas to a base64 string as a JPEG image 100 | const base64 = canvas.toDataURL("image/jpeg"); 101 | 102 | return base64; 103 | } 104 | ``` 105 | 106 |29 |, 47 | { 48 | height: 630, 49 | width: 1200, 50 | emoji: "twemoji", 51 | fonts: [ 52 | { 53 | name: "Pangea", 54 | data: font, 55 | style: "normal", 56 | }, 57 | ], 58 | }, 59 | ); 60 | } catch (e) { 61 | console.log(`${(e as Error).message}`); 62 | return new Response("Failed to generate the image", { 63 | status: 500, 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | "postcss-focus-visible": { 5 | replaceWith: "[data-focus-visible-added]", 6 | }, 7 | autoprefixer: {}, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chronark/starlog/48e27a6f7a9e1c74c3dd055d8841a4e7919580c6/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/PangeaAfrikanTrial-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chronark/starlog/48e27a6f7a9e1c74c3dd055d8841a4e7919580c6/public/fonts/PangeaAfrikanTrial-Bold.woff2 -------------------------------------------------------------------------------- /public/fonts/PangeaAfrikanTrial-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chronark/starlog/48e27a6f7a9e1c74c3dd055d8841a4e7919580c6/public/fonts/PangeaAfrikanTrial-Light.woff2 -------------------------------------------------------------------------------- /public/fonts/PangeaAfrikanTrial-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chronark/starlog/48e27a6f7a9e1c74c3dd055d8841a4e7919580c6/public/fonts/PangeaAfrikanTrial-Medium.woff2 -------------------------------------------------------------------------------- /public/fonts/PangeaAfrikanTrial-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chronark/starlog/48e27a6f7a9e1c74c3dd055d8841a4e7919580c6/public/fonts/PangeaAfrikanTrial-Regular.woff2 -------------------------------------------------------------------------------- /public/fonts/PangeaAfrikanTrial-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chronark/starlog/48e27a6f7a9e1c74c3dd055d8841a4e7919580c6/public/fonts/PangeaAfrikanTrial-SemiBold.woff -------------------------------------------------------------------------------- /public/fonts/PangeaAfrikanTrial-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chronark/starlog/48e27a6f7a9e1c74c3dd055d8841a4e7919580c6/public/fonts/PangeaAfrikanTrial-SemiBold.woff2 -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/thirteen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/rome/configuration_schema.json", 3 | "linter": { 4 | "enabled": true, 5 | "rules": { 6 | "recommended": true 7 | }, 8 | "ignore": [ 9 | "node_modules", 10 | ".next", 11 | "dist", 12 | ".turbo" 13 | ] 14 | }, 15 | "formatter": { 16 | "enabled": true, 17 | "lineWidth": 120, 18 | "indentStyle": "space", 19 | "ignore": [ 20 | "node_modules", 21 | ".next", 22 | "dist", 23 | ".turbo" 24 | ] 25 | } 26 | } -------------------------------------------------------------------------------- /styles/mdx.css: -------------------------------------------------------------------------------- 1 | [data-rehype-pretty-code-fragment] code { 2 | @apply grid min-w-full break-words rounded-none border-0 bg-transparent p-0 text-sm text-black; 3 | counter-reset: line; 4 | box-decoration-break: clone; 5 | } 6 | [data-rehype-pretty-code-fragment] .line { 7 | @apply px-4 py-1; 8 | } 9 | [data-rehype-pretty-code-fragment] [data-line-numbers] > .line::before { 10 | counter-increment: line; 11 | content: counter(line); 12 | display: inline-block; 13 | width: 1rem; 14 | margin-right: 1rem; 15 | text-align: right; 16 | color: gray; 17 | } 18 | [data-rehype-pretty-code-fragment] .line--highlighted { 19 | @apply bg-slate-300 bg-opacity-10; 20 | } 21 | [data-rehype-pretty-code-fragment] .line-highlighted span { 22 | @apply relative; 23 | } 24 | [data-rehype-pretty-code-fragment] .word--highlighted { 25 | @apply rounded-md bg-slate-300 bg-opacity-10 p-1; 26 | } 27 | [data-rehype-pretty-code-title] { 28 | @apply mt-4 py-2 px-4 text-sm font-medium; 29 | } 30 | [data-rehype-pretty-code-title] + pre { 31 | @apply mt-0; 32 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require("tailwindcss/defaultTheme"); 2 | /** @type {import('tailwindcss').Config} */ 3 | module.exports = { 4 | content: ["./app/**/*.{ts,js,tsx,jsx,mdx}", "./projects/**/*.mdx", "./authors/**/*.mdx"], 5 | darkMode: "class", 6 | theme: { 7 | fontFamily: { 8 | sans: ["var(--font-inter)", ...defaultTheme.fontFamily.sans], 9 | display: ["var(--font-pangea)"], 10 | }, 11 | }, 12 | plugins: [require("@tailwindcss/typography")], 13 | }; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "baseUrl": ".", 23 | "paths": { 24 | "@/*": ["./*"], 25 | "contentlayer/generated": ["./.contentlayer/generated"] 26 | } 27 | }, 28 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 29 | "exclude": ["node_modules"] 30 | } 31 | --------------------------------------------------------------------------------{title}
30 |{subtitle}
31 |32 | {authors.map((author) => ( 33 |
46 |34 |44 | ))} 45 |39 |
40 | {author.name} 41 | {author.description} 42 |43 |