commit f3b12c375677193ead8b9899118264c22aa2d554 Author: zomo Date: Sun Dec 11 01:36:17 2022 -0600 init + login diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9f01e65 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +API= \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..bd542a5 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,50 @@ +{ + "root": true, + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/strict-boolean-expressions": [ + 2, + { + "allowString": false, + "allowNumber": false + } + ], + + /* important */ + "prefer-const": "error", + "quotes": ["error", "single"], + + "block-scoped-var": "error", + "camelcase": "error", + "consistent-this": ["error", "that"], + "no-else-return": "error", + "no-eq-null": "error", + "no-floating-decimal": "error", + "no-implicit-coercion": "error", + "no-implied-eval": "error", + "no-invalid-this": "error", + "require-await": "error", + "yoda": "error", + + "semi": ["error", "always"], + "semi-style": ["error", "last"], + + /* less important */ + "no-unreachable-loop": "error", + "no-unused-private-class-members": "error", + "no-use-before-define": "error", + "no-unmodified-loop-condition": "error", + "no-duplicate-imports": "error", + "no-promise-executor-return": "error", + "no-self-compare": "error", + "no-constructor-return": "error", + "no-template-curly-in-string": "error", + "array-callback-return": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error" + }, + "ignorePatterns": ["src/**/*.test.ts", "src/frontend/generated/*"] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..182abe2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.vscode +.env +dist +node_modules +package-lock.json +pnpm-lock.yaml +yarn.lock \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d168464 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +dist +log +storage +package.lock.json +pnpm-lock.yaml +yarn.lock \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..07a8707 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "printWidth": 80, + "tabWidth": 4, + "useTabs": false, + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "avoid" +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3115448 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "discord-retokenizer-web", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "env-ts": "import-meta-env-typescript -x .env.example -o src" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@import-meta-env/typescript": "^0.2.0", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.9", + "@vitejs/plugin-react": "^3.0.0", + "dotenv": "^16.0.3", + "eslint": "^8.29.0", + "prettier": "^2.8.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.4.5", + "sass": "^1.56.2", + "typescript": "^4.9.4", + "vite": "^4.0.0" + }, + "devDependencies": { + "@import-meta-env/unplugin": "^0.4.0" + } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..8d121bf --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,25 @@ +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { Layout } from "./pages/Layout"; +import { Login } from "./pages/Login"; + +function App() { + return ( +
+ + + } /> + }> {/* collapsible sidebar with links */} + {/* } /> + } /> + } /> + } /> + } /> + } /> */} + + + +
+ ) +} + +export default App diff --git a/src/authorization.ts b/src/authorization.ts new file mode 100644 index 0000000..be7dd6b --- /dev/null +++ b/src/authorization.ts @@ -0,0 +1,48 @@ +export function getTokenFromStorage() { + return localStorage.getItem('token') +} + +export enum LoggedIn { + No, + Yes, + Invalid, +} + +export function isLoggedIn(): LoggedIn { + const token = getTokenFromStorage() + if (token) { + return LoggedIn.Yes + } + return LoggedIn.No +} + +export function authorizeLogin( + username: string, + password: string, + setLoggedIn: (loggedIn: LoggedIn) => void +) { + // TODO why is ENV undefined? + console.log(import.meta.env.API) + + fetch('http://localhost:8080' + '/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: username, + password: password, + }), + }) + .then(response => { + return response.json() + }) + .then(data => { + if (data.token) { + localStorage.setItem('token', data.token) + setLoggedIn(LoggedIn.Yes) + } else { + setLoggedIn(LoggedIn.Invalid) + } + }) +} diff --git a/src/import-meta-env.d.ts b/src/import-meta-env.d.ts new file mode 100644 index 0000000..b14cac5 --- /dev/null +++ b/src/import-meta-env.d.ts @@ -0,0 +1,9 @@ +// Generated by '@import-meta-env/typescript' + +interface ImportMetaEnv { + readonly API: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..d6a4b32 --- /dev/null +++ b/src/index.html @@ -0,0 +1,7 @@ + + + +
+ + + diff --git a/src/index.scss b/src/index.scss new file mode 100644 index 0000000..650d490 --- /dev/null +++ b/src/index.scss @@ -0,0 +1,95 @@ +body { + margin: 0; +} + +#root { + --bg-color-1: #262626; + --bg-color-2: #363636; + --bg-color-3: #464646; + --text-color-1: #d6d6d6; + --text-color-2: #a6a6a6; + --color-error: #860000; + + background-color: var(--bg-color-1); + color: var(--text-color-1); + font-family: Helvetica, Arial, sans-serif; + font-size: 16px; + + width: 100%; + height: 100%; + + input { + border: none; + outline: none; + background-color: var(--bg-color-2); + color: var(--text-color-2); + font-size: 16px; + + &[type="text"], + &[type="password"] { + width: 200px; + } + + padding: 7px 10px; + margin: 0 5px; + + border-radius: 200px; + + &:focus { + background-color: var(--bg-color-3); + color: var(--text-color-1); + } + + border-bottom: 1px solid transparent; + &.error { + border-bottom: 1px solid var(--color-error); + } + } + + button { + border: none; + outline: none; + background-color: var(--bg-color-2); + color: var(--text-color-2); + font-size: 16px; + + &.svg svg { + fill: var(--text-color-2); + height: 16px; + } + + padding: 7px 10px; + margin: 0 5px; + + &.pad { + padding: 10px 15px; + margin: 10px; + } + + border-radius: 200px; + + &:hover, + &:focus { + background-color: var(--bg-color-3); + color: var(--text-color-1); + + &.svg svg { + fill: var(--text-color-1); + } + } + } + + #login { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + width: 100%; + height: 100%; + + .form { + margin-bottom: 5px; + } + } +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..3ffdf28 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.scss' + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + +) diff --git a/src/pages/Layout.tsx b/src/pages/Layout.tsx new file mode 100644 index 0000000..536032b --- /dev/null +++ b/src/pages/Layout.tsx @@ -0,0 +1,14 @@ +import { Outlet, Navigate } from 'react-router-dom' +import { isLoggedIn, LoggedIn } from '../authorization' + +export function Layout() { + return ( +
+

Layout

+ { + isLoggedIn() === LoggedIn.No && + } + +
+ ) +} \ No newline at end of file diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx new file mode 100644 index 0000000..b796d27 --- /dev/null +++ b/src/pages/Login.tsx @@ -0,0 +1,42 @@ +import { useMemo, useState } from "react" +import { Navigate } from "react-router-dom" +import { authorizeLogin, isLoggedIn, LoggedIn } from "../authorization" + +export function Login() { + var [username, setUsername] = useState("") + var [password, setPassword] = useState("") + var [loggedIn, setLoggedIn] = useState(isLoggedIn()) + + var login = () => { + if (username.length === 0 || password.length === 0) { + setLoggedIn(LoggedIn.Invalid) + return + } + authorizeLogin(username, password, setLoggedIn) + } + + useMemo(() => { + if (loggedIn === LoggedIn.Invalid) { + setLoggedIn(LoggedIn.No) + } + }, [username, password]) + + return ( +
+
+ setUsername(e.target.value)} /> + setPassword(e.target.value)} /> +
+
+ +
+ { + loggedIn === LoggedIn.Yes && + } +
+ ) +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ebdd95e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "rootDir": "./src", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "jsx": "react-jsx", + + "useDefineForClassFields": true, + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "sourceMap": true, + "resolveJsonModule": true, + "noEmit": true, + }, + "include": ["src/**/*"], +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..6c4d3e2 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from "vite"; +import { resolve } from "path"; +import react from '@vitejs/plugin-react' +import importMetaEnv from "@import-meta-env/unplugin"; + +export default defineConfig({ + root: resolve(__dirname, 'src'), + plugins: [ + react(), + importMetaEnv.vite({ + example: ".env.example", + env: ".env", + transformMode: "development", + }) + ], + build: { + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html') + }, + } + } +}); \ No newline at end of file