diff --git a/.eslintrc.json b/.eslintrc.json index bffb357..e4d7c83 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,12 @@ { - "extends": "next/core-web-vitals" + "extends": ["next/core-web-vitals", "prettier", "plugin:prettier/recommended"], + "rules": { + "prettier/prettier": [ + "error", + { + "trailingComma": "es5", + "singleQuote": true + } + ] + } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..68b2eb0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,23 @@ +name: CI test + +on: + # Triggers the workflow on push or pull request events but only for the dev branch + - pull_request + - push + # Allows you to run this workflow manually from the Actions tab + - workflow_dispatch + +jobs: + install-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'npm' + - run: npm ci + # Running build would lint and check type + - run: npm run build + env: + COFACTS_API_URL: 'https://dev-api.cofacts.tw/graphql' diff --git a/README.md b/README.md index e46bf47..2dc6355 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Cofacts Dashboard +[![CI test](https://github.com/cofacts/dashboard/actions/workflows/ci.yml/badge.svg)](https://github.com/cofacts/dashboard/actions/workflows/ci.yml) + Visualize the data of Cofacts fact-checking platform. ## Getting Started @@ -23,3 +25,7 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the The main branch of this repository is automatically deployed to `https://dash.cofacts.tw`. +## Other scripts +- `npm run lint`: Run eslint (includes prettier). +- `npm run codegen`: Generates types for graphql queries. Useful when Typescript complains about.`TypedDocumentNode` for new or updated GraphQL queries. +- `npm run build` and `npm start`: Generate production build and start, respectively. diff --git a/app/layout.tsx b/app/layout.tsx index 3314e47..28da9d3 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,12 +1,12 @@ -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; -import "./globals.css"; +import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; +import './globals.css'; -const inter = Inter({ subsets: ["latin"] }); +const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: 'Create Next App', + description: 'Generated by create next app', }; export default function RootLayout({ diff --git a/app/page.tsx b/app/page.tsx index c84b8ad..d2b6afd 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,23 +2,25 @@ import { request } from 'graphql-request'; import { graphql } from '@/typegen/gql'; async function getData() { - return request(process.env.COFACTS_API_URL ?? '', graphql(/* GraphQL */ ` - query LoadAPIStats { - allArticles: ListArticles { - totalCount + return request( + process.env.COFACTS_API_URL ?? '', + graphql(/* GraphQL */ ` + query LoadAPIStats { + allArticles: ListArticles { + totalCount + } + allRepliedArticles: ListArticles { + totalCount + } + articlesHasUsefulReplies: ListArticles { + totalCount + } } - allRepliedArticles: ListArticles { - totalCount - } - articlesHasUsefulReplies: ListArticles { - totalCount - } - } - `)); + `) + ); } export default async function Home() { - const resp = await getData(); return ( diff --git a/codegen.ts b/codegen.ts index 40ad82b..88ae5af 100644 --- a/codegen.ts +++ b/codegen.ts @@ -5,10 +5,10 @@ import type { CodegenConfig } from '@graphql-codegen/cli'; const config: CodegenConfig = { overwrite: true, schema: process.env.COFACTS_API_URL, - documents: ["app/**/*.tsx"], + documents: ['app/**/*.tsx'], generates: { 'typegen/': { - preset: "client", + preset: 'client', plugins: [], presetConfig: { fragmentMasking: false, @@ -19,8 +19,8 @@ const config: CodegenConfig = { skipTypename: true, avoidOptionals: true, }, - } - } + }, + }, }; export default config; diff --git a/package-lock.json b/package-lock.json index 59647a9..148b0f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,10 @@ "autoprefixer": "^10.0.1", "eslint": "^8", "eslint-config-next": "14.1.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "postcss": "^8", + "prettier": "3.2.5", "tailwindcss": "^3.3.0", "typescript": "^5.3.3" }, @@ -2653,6 +2656,18 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@repeaterjs/repeater": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.5.tgz", @@ -4449,6 +4464,18 @@ } } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -4611,6 +4638,36 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-react": { "version": "7.33.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", @@ -4816,6 +4873,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -7457,6 +7520,33 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -8373,6 +8463,22 @@ "tslib": "^2.0.3" } }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tailwindcss": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", diff --git a/package.json b/package.json index 51dca0a..e7c717f 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,10 @@ "autoprefixer": "^10.0.1", "eslint": "^8", "eslint-config-next": "14.1.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "postcss": "^8", + "prettier": "3.2.5", "tailwindcss": "^3.3.0", "typescript": "^5.3.3" }, diff --git a/tailwind.config.ts b/tailwind.config.ts index 7e4bd91..2686392 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,17 +1,17 @@ -import type { Config } from "tailwindcss"; +import type { Config } from 'tailwindcss'; const config: Config = { content: [ - "./pages/**/*.{js,ts,jsx,tsx,mdx}", - "./components/**/*.{js,ts,jsx,tsx,mdx}", - "./app/**/*.{js,ts,jsx,tsx,mdx}", + './pages/**/*.{js,ts,jsx,tsx,mdx}', + './components/**/*.{js,ts,jsx,tsx,mdx}', + './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { backgroundImage: { - "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", - "gradient-conic": - "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", + 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', + 'gradient-conic': + 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, }, }, diff --git a/typegen/gql.ts b/typegen/gql.ts index 81c0747..28f4491 100644 --- a/typegen/gql.ts +++ b/typegen/gql.ts @@ -13,7 +13,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { - "\n query LoadAPIStats {\n allArticles: ListArticles {\n totalCount\n }\n allRepliedArticles: ListArticles {\n totalCount\n }\n articlesHasUsefulReplies: ListArticles {\n totalCount\n }\n }\n ": types.LoadApiStatsDocument, + "\n query LoadAPIStats {\n allArticles: ListArticles {\n totalCount\n }\n allRepliedArticles: ListArticles {\n totalCount\n }\n articlesHasUsefulReplies: ListArticles {\n totalCount\n }\n }\n ": types.LoadApiStatsDocument, }; /** @@ -33,7 +33,7 @@ export function graphql(source: string): unknown; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query LoadAPIStats {\n allArticles: ListArticles {\n totalCount\n }\n allRepliedArticles: ListArticles {\n totalCount\n }\n articlesHasUsefulReplies: ListArticles {\n totalCount\n }\n }\n "): (typeof documents)["\n query LoadAPIStats {\n allArticles: ListArticles {\n totalCount\n }\n allRepliedArticles: ListArticles {\n totalCount\n }\n articlesHasUsefulReplies: ListArticles {\n totalCount\n }\n }\n "]; +export function graphql(source: "\n query LoadAPIStats {\n allArticles: ListArticles {\n totalCount\n }\n allRepliedArticles: ListArticles {\n totalCount\n }\n articlesHasUsefulReplies: ListArticles {\n totalCount\n }\n }\n "): (typeof documents)["\n query LoadAPIStats {\n allArticles: ListArticles {\n totalCount\n }\n allRepliedArticles: ListArticles {\n totalCount\n }\n articlesHasUsefulReplies: ListArticles {\n totalCount\n }\n }\n "]; export function graphql(source: string) { return (documents as any)[source] ?? {};