├── .env.example ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── prettier.config.js ├── prisma ├── migrations │ ├── 20240211092205_add_type_and_releaselink │ │ └── migration.sql │ ├── 20240211095643_optional_link │ │ └── migration.sql │ ├── 20240818100533_introduce_runtimes │ │ └── migration.sql │ ├── 20240818125041_optional_date │ │ └── migration.sql │ └── migration_lock.toml └── schema.prisma ├── public └── favicon.svg ├── src ├── app │ ├── api │ │ └── trpc │ │ │ └── [trpc] │ │ │ └── route.ts │ ├── history │ │ ├── layout.tsx │ │ └── page.tsx │ ├── layout.tsx │ ├── page.tsx │ └── runtimes │ │ ├── layout.tsx │ │ └── page.tsx ├── components │ ├── Counter.tsx │ ├── Error.tsx │ ├── Footer.tsx │ ├── History.tsx │ ├── List.tsx │ ├── Loading.tsx │ ├── Navbar.tsx │ └── Runtimes.tsx ├── env.js ├── server │ ├── api │ │ ├── root.ts │ │ ├── routers │ │ │ └── frameworks.ts │ │ └── trpc.ts │ ├── db.ts │ ├── db │ │ ├── framework.ts │ │ └── runtime.ts │ ├── service │ │ ├── counter.ts │ │ ├── history.ts │ │ └── runtimes.ts │ └── types.ts ├── styles │ └── globals.css ├── trpc │ ├── query-client.ts │ ├── react.tsx │ └── server.ts └── util │ ├── date.ts │ └── string.ts ├── start-database.sh ├── static └── favicon.svg ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # Since the ".env" file is gitignored, you can use the ".env.example" file to 2 | # build a new ".env" file when you clone the repo. Keep this file up-to-date 3 | # when you add new variables to `.env`. 4 | 5 | # This file will be committed to version control, so make sure not to have any 6 | # secrets in it. If you are cloning this repo, create a copy of this file named 7 | # ".env" and populate it with your secrets. 8 | 9 | # When adding additional environment variables, the schema in "/src/env.js" 10 | # should be updated accordingly. 11 | 12 | # Prisma 13 | # https://www.prisma.io/docs/reference/database-reference/connection-urls#env 14 | DATABASE_URL="" 15 | DIRECT_URL="" 16 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("eslint").Linter.Config} */ 2 | const config = { 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "project": true 6 | }, 7 | "plugins": [ 8 | "@typescript-eslint" 9 | ], 10 | "extends": [ 11 | "next/core-web-vitals", 12 | "plugin:@typescript-eslint/recommended-type-checked", 13 | "plugin:@typescript-eslint/stylistic-type-checked" 14 | ], 15 | "rules": { 16 | "@typescript-eslint/array-type": "off", 17 | "@typescript-eslint/consistent-type-definitions": "off", 18 | "@typescript-eslint/consistent-type-imports": [ 19 | "warn", 20 | { 21 | "prefer": "type-imports", 22 | "fixStyle": "inline-type-imports" 23 | } 24 | ], 25 | "@typescript-eslint/no-unused-vars": [ 26 | "warn", 27 | { 28 | "argsIgnorePattern": "^_" 29 | } 30 | ], 31 | "@typescript-eslint/require-await": "off", 32 | "@typescript-eslint/no-misused-promises": [ 33 | "error", 34 | { 35 | "checksVoidReturn": { 36 | "attributes": false 37 | } 38 | } 39 | ] 40 | } 41 | } 42 | module.exports = config; -------------------------------------------------------------------------------- /.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 | # database 12 | /prisma/db.sqlite 13 | /prisma/db.sqlite-journal 14 | db.sqlite 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | next-env.d.ts 20 | 21 | # production 22 | /build 23 | 24 | # misc 25 | .DS_Store 26 | *.pem 27 | 28 | # debug 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | .pnpm-debug.log* 33 | 34 | # local env files 35 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables 36 | .env 37 | .env*.local 38 | 39 | # vercel 40 | .vercel 41 | 42 | # typescript 43 | *.tsbuildinfo 44 | 45 | # idea files 46 | .idea 47 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Luca Müller 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # days-since-js-framework 2 | 3 | Simple counter for counting days since latest js framework release. Productive version can be found at [here](https://dayssincelastjsframework.com/) 4 | 5 | ## Contributing 6 | 7 | Pull requests are welcome. For major changes, please open an issue first 8 | to discuss what you would like to change. 9 | 10 | ## License 11 | 12 | This project is licensed under the [MIT](./LICENSE) license. 13 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful 3 | * for Docker builds. 4 | */ 5 | await import("./src/env.js"); 6 | 7 | /** @type {import("next").NextConfig} */ 8 | const config = {}; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "days-since-js-framework", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "next build", 8 | "db:generate": "prisma migrate dev", 9 | "db:migrate": "prisma migrate deploy", 10 | "db:push": "prisma db push", 11 | "db:studio": "prisma studio", 12 | "dev": "next dev", 13 | "postinstall": "prisma generate", 14 | "lint": "next lint", 15 | "start": "next start" 16 | }, 17 | "dependencies": { 18 | "@prisma/client": "^5.14.0", 19 | "@t3-oss/env-nextjs": "^0.10.1", 20 | "@tanstack/react-query": "^5.50.0", 21 | "@trpc/client": "^11.0.0-rc.446", 22 | "@trpc/react-query": "^11.0.0-rc.446", 23 | "@trpc/server": "^11.0.0-rc.446", 24 | "geist": "^1.3.0", 25 | "next": "^14.2.4", 26 | "react": "^18.3.1", 27 | "react-dom": "^18.3.1", 28 | "server-only": "^0.0.1", 29 | "superjson": "^2.2.1", 30 | "zod": "^3.23.3" 31 | }, 32 | "devDependencies": { 33 | "@types/eslint": "^8.56.10", 34 | "@types/node": "^20.14.10", 35 | "@types/react": "^18.3.3", 36 | "@types/react-dom": "^18.3.0", 37 | "@typescript-eslint/eslint-plugin": "^7.1.1", 38 | "@typescript-eslint/parser": "^7.1.1", 39 | "eslint": "^8.57.0", 40 | "eslint-config-next": "^14.2.4", 41 | "postcss": "^8.4.39", 42 | "prettier": "^3.3.2", 43 | "prettier-plugin-tailwindcss": "^0.6.5", 44 | "prisma": "^5.14.0", 45 | "tailwindcss": "^3.4.3", 46 | "typescript": "^5.5.3" 47 | }, 48 | "ct3aMetadata": { 49 | "initVersion": "7.36.2" 50 | }, 51 | "packageManager": "npm@10.2.3" 52 | } -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | }, 5 | }; 6 | 7 | module.exports = config; -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */ 2 | const config = { 3 | plugins: ["prettier-plugin-tailwindcss"], 4 | }; 5 | 6 | export default config; 7 | -------------------------------------------------------------------------------- /prisma/migrations/20240211092205_add_type_and_releaselink/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "Type" AS ENUM ('STABLE', 'ALPHA', 'BETA'); 3 | 4 | -- CreateTable 5 | CREATE TABLE "Framework" ( 6 | "id" SERIAL NOT NULL, 7 | "name" TEXT NOT NULL, 8 | "link" TEXT NOT NULL, 9 | "releaseLink" TEXT, 10 | "type" "Type" NOT NULL DEFAULT 'STABLE', 11 | "date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 12 | 13 | CONSTRAINT "Framework_pkey" PRIMARY KEY ("id") 14 | ); 15 | 16 | -- CreateIndex 17 | CREATE UNIQUE INDEX "Framework_name_key" ON "Framework"("name"); 18 | -------------------------------------------------------------------------------- /prisma/migrations/20240211095643_optional_link/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Framework" ALTER COLUMN "link" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /prisma/migrations/20240818100533_introduce_runtimes/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "Runtime" ( 3 | "id" SERIAL NOT NULL, 4 | "name" TEXT NOT NULL, 5 | "link" TEXT, 6 | "releaseLink" TEXT, 7 | "type" "Type" NOT NULL DEFAULT 'STABLE', 8 | "date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | 10 | CONSTRAINT "Runtime_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "Runtime_name_key" ON "Runtime"("name"); 15 | -------------------------------------------------------------------------------- /prisma/migrations/20240818125041_optional_date/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Runtime" ALTER COLUMN "date" DROP NOT NULL, 3 | ALTER COLUMN "date" DROP DEFAULT; 4 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "postgresql" 10 | url = env("DATABASE_URL") 11 | } 12 | 13 | model Framework { 14 | id Int @id @default(autoincrement()) 15 | name String @unique 16 | link String? 17 | releaseLink String? 18 | type Type @default(STABLE) 19 | date DateTime @default(now()) 20 | } 21 | 22 | model Runtime { 23 | id Int @id @default(autoincrement()) 24 | name String @unique 25 | link String? 26 | releaseLink String? 27 | type Type @default(STABLE) 28 | date DateTime? 29 | } 30 | 31 | enum Type { 32 | STABLE 33 | ALPHA 34 | BETA 35 | } 36 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/app/api/trpc/[trpc]/route.ts: -------------------------------------------------------------------------------- 1 | import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; 2 | import { type NextRequest } from "next/server"; 3 | 4 | import { env } from "~/env"; 5 | import { appRouter } from "~/server/api/root"; 6 | import { createTRPCContext } from "~/server/api/trpc"; 7 | 8 | /** 9 | * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when 10 | * handling a HTTP request (e.g. when you make requests from Client Components). 11 | */ 12 | const createContext = async (req: NextRequest) => { 13 | return createTRPCContext({ 14 | headers: req.headers, 15 | }); 16 | }; 17 | 18 | const handler = (req: NextRequest) => 19 | fetchRequestHandler({ 20 | endpoint: "/api/trpc", 21 | req, 22 | router: appRouter, 23 | createContext: () => createContext(req), 24 | onError: 25 | env.NODE_ENV === "development" 26 | ? ({ path, error }) => { 27 | console.error( 28 | `❌ tRPC failed on ${path ?? ""}: ${error.message}` 29 | ); 30 | } 31 | : undefined, 32 | }); 33 | 34 | export { handler as GET, handler as POST }; 35 | -------------------------------------------------------------------------------- /src/app/history/layout.tsx: -------------------------------------------------------------------------------- 1 | import "~/styles/globals.css"; 2 | 3 | import { type Metadata } from "next"; 4 | 5 | export const metadata: Metadata = { 6 | title: "Latest JS framework releases", 7 | description: "History of JavaScript frameworks and their release dates.", 8 | keywords: "javaScript, framework, counter, last, js, days, since, release", 9 | icons: [{ rel: "icon", url: "/favicon.svg" }], 10 | }; 11 | 12 | export default function Layout({ 13 | children, 14 | }: Readonly<{ children: React.ReactNode }>) { 15 | return children; 16 | } 17 | -------------------------------------------------------------------------------- /src/app/history/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import ErrorPage from "~/components/Error"; 4 | import HistoryPage from "~/components/History"; 5 | import Loading from "~/components/Loading"; 6 | 7 | import { api } from "~/trpc/react"; 8 | 9 | export default function Home() { 10 | const getCounterQuery = api.framework.getHistory.useQuery(); 11 | 12 | return ( 13 |
14 | {getCounterQuery.isLoading && } 15 | {getCounterQuery.isError && } 16 | {getCounterQuery.isSuccess && ( 17 | 18 | )} 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "~/styles/globals.css"; 2 | 3 | import { GeistSans } from "geist/font/sans"; 4 | import { type Metadata } from "next"; 5 | 6 | import Footer from "~/components/Footer"; 7 | import Navbar from "~/components/Navbar"; 8 | import { TRPCReactProvider } from "~/trpc/react"; 9 | 10 | export const metadata: Metadata = { 11 | title: "Days since last JS framework", 12 | description: 13 | "Counter for the days since the release of the latest JavaScript framework", 14 | keywords: "javaScript, framework, counter, last, js, days, since, release", 15 | icons: [{ rel: "icon", url: "/favicon.svg" }], 16 | }; 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: Readonly<{ children: React.ReactNode }>) { 21 | return ( 22 | 23 | 24 | 25 | 26 |
{children}
27 |