├── 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 |
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 |
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 |
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 |
65 |
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 |
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 |
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 |
--------------------------------------------------------------------------------