Skip to content

Commit

Permalink
Merge pull request #140 from isucon/react
Browse files Browse the repository at this point in the history
add React frontend
  • Loading branch information
kamijin-fanta authored Nov 2, 2023
2 parents fb752cb + 7afc3ef commit 1bc1d9f
Show file tree
Hide file tree
Showing 42 changed files with 9,068 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"[yaml]": {
"editor.formatOnSave": false
}
}
35 changes: 26 additions & 9 deletions docs/isupipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ paths:
parameters: []
get:
summary: ""
operationid: get-tag
operationId: get-tag
responses:
"200":
$ref: "#/components/responses/GetTag"
Expand Down Expand Up @@ -73,6 +73,17 @@ paths:
description: ユーザ登録
requestBody:
$ref: "#/components/requestBodies/PostUser"
/user/me:
get:
summary:
operationId: get-user-me
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"/users/{username}":
parameters:
- schema:
Expand Down Expand Up @@ -177,7 +188,7 @@ paths:
responses:
"200":
$ref: "#/components/responses/GetLivestream"
operationId: "get-livestream-:livestreamid"
operationId: "get-livestream-_livestreamid"
description: ライブストリーム視聴画面の情報取得
"/livestream/{livestreamid}/moderate":
parameters:
Expand Down Expand Up @@ -218,7 +229,7 @@ paths:
get:
summary: Your GET endpoint
tags: []
operationId: "get-livestream-:livestreamid-livecomment"
operationId: "get-livestream-_livestreamid-livecomment"
description: 当該ライブストリームのライブコメント取得
responses:
"200":
Expand Down Expand Up @@ -303,7 +314,7 @@ paths:
get:
summary: Your GET endpoint
tags: []
operationId: "get-livestream-:livestreamid-reaction"
operationId: "get-livestream-_livestreamid-reaction"
description: 当該ライブストリームのリアクション取得
responses:
"200":
Expand Down Expand Up @@ -358,7 +369,9 @@ paths:
responses:
"200":
$ref: "#/components/responses/GetLivestreamStatistics"
operationId: "get-livestream-:livestreamid-statistics"
"404":
description: Not Found
operationId: "get-livestream-_livestreamid-statistics"
description: ライブストリームの統計情報取得
/livestream/reservation:
post:
Expand Down Expand Up @@ -707,12 +720,16 @@ components:
end_at: 0
responses:
GetTag:
description: Example response
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Tag"
type: object
properties:
tags:
type: array
items:
$ref: "#/components/schemas/Tag"
GetUser:
description: Example response
content:
Expand Down Expand Up @@ -744,7 +761,7 @@ components:
content:
application/json:
schema:
$ref: "#/components/schemas/LivestreamView"
$ref: "#/components/schemas/Livestream"
GetLivestreamStatistics:
description: Example response
content:
Expand Down
42 changes: 42 additions & 0 deletions frontend/.eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
env:
browser: true
es6: true
extends:
- 'eslint:recommended'
- 'plugin:react/recommended'
- 'plugin:@typescript-eslint/eslint-recommended'
- 'plugin:import/recommended'
- 'plugin:import/errors'
- 'plugin:import/warnings'
globals:
Atomics: readonly
SharedArrayBuffer: readonly
parser: '@typescript-eslint/parser'
parserOptions:
ecmaFeatures:
jsx: true
ecmaVersion: 2018
sourceType: module
plugins:
- react
- '@typescript-eslint'
- 'react-hooks'
rules:
import/order: # https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/order.md
- 'warn'
- alphabetize: { order: asc, caseInsensitive: true }
newlines-between: never
pathGroups:
- { pattern: '~/**', group: 'parent' }
- { pattern: 'react', group: 'parent', position: 'before' }
import/no-unresolved: 0
'no-unused-vars': 'off'
'@typescript-eslint/no-unused-vars': ['error']
'@typescript-eslint/explicit-module-boundary-types': ['error']
'react/jsx-curly-brace-presence': 'error'
'react-hooks/rules-of-hooks': 'error'
'arrow-body-style': ['error', 'as-needed']
'dot-notation': 'error'
settings:
react:
version: 'detect'
4 changes: 4 additions & 0 deletions frontend/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.yarn/* linguist-generated=true
yarn.lock linguist-generated=true
src/api/apiClient.ts linguist-generated=true
src/api/types.ts linguist-generated=true
26 changes: 26 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

/.yarn/cache
/.yarn/install-state.gz
3 changes: 3 additions & 0 deletions frontend/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.yarn
node_modules
/dist/
3 changes: 3 additions & 0 deletions frontend/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
8 changes: 8 additions & 0 deletions frontend/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.insertFinalNewline": true
}
1 change: 1 addition & 0 deletions frontend/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
12 changes: 12 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ISUCON13</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
64 changes: 64 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "vite-project",
"private": true,
"version": "0.0.0",
"packageManager": "[email protected]",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint --ext .jsx,.js,.tsx,.ts src/",
"format": "prettier --write --ignore-unknown .",
"format:ci": "prettier --check --ignore-unknown .",
"test": "vitest --config ./vitest.config.ts",
"test:ci": "yarn test run",
"generate-api-client": "ts-node -P scripts/openapi/tsconfig.build.json scripts/openapi/generate-api-client.ts"
},
"dependencies": {
"@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@hookform/resolvers": "^3.3.1",
"@mui/joy": "^5.0.0-beta.11",
"@react-stately/toast": "^3.0.0-beta.1",
"d3-format": "^3.1.0",
"date-fns": "^2.30.0",
"dayjs": "^1.11.9",
"emoji-mart": "^5.5.2",
"events": "^3.3.0",
"focus-visible": "^5.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.46.1",
"react-hooks-global-state": "^2.1.0",
"react-icons": "^4.11.0",
"react-router-dom": "^6.15.0",
"swr": "^2.2.2",
"zod": "^3.22.2"
},
"devDependencies": {
"@himenon/openapi-typescript-code-generator": "^0.27.1",
"@types/d3-format": "^3.0.1",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"@vitejs/plugin-react": "^4.0.4",
"cross-env": "^7.0.3",
"eslint": "^8.49.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^3.0.3",
"ts-node": "^10.9.1",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-plugin-pages": "^0.31.0",
"vitest": "^0.34.4"
},
"readme": "ERROR: No README data found!",
"_id": "[email protected]"
}
37 changes: 37 additions & 0 deletions frontend/scripts/openapi/generate-api-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as fs from 'fs';
import * as path from 'path';
import { CodeGenerator } from '@himenon/openapi-typescript-code-generator';
import * as Templates from '@himenon/openapi-typescript-code-generator/templates';

const main = () => {
const codeGenerator = new CodeGenerator(
path.join(__dirname, '../../../docs/isupipe.yaml'),
);

const apiClientGeneratorTemplate = {
generator: Templates.ClassApiClient.generator,
option: {},
};

const typeDefCode = codeGenerator.generateTypeDefinition();
const apiClientCode = codeGenerator.generateCode([
{
generator: () => [
`import { Schemas, RequestBodies, Responses } from "./types";`,
],
},
codeGenerator.getAdditionalTypeDefinitionCustomCodeGenerator(),
apiClientGeneratorTemplate,
]);

fs.writeFileSync(__dirname + '/../../src/api/types.ts', typeDefCode, {
encoding: 'utf-8',
});
fs.writeFileSync(__dirname + '/../../src/api/apiClient.ts', apiClientCode, {
encoding: 'utf-8',
});

console.log('Generate API Client');
};

main();
6 changes: 6 additions & 0 deletions frontend/scripts/openapi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "openapi",
"private": true,
"version": "0.0.0",
"packageManager": "[email protected]"
}
10 changes: 10 additions & 0 deletions frontend/scripts/openapi/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"types": ["node"],
"declaration": false,
"noEmit": false
},
"include": ["./*"]
}
44 changes: 44 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { CssVarsProvider } from '@mui/joy/styles';
import React, { Suspense } from 'react';
import { useRoutes } from 'react-router-dom';
import { SWRConfig } from 'swr';
import { HTTPError } from './api/client';
import { Layout } from './components/layout/Layout';
import { LoginModal } from './components/layout/loginmodal';
import routes from '~react-pages';
import 'focus-visible/dist/focus-visible';

export function App(): React.ReactElement {
const routeContent = useRoutes(routes);
const [isOpenReLoginModal, setIsOpenReLoginModal] = React.useState(false);

return (
<CssVarsProvider defaultMode="light">
<SWRConfig
value={{
focusThrottleInterval: 20000,
dedupingInterval: 10000,

onError: (error) => {
if (error instanceof HTTPError) {
switch (error.response.status) {
case 401:
case 403:
setIsOpenReLoginModal(true);
break;
}
}
},
}}
>
<Layout>
<LoginModal
isOpen={isOpenReLoginModal}
onClose={() => setIsOpenReLoginModal(false)}
/>
<Suspense fallback={<p>Loading...</p>}>{routeContent}</Suspense>
</Layout>
</SWRConfig>
</CssVarsProvider>
);
}
Loading

0 comments on commit 1bc1d9f

Please sign in to comment.