├── common ├── .npmignore ├── .gitignore ├── package.json ├── package-lock.json ├── src │ └── index.ts ├── dist │ ├── index.js │ └── index.d.ts └── tsconfig.json ├── frontend ├── src │ ├── App.css │ ├── vite-env.d.ts │ ├── index.css │ ├── atoms │ │ └── auth.ts │ ├── components │ │ ├── Logo.tsx │ │ ├── Heading.tsx │ │ ├── SubHeading.tsx │ │ ├── Button.tsx │ │ ├── Quote.tsx │ │ ├── Avatar.tsx │ │ ├── BottomWarning.tsx │ │ ├── Input.tsx │ │ ├── BlogCardSkeleton.tsx │ │ ├── Appbar.tsx │ │ ├── BlogCard.tsx │ │ └── BlogPageSkeleton.tsx │ ├── main.tsx │ ├── App.tsx │ ├── pages │ │ ├── Landing.tsx │ │ ├── Blog.tsx │ │ ├── Publish.tsx │ │ ├── SignIn.tsx │ │ └── SignUp.tsx │ └── hooks │ │ └── index.ts ├── vercel.json ├── public │ └── favicon.ico ├── postcss.config.js ├── vite.config.ts ├── tsconfig.node.json ├── tailwind.config.js ├── .gitignore ├── .eslintrc.cjs ├── tsconfig.json ├── index.html └── package.json └── backend ├── README.md ├── prisma ├── migrations │ ├── migration_lock.toml │ ├── 20240512130139_add_date_to_posts │ │ └── migration.sql │ ├── 20240512141603_change_date_to_string │ │ └── migration.sql │ └── 20240427203926_init_schema │ │ └── migration.sql └── schema.prisma ├── .gitignore ├── tsconfig.json ├── wrangler.toml ├── src └── index.ts ├── package.json └── routes ├── user.ts └── blog.ts /common/.npmignore: -------------------------------------------------------------------------------- 1 | src -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /frontend/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{ "source": "/(.*)", "destination": "/" }] 3 | } 4 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm run dev 4 | ``` 5 | 6 | ``` 7 | npm run deploy 8 | ``` 9 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzalahwaheed/LearnWithHanzo/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /backend/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" -------------------------------------------------------------------------------- /backend/prisma/migrations/20240512130139_add_date_to_posts/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Post" ADD COLUMN "publishedDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 3 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .wrangler 4 | .dev.vars 5 | 6 | # Change them to your taste: 7 | package-lock.json 8 | yarn.lock 9 | pnpm-lock.yaml 10 | bun.lockb 11 | 12 | .env 13 | -------------------------------------------------------------------------------- /backend/prisma/migrations/20240512141603_change_date_to_string/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "Post" ALTER COLUMN "publishedDate" DROP DEFAULT, 3 | ALTER COLUMN "publishedDate" SET DATA TYPE TEXT; 4 | -------------------------------------------------------------------------------- /frontend/src/atoms/auth.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "recoil"; 2 | 3 | export const authState = atom({ 4 | key: "authState", 5 | default: { 6 | isAuth: false, 7 | userName: "", 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /frontend/src/components/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | const Logo = () => { 4 | return ( 5 |
6 | h2wh 7 |
8 | ); 9 | }; 10 | 11 | export default Logo; 12 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/components/Heading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface HeadingProps { 4 | content: string; 5 | } 6 | 7 | const Heading: React.FC = ({ content }) => { 8 | return

{content}

; 9 | }; 10 | 11 | export default Heading; 12 | -------------------------------------------------------------------------------- /frontend/src/components/SubHeading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface SubHeadingProps { 4 | content: string; 5 | } 6 | 7 | const SubHeading: React.FC = ({ content }) => { 8 | return
{content}
; 9 | }; 10 | 11 | export default SubHeading; 12 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | export default { 4 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 5 | theme: { 6 | extend: { 7 | fontFamily: { 8 | ubunutSansMono: ["Ubuntu Sans Mono", "sans-serif"] 9 | } 10 | }, 11 | }, 12 | plugins: [], 13 | }; 14 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "lib": [ 8 | "ESNext" 9 | ], 10 | "types": [ 11 | "@cloudflare/workers-types" 12 | ], 13 | "jsx": "react-jsx", 14 | "jsxImportSource": "hono/jsx" 15 | }, 16 | } -------------------------------------------------------------------------------- /common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hanzalahwaheed/h2wh-common", 3 | "version": "1.0.2", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "zod": "^3.23.6" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .env -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | import "./index.css"; 5 | import { RecoilRoot } from "recoil"; 6 | 7 | ReactDOM.createRoot(document.getElementById("root")!).render( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /backend/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "backend" 2 | compatibility_date = "2023-12-01" 3 | 4 | [vars] 5 | DATABASE_URL="prisma://accelerate.prisma-data.net/?api_key=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcGlfa2V5IjoiMGJjY2I1NTAtYjVhMy00Yjc4LWFhNDEtYzhkMjkyNzRhNTQyIiwidGVuYW50X2lkIjoiNGRjMGU5YzE5MGVkNGM1NTY5ZWFmYjg0YzQwZTBmMGM4ZWRjMDM5ZTFhNzdmMTExMDc1ZDVkYjg0ZmYwYjRjOSIsImludGVybmFsX3NlY3JldCI6IjliOWVmZGYwLWE0M2EtNGY5Zi1hYWNmLTZlODU0YWVlNDJiMyJ9.0TLgf-OYLIl2J-KkI85kjoeT3XJjSFfnUrvemfWN1u4" 6 | JWT_SECRET="jwt_secret" -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { userRouter } from "../routes/user"; 3 | import { blogRouter } from "../routes/blog"; 4 | import { cors } from "hono/cors"; 5 | 6 | const app = new Hono<{ 7 | Bindings: { 8 | DATABASE_URL: string; 9 | JWT_SECRET: string; 10 | }; 11 | }>(); 12 | 13 | app.get("/", (c) => { 14 | return c.text("Hello Hono!"); 15 | }); 16 | 17 | app.use("/*", cors()); 18 | app.route("/api/v1/user", userRouter); 19 | app.route("/api/v1/blog", blogRouter); 20 | 21 | export default app; 22 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "wrangler dev src/index.ts", 4 | "deploy": "wrangler deploy --minify src/index.ts" 5 | }, 6 | "dependencies": { 7 | "@hanzalahwaheed/h2wh-common": "^1.0.2", 8 | "@prisma/client": "^5.13.0", 9 | "@prisma/extension-accelerate": "^1.0.0", 10 | "hono": "^4.2.8", 11 | "i": "^0.3.7", 12 | "npm": "^10.7.0", 13 | "prisma": "^5.13.0" 14 | }, 15 | "devDependencies": { 16 | "@cloudflare/workers-types": "^4.20240403.0", 17 | "wrangler": "^4.18.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface ButtonProps { 4 | content: string; 5 | onClick: React.MouseEventHandler; 6 | } 7 | 8 | const Button: React.FC = ({ content, onClick }) => { 9 | return ( 10 | 17 | ); 18 | }; 19 | 20 | export default Button; 21 | -------------------------------------------------------------------------------- /frontend/src/components/Quote.tsx: -------------------------------------------------------------------------------- 1 | import Heading from "./Heading"; 2 | import SubHeading from "./SubHeading"; 3 | 4 | const Quote = () => { 5 | const headingContent = '"To TEACH is to learn TWICE"'; 6 | const subHeadingContent = "-Hanzalah Waheed"; 7 | return ( 8 |
9 |
10 | 11 | 12 |
13 |
14 | ); 15 | }; 16 | 17 | export default Quote; 18 | -------------------------------------------------------------------------------- /frontend/src/components/Avatar.tsx: -------------------------------------------------------------------------------- 1 | const Avatar = ({ content, size }: { content: string; size: string }) => { 2 | return ( 3 |
9 | 14 | {content[0].toUpperCase()} 15 | 16 |
17 | ); 18 | }; 19 | 20 | export default Avatar; 21 | -------------------------------------------------------------------------------- /frontend/src/components/BottomWarning.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import React from "react"; 3 | 4 | interface BottomWaringProps { 5 | content: string; 6 | buttonText: string; 7 | to: string; 8 | } 9 | 10 | const BottomWarning: React.FC = ({ 11 | content, 12 | buttonText, 13 | to, 14 | }) => { 15 | return ( 16 |
17 |
{content}
18 | 22 | {buttonText} 23 | 24 |
25 | ); 26 | }; 27 | 28 | export default BottomWarning; 29 | -------------------------------------------------------------------------------- /common/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "common", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "zod": "^3.23.6" 13 | } 14 | }, 15 | "node_modules/zod": { 16 | "version": "3.23.6", 17 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.6.tgz", 18 | "integrity": "sha512-RTHJlZhsRbuA8Hmp/iNL7jnfc4nZishjsanDAfEY1QpDQZCahUp3xDzl+zfweE9BklxMUcgBgS1b7Lvie/ZVwA==", 19 | "funding": { 20 | "url": "https://github.com/sponsors/colinhacks" 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LearnWithHanzo 7 | 8 | 9 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /common/src/index.ts: -------------------------------------------------------------------------------- 1 | import z from "zod"; 2 | 3 | export const signUpInput = z.object({ 4 | email: z.string(), 5 | password: z.string().min(4), 6 | name: z.string().optional(), 7 | }); 8 | 9 | export const signInInput = z.object({ 10 | email: z.string(), 11 | password: z.string().min(4), 12 | }); 13 | 14 | export const createBlogInput = z.object({ 15 | title: z.string(), 16 | content: z.string(), 17 | }); 18 | 19 | export const updateBlogInput = z.object({ 20 | title: z.string(), 21 | content: z.string(), 22 | id: z.string(), 23 | }); 24 | 25 | export type SignUpInput = z.infer; 26 | export type SignInInput = z.infer; 27 | export type CreateBlogInput = z.infer; 28 | export type UpdateBlogInput = z.infer; 29 | -------------------------------------------------------------------------------- /backend/prisma/migrations/20240427203926_init_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "User" ( 3 | "id" TEXT NOT NULL, 4 | "email" TEXT NOT NULL, 5 | "name" TEXT, 6 | "password" TEXT NOT NULL, 7 | 8 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateTable 12 | CREATE TABLE "Post" ( 13 | "id" TEXT NOT NULL, 14 | "title" TEXT NOT NULL, 15 | "content" TEXT NOT NULL, 16 | "published" BOOLEAN NOT NULL DEFAULT false, 17 | "authorId" TEXT NOT NULL, 18 | 19 | CONSTRAINT "Post_pkey" PRIMARY KEY ("id") 20 | ); 21 | 22 | -- CreateIndex 23 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 24 | 25 | -- AddForeignKey 26 | ALTER TABLE "Post" ADD CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 27 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 2 | import "./App.css"; 3 | import SignIn from "./pages/SignIn"; 4 | import SignUp from "./pages/SignUp"; 5 | import Blog from "./pages/Blog"; 6 | import Landing from "./pages/Landing"; 7 | import Publish from "./pages/Publish"; 8 | 9 | function App() { 10 | return ( 11 |
12 | 13 | 14 | } /> 15 | } /> 16 | } /> 17 | } /> 18 | } /> 19 | 20 | 21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /frontend/src/components/Input.tsx: -------------------------------------------------------------------------------- 1 | import React, { ChangeEvent } from "react"; 2 | 3 | interface InputProps { 4 | type: string; 5 | placeholder: string; 6 | label: string; 7 | name: string; 8 | onChange: (e: ChangeEvent) => void; 9 | value: string | undefined; 10 | } 11 | 12 | const Input: React.FC = ({ 13 | type, 14 | placeholder, 15 | label, 16 | name, 17 | onChange, 18 | value, 19 | }) => { 20 | return ( 21 |
22 | 23 | 31 |
32 | ); 33 | }; 34 | 35 | export default Input; 36 | -------------------------------------------------------------------------------- /backend/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 | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? 5 | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init 6 | 7 | generator client { 8 | provider = "prisma-client-js" 9 | } 10 | 11 | datasource db { 12 | provider = "postgresql" 13 | url = env("DATABASE_URL") 14 | } 15 | 16 | model User { 17 | id String @id @default(uuid()) 18 | email String @unique 19 | name String? 20 | password String 21 | posts Post[] 22 | } 23 | 24 | model Post { 25 | id String @id @default(uuid()) 26 | title String 27 | content String 28 | published Boolean @default(false) 29 | author User @relation(fields: [authorId], references: [id]) 30 | authorId String 31 | publishedDate String 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/components/BlogCardSkeleton.tsx: -------------------------------------------------------------------------------- 1 | const BlogCardSkeleton = () => { 2 | return ( 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ); 18 | }; 19 | 20 | export default BlogCardSkeleton; 21 | -------------------------------------------------------------------------------- /common/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.updateBlogInput = exports.createBlogInput = exports.signInInput = exports.signUpInput = void 0; 7 | const zod_1 = __importDefault(require("zod")); 8 | exports.signUpInput = zod_1.default.object({ 9 | email: zod_1.default.string(), 10 | password: zod_1.default.string().min(4), 11 | name: zod_1.default.string().optional(), 12 | }); 13 | exports.signInInput = zod_1.default.object({ 14 | email: zod_1.default.string(), 15 | password: zod_1.default.string().min(4), 16 | }); 17 | exports.createBlogInput = zod_1.default.object({ 18 | title: zod_1.default.string(), 19 | content: zod_1.default.string(), 20 | }); 21 | exports.updateBlogInput = zod_1.default.object({ 22 | title: zod_1.default.string(), 23 | content: zod_1.default.string(), 24 | id: zod_1.default.string(), 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/src/components/Appbar.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | import Avatar from "./Avatar"; 3 | import Logo from "./Logo"; 4 | import { useAuth } from "../hooks"; 5 | import Button from "./Button"; 6 | 7 | const Appbar = () => { 8 | const navigate = useNavigate(); 9 | const handleClick = () => { 10 | navigate("/publish"); 11 | }; 12 | 13 | const { isAuth, userName } = useAuth(); 14 | 15 | return ( 16 | 32 | ); 33 | }; 34 | 35 | export default Appbar; 36 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@hanzalahwaheed/h2wh-common": "^1.0.2", 14 | "axios": "^1.6.8", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-router-dom": "^6.23.0", 18 | "recoil": "^0.7.7" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.2.66", 22 | "@types/react-dom": "^18.2.22", 23 | "@typescript-eslint/eslint-plugin": "^7.2.0", 24 | "@typescript-eslint/parser": "^7.2.0", 25 | "@vitejs/plugin-react": "^4.2.1", 26 | "autoprefixer": "^10.4.19", 27 | "eslint": "^8.57.0", 28 | "eslint-plugin-react-hooks": "^4.6.0", 29 | "eslint-plugin-react-refresh": "^0.4.6", 30 | "postcss": "^8.4.38", 31 | "tailwindcss": "^3.4.3", 32 | "typescript": "^5.2.2", 33 | "vite": "^5.2.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/pages/Landing.tsx: -------------------------------------------------------------------------------- 1 | import Appbar from "../components/Appbar"; 2 | import BlogCard from "../components/BlogCard"; 3 | import BlogCardSkeleton from "../components/BlogCardSkeleton"; 4 | import { useBlogs } from "../hooks"; 5 | 6 | const Landing = () => { 7 | const { loading, blogs } = useBlogs(); 8 | if (loading) 9 | return ( 10 | <> 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | ); 21 | return ( 22 | <> 23 | 24 |
25 | {blogs.map((blog) => ( 26 | 33 | ))} 34 |
35 | 36 | ); 37 | }; 38 | 39 | export default Landing; 40 | -------------------------------------------------------------------------------- /frontend/src/pages/Blog.tsx: -------------------------------------------------------------------------------- 1 | import { useBlog } from "../hooks"; 2 | import { useParams } from "react-router-dom"; 3 | import Appbar from "../components/Appbar"; 4 | import BlogPageSkeleton from "../components/BlogPageSkeleton"; 5 | 6 | const Blog = () => { 7 | const { id } = useParams(); 8 | const { loading, blog } = useBlog({ id: id || "" }); 9 | 10 | if (loading) 11 | return ( 12 | <> 13 | 14 | 15 | 16 | ); 17 | 18 | return ( 19 | <> 20 | 21 |
22 |
23 |

{blog?.title}

24 |

Published Date: {blog?.publishedDate}

25 |
26 |

{blog?.content}

27 |
28 |
29 |
30 |

Author

31 |

{blog?.author.name}

32 |
33 |
34 | 35 | ); 36 | }; 37 | 38 | export default Blog; 39 | -------------------------------------------------------------------------------- /frontend/src/components/BlogCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Avatar from "./Avatar"; 3 | import { Link } from "react-router-dom"; 4 | 5 | interface BlogCardProps { 6 | id: string; 7 | authorName: string; 8 | title: string; 9 | content: string; 10 | publishedDate: string; 11 | } 12 | 13 | const BlogCard: React.FC = ({ 14 | id, 15 | authorName, 16 | title, 17 | content, 18 | publishedDate, 19 | }) => { 20 | return ( 21 |
22 |
23 | 24 |

{authorName}

●{" "} 25 |

{publishedDate}

26 |
27 |
28 |

29 | {title} 30 |

31 |

32 | {content.length >= 100 ? content.slice(0, 200) + "..." : content} 33 |

34 |

35 | {Math.ceil(content.length / 100)} minute read 36 |

37 |
38 |
39 | ); 40 | }; 41 | 42 | export default BlogCard; 43 | -------------------------------------------------------------------------------- /frontend/src/components/BlogPageSkeleton.tsx: -------------------------------------------------------------------------------- 1 | const BlogPageSkeleton = () => { 2 | return ( 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ); 25 | }; 26 | 27 | export default BlogPageSkeleton; 28 | -------------------------------------------------------------------------------- /common/dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import z from "zod"; 2 | export declare const signUpInput: z.ZodObject<{ 3 | email: z.ZodString; 4 | password: z.ZodString; 5 | name: z.ZodOptional; 6 | }, "strip", z.ZodTypeAny, { 7 | email: string; 8 | password: string; 9 | name?: string | undefined; 10 | }, { 11 | email: string; 12 | password: string; 13 | name?: string | undefined; 14 | }>; 15 | export declare const signInInput: z.ZodObject<{ 16 | email: z.ZodString; 17 | password: z.ZodString; 18 | }, "strip", z.ZodTypeAny, { 19 | email: string; 20 | password: string; 21 | }, { 22 | email: string; 23 | password: string; 24 | }>; 25 | export declare const createBlogInput: z.ZodObject<{ 26 | title: z.ZodString; 27 | content: z.ZodString; 28 | }, "strip", z.ZodTypeAny, { 29 | title: string; 30 | content: string; 31 | }, { 32 | title: string; 33 | content: string; 34 | }>; 35 | export declare const updateBlogInput: z.ZodObject<{ 36 | title: z.ZodString; 37 | content: z.ZodString; 38 | id: z.ZodString; 39 | }, "strip", z.ZodTypeAny, { 40 | title: string; 41 | content: string; 42 | id: string; 43 | }, { 44 | title: string; 45 | content: string; 46 | id: string; 47 | }>; 48 | export type SignUpInput = z.infer; 49 | export type SignInInput = z.infer; 50 | export type CreateBlogInput = z.infer; 51 | export type UpdateBlogInput = z.infer; 52 | -------------------------------------------------------------------------------- /frontend/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import axios from "axios"; 3 | import { useRecoilState } from "recoil"; 4 | import { authState } from "../atoms/auth"; 5 | 6 | const API_LINK_ALL_BLOGS = `${import.meta.env.VITE_BASE_URL}/api/v1/blog/bulk`; 7 | const API_LINK_AUTH = `${import.meta.env.VITE_BASE_URL}/api/v1/user/me`; 8 | 9 | interface Blog { 10 | id: string; 11 | title: string; 12 | content: string; 13 | author: { 14 | name: string; 15 | }; 16 | publishedDate: string; 17 | } 18 | 19 | export const useBlogs = () => { 20 | const [loading, setLoading] = useState(true); 21 | const [blogs, setBlogs] = useState([]); 22 | 23 | useEffect(() => { 24 | axios.get(API_LINK_ALL_BLOGS).then((response) => { 25 | const reversedBlogs = response.data.reverse(); 26 | setBlogs(reversedBlogs); 27 | setLoading(false); 28 | }); 29 | }, []); 30 | return { loading, blogs }; 31 | }; 32 | 33 | export const useBlog = ({ id }: { id: string }) => { 34 | const [loading, setLoading] = useState(true); 35 | const [blog, setBlog] = useState(); 36 | 37 | useEffect(() => { 38 | axios 39 | .get( 40 | `${import.meta.env.VITE_BASE_URL}/api/v1/blog/${id}` 41 | // ,{ 42 | // headers: { 43 | // Authorization: localStorage.getItem("token"), 44 | // }, 45 | // } 46 | ) 47 | .then((response) => { 48 | setBlog(response.data); 49 | setLoading(false); 50 | }); 51 | }, [id]); 52 | return { loading, blog }; 53 | }; 54 | 55 | export const useAuth = () => { 56 | const [auth, setAuth] = useRecoilState(authState); 57 | 58 | useEffect(() => { 59 | const token = localStorage.getItem("token"); 60 | if (token && !auth.isAuth) { 61 | axios 62 | .get(API_LINK_AUTH, { 63 | headers: { 64 | Authorization: token, 65 | }, 66 | }) 67 | .then((response) => { 68 | if (response.data.status) { 69 | setAuth({ 70 | isAuth: true, 71 | userName: response.data.user.name, 72 | }); 73 | } 74 | }) 75 | .catch((error) => { 76 | console.error("Authentication error:", error); 77 | }); 78 | } 79 | }, [auth.isAuth, setAuth]); 80 | 81 | return auth; 82 | }; 83 | -------------------------------------------------------------------------------- /frontend/src/pages/Publish.tsx: -------------------------------------------------------------------------------- 1 | import { useState, ChangeEvent } from "react"; 2 | import axios from "axios"; 3 | import Appbar from "../components/Appbar"; 4 | import Button from "../components/Button"; 5 | import { CreateBlogInput } from "@hanzalahwaheed/h2wh-common"; 6 | import { useNavigate } from "react-router-dom"; 7 | 8 | const Publish = () => { 9 | const navigate = useNavigate(); 10 | 11 | const [postInputs, setPostInputs] = useState({ 12 | title: "", 13 | content: "", 14 | }); 15 | 16 | const handleInputChange = ( 17 | e: ChangeEvent 18 | ) => { 19 | const { name, value } = e.target; 20 | setPostInputs({ 21 | ...postInputs, 22 | [name]: value, 23 | }); 24 | }; 25 | 26 | const handlePublish = async () => { 27 | try { 28 | const response = await axios.post( 29 | `${import.meta.env.VITE_BASE_URL}/api/v1/blog`, 30 | postInputs, 31 | { 32 | headers: { Authorization: localStorage.getItem("token") }, 33 | } 34 | ); 35 | navigate(`/blog/${response.data}`); 36 | } catch (error) { 37 | console.error("Error publishing blog post:", error); 38 | } 39 | }; 40 | 41 | return ( 42 | <> 43 | 44 |
45 |
46 |
47 | 55 |
56 | 64 |
66 |
67 | 68 | ); 69 | }; 70 | 71 | export default Publish; 72 | -------------------------------------------------------------------------------- /frontend/src/pages/SignIn.tsx: -------------------------------------------------------------------------------- 1 | import { useState, ChangeEvent } from "react"; 2 | import BottomWarning from "../components/BottomWarning"; 3 | import Heading from "../components/Heading"; 4 | import Input from "../components/Input"; 5 | import Quote from "../components/Quote"; 6 | import SubHeading from "../components/SubHeading"; 7 | import { SignInInput } from "@hanzalahwaheed/h2wh-common"; 8 | import { useNavigate } from "react-router-dom"; 9 | import Button from "../components/Button"; 10 | import axios from "axios"; 11 | 12 | const SignIn = () => { 13 | const [postInputs, setPostInputs] = useState({ 14 | email: "", 15 | password: "", 16 | }); 17 | const navigate = useNavigate(); 18 | 19 | const handleInputChange = (e: ChangeEvent) => { 20 | const { name, value } = e.target; 21 | setPostInputs({ 22 | ...postInputs, 23 | [name]: value, 24 | }); 25 | }; 26 | 27 | const handleClick = async () => { 28 | try { 29 | const response = await axios.post( 30 | `${import.meta.env.VITE_BASE_URL}/api/v1/user/signin`, 31 | postInputs 32 | ); 33 | const token = response.data.token; 34 | localStorage.setItem("token", token); 35 | if (response) navigate("/"); 36 | } catch (error) { 37 | console.error(error); 38 | } 39 | }; 40 | 41 | return ( 42 |
43 |
44 |
45 | 46 |
47 |
48 | 49 | 57 | 65 |
66 |
67 |
74 |
75 | 76 |
77 | ); 78 | }; 79 | 80 | export default SignIn; 81 | -------------------------------------------------------------------------------- /frontend/src/pages/SignUp.tsx: -------------------------------------------------------------------------------- 1 | import { useState, ChangeEvent } from "react"; 2 | import BottomWarning from "../components/BottomWarning"; 3 | import Heading from "../components/Heading"; 4 | import Input from "../components/Input"; 5 | import Quote from "../components/Quote"; 6 | import SubHeading from "../components/SubHeading"; 7 | import axios from "axios"; 8 | import { useNavigate } from "react-router-dom"; 9 | import Button from "../components/Button"; 10 | import { SignUpInput } from "@hanzalahwaheed/h2wh-common"; 11 | 12 | const SignUp = () => { 13 | const [postInputs, setPostInputs] = useState({ 14 | name: "", 15 | email: "", 16 | password: "", 17 | }); 18 | 19 | const navigate = useNavigate(); 20 | 21 | const handleInputChange = (e: ChangeEvent) => { 22 | const { name, value } = e.target; 23 | setPostInputs({ 24 | ...postInputs, 25 | [name]: value, 26 | }); 27 | }; 28 | 29 | const handleClick = async () => { 30 | try { 31 | const response = await axios.post( 32 | `${import.meta.env.VITE_BASE_URL}/api/v1/user/signup`, 33 | postInputs 34 | ); 35 | const token = response.data; 36 | localStorage.setItem("token", token); 37 | if (response) navigate("/signin"); 38 | } catch (error) { 39 | console.error(error); 40 | } 41 | }; 42 | 43 | return ( 44 |
45 |
46 |
47 | 48 |
49 |
50 | 51 | 59 | 67 | 75 |
76 |
77 |
84 |
85 | 86 |
87 | ); 88 | }; 89 | 90 | export default SignUp; 91 | -------------------------------------------------------------------------------- /backend/routes/user.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client/edge"; 2 | import { withAccelerate } from "@prisma/extension-accelerate"; 3 | import { Hono } from "hono"; 4 | import { sign } from "hono/jwt"; 5 | import { signUpInput, signInInput } from "@hanzalahwaheed/h2wh-common"; 6 | import { verify } from "hono/jwt"; 7 | import { JWTPayload } from "hono/utils/jwt/types"; 8 | 9 | const userRouter = new Hono<{ 10 | Bindings: { 11 | DATABASE_URL: string; 12 | JWT_SECRET: string; 13 | }; 14 | Variables: { 15 | user: { id: string; name: string } | JWTPayload; 16 | }; 17 | }>(); 18 | 19 | userRouter.get("/me", async (c) => { 20 | try { 21 | const token = c.req.header("authorization") || ""; 22 | if (!token) return c.json({ status: false, message: "unauthorized" }); 23 | const user = await verify(token, c.env.JWT_SECRET); 24 | if (!user) return c.json({ status: false, message: "unauthorized" }); 25 | c.set("user", user); 26 | return c.json({ status: true, user: user }); 27 | } catch (error) { 28 | return c.json({ error: error }); 29 | } 30 | }); 31 | 32 | userRouter.post("/signup", async (c) => { 33 | const prisma = new PrismaClient({ 34 | datasourceUrl: c.env.DATABASE_URL, 35 | }).$extends(withAccelerate()); 36 | 37 | try { 38 | const body = await c.req.json(); 39 | const { success } = signUpInput.safeParse(body); 40 | if (!success) { 41 | c.status(411); 42 | return c.json({ message: "Imputs not correct" }); 43 | } 44 | const { email, name, password } = body; 45 | const user = await prisma.user.findUnique({ 46 | where: { 47 | email: email, 48 | }, 49 | }); 50 | if (user) { 51 | c.status(403); 52 | return c.json("User already exists"); 53 | } 54 | const newUser = await prisma.user.create({ 55 | data: { 56 | email, 57 | password, 58 | name, 59 | }, 60 | }); 61 | const token = await sign( 62 | { id: newUser.id, name: newUser.name }, 63 | c.env.JWT_SECRET 64 | ); 65 | return c.json({ token }); 66 | } catch (error) { 67 | return c.json({ error }); 68 | } 69 | }); 70 | 71 | userRouter.post("/signin", async (c) => { 72 | const prisma = new PrismaClient({ 73 | datasourceUrl: c.env.DATABASE_URL, 74 | }).$extends(withAccelerate()); 75 | 76 | const body = await c.req.json(); 77 | const { success } = signInInput.safeParse(body); 78 | if (!success) { 79 | c.status(411); 80 | return c.json({ message: "Imputs not correct" }); 81 | } 82 | const { email, password } = body; 83 | 84 | const user = await prisma.user.findUnique({ 85 | where: { 86 | email: email, 87 | password: password, 88 | }, 89 | }); 90 | 91 | if (!user) { 92 | c.status(403); 93 | return c.json({ error: "Invalid Username or Password" }); 94 | } 95 | 96 | const token = await sign({ id: user.id, name: user.name }, c.env.JWT_SECRET); 97 | return c.json({ token }); 98 | }); 99 | 100 | export { userRouter }; 101 | -------------------------------------------------------------------------------- /backend/routes/blog.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client/edge"; 2 | import { withAccelerate } from "@prisma/extension-accelerate"; 3 | import { Hono } from "hono"; 4 | import { verify } from "hono/jwt"; 5 | import { createBlogInput, updateBlogInput } from "@hanzalahwaheed/h2wh-common"; 6 | import { JWTPayload } from "hono/utils/jwt/types"; 7 | 8 | const blogRouter = new Hono<{ 9 | Bindings: { 10 | DATABASE_URL: string; 11 | JWT_SECRET: string; 12 | }; 13 | Variables: { 14 | user: { id: string; name: string } | JWTPayload; 15 | }; 16 | }>(); 17 | 18 | // get all posts 19 | /* 20 | Note that /bul is declared above the middleware to avoid it getting authorised on fetching all blogs 21 | */ 22 | blogRouter.get("/bulk", async (c) => { 23 | const prisma = new PrismaClient({ 24 | datasourceUrl: c.env?.DATABASE_URL, 25 | }).$extends(withAccelerate()); 26 | try { 27 | // implement pagination (future scope) 28 | const posts = await prisma.post.findMany({ 29 | select: { 30 | content: true, 31 | title: true, 32 | id: true, 33 | publishedDate: true, 34 | author: { 35 | select: { 36 | name: true, 37 | }, 38 | }, 39 | }, 40 | }); 41 | return c.json(posts); 42 | } catch (error) { 43 | c.status(500); 44 | return c.json({ error }); 45 | } 46 | }); 47 | 48 | /* 49 | Note that getAllPosts endpoint is declared before getSinglePost endpoint to prevent overlap of /:id as /bulk 50 | */ 51 | 52 | // get single post 53 | blogRouter.get("/:id", async (c) => { 54 | const id = c.req.param("id"); 55 | const prisma = new PrismaClient({ 56 | datasourceUrl: c.env?.DATABASE_URL, 57 | }).$extends(withAccelerate()); 58 | 59 | try { 60 | const post = await prisma.post.findFirst({ 61 | where: { id }, 62 | select: { 63 | id: true, 64 | title: true, 65 | content: true, 66 | publishedDate: true, 67 | author: { 68 | select: { 69 | name: true, 70 | }, 71 | }, 72 | }, 73 | }); 74 | return c.json(post); 75 | } catch (error) { 76 | c.status(500); 77 | return c.json({ error }); 78 | } 79 | }); 80 | 81 | // auth middleware 82 | blogRouter.use("/*", async (c, next) => { 83 | try { 84 | const token = c.req.header("authorization") || ""; 85 | const user = await verify(token, c.env.JWT_SECRET); 86 | c.set("user", user); 87 | await next(); 88 | } catch (error) { 89 | c.status(403); 90 | return c.json({ message: "Unauthorized", error: error }); 91 | } 92 | }); 93 | 94 | // new post 95 | blogRouter.post("/", async (c) => { 96 | const body = await c.req.json(); 97 | const { success } = createBlogInput.safeParse(body); 98 | if (!success) { 99 | c.status(411); 100 | return c.json({ 101 | message: "Invalid Inputs", 102 | }); 103 | } 104 | const prisma = new PrismaClient({ 105 | datasourceUrl: c.env?.DATABASE_URL, 106 | }).$extends(withAccelerate()); 107 | 108 | const user = c.get("user"); 109 | const context_userId = user.id; 110 | const currentDate = new Date(); 111 | const year = currentDate.getFullYear(); 112 | const month = currentDate.getMonth() + 1; // Months are zero-based, so add 1 113 | const day = currentDate.getDate(); 114 | 115 | const publishedDate = `${year}-${month < 10 ? "0" + month : month}-${ 116 | day < 10 ? "0" + day : day 117 | }`; 118 | 119 | const post = await prisma.post.create({ 120 | data: { 121 | title: body.title, 122 | content: body.content, 123 | authorId: context_userId, 124 | publishedDate: publishedDate, 125 | }, 126 | }); 127 | 128 | return c.json(post.id); 129 | }); 130 | 131 | // update post 132 | blogRouter.put("/", async (c) => { 133 | const body = await c.req.json(); 134 | const { success } = updateBlogInput.safeParse(body); 135 | if (!success) { 136 | c.status(411); 137 | return c.json({ 138 | message: "Invalid Inputs", 139 | }); 140 | } 141 | const prisma = new PrismaClient({ 142 | datasourceUrl: c.env?.DATABASE_URL, 143 | }).$extends(withAccelerate()); 144 | const post = await prisma.post.update({ 145 | where: { id: body.id }, 146 | data: { 147 | title: body.title ?? undefined, 148 | content: body.content ?? undefined, 149 | }, 150 | }); 151 | return c.json(post.id); 152 | }); 153 | 154 | export { blogRouter }; 155 | -------------------------------------------------------------------------------- /common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | "rootDir": "./src", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | --------------------------------------------------------------------------------