diff --git a/src/app/pages/dashboard/types.ts b/src/pages/dashboard/types.ts
similarity index 100%
rename from src/app/pages/dashboard/types.ts
rename to src/pages/dashboard/types.ts
diff --git a/src/app/pages/details/details.test.tsx b/src/pages/details/details.test.tsx
similarity index 92%
rename from src/app/pages/details/details.test.tsx
rename to src/pages/details/details.test.tsx
index fc36739..787072e 100644
--- a/src/app/pages/details/details.test.tsx
+++ b/src/pages/details/details.test.tsx
@@ -1,13 +1,12 @@
-import React from 'react';
-import { render, act } from '@testing-library/react';
+import axios from '@src/utils/axios';
+import { act, render } from '@testing-library/react';
+import MockAdapter from 'axios-mock-adapter';
import { BrowserRouter } from 'react-router-dom';
-import { Details } from './details';
import { RecoilRoot } from 'recoil';
-import MockAdapter from 'axios-mock-adapter';
-import * as useAuthMock from '../../hooks/use-auth';
-import axios from '../../axios';
import { launchData } from '../../data/launch';
+import * as useAuthMock from '../../hooks/use-auth';
import { User } from '../../types/user';
+import { Details } from './details';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
@@ -50,7 +49,9 @@ describe('Details', () => {
await act(async () => {
expect(baseElement).toBeTruthy();
});
- expect(baseElement.querySelector('h1')?.textContent).toEqual('Launch Details');
+ expect(baseElement.querySelector('h1')?.textContent).toEqual(
+ 'Launch Details',
+ );
expect(baseElement.querySelectorAll('#details-card li')).toHaveLength(5);
});
@@ -66,7 +67,9 @@ describe('Details', () => {
await act(async () => {
expect(baseElement).toBeTruthy();
});
- expect(baseElement.querySelector('h1')?.textContent).toEqual('Launch Details');
+ expect(baseElement.querySelector('h1')?.textContent).toEqual(
+ 'Launch Details',
+ );
expect(baseElement.querySelector('.usa-alert')).toBeDefined();
expect(baseElement.querySelector('.usa-alert--error')).toBeDefined();
});
diff --git a/src/app/pages/details/details.tsx b/src/pages/details/details.tsx
similarity index 98%
rename from src/app/pages/details/details.tsx
rename to src/pages/details/details.tsx
index 9ac26e5..e7da803 100644
--- a/src/app/pages/details/details.tsx
+++ b/src/pages/details/details.tsx
@@ -1,11 +1,11 @@
+import { Spinner } from '@metrostar/comet-extras';
+import { Card } from '@metrostar/comet-uswds';
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
-import { Card } from '@metrostar/comet-uswds';
-import { Spinner } from '@metrostar/comet-extras';
-import { Launch } from '../../types/launch';
+import ErrorNotification from '../../components/error-notification/error-notification';
import useApi from '../../hooks/use-api';
import useAuth from '../../hooks/use-auth';
-import ErrorNotification from '../../components/error-notification/error-notification';
+import { Launch } from '../../types/launch';
export const Details = (): React.ReactElement => {
const { id } = useParams();
@@ -17,7 +17,7 @@ export const Details = (): React.ReactElement => {
if (isSignedIn && id) {
getItem(id);
}
- }, [isSignedIn, id]);
+ }, [isSignedIn, id, getItem]);
useEffect(() => {
if (item) {
diff --git a/src/app/pages/home/home.test.tsx b/src/pages/home/home.test.tsx
similarity index 90%
rename from src/app/pages/home/home.test.tsx
rename to src/pages/home/home.test.tsx
index fe570af..ebdc80e 100644
--- a/src/app/pages/home/home.test.tsx
+++ b/src/pages/home/home.test.tsx
@@ -1,10 +1,9 @@
-import React from 'react';
-import { render, act } from '@testing-library/react';
+import { act, render } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
-import * as useAuthMock from '../../hooks/use-auth';
-import { Home } from './home';
import { RecoilRoot } from 'recoil';
+import * as useAuthMock from '../../hooks/use-auth';
import { User } from '../../types/user';
+import { Home } from './home';
describe('Home', () => {
test('should render successfully', () => {
@@ -16,7 +15,9 @@ describe('Home', () => {
,
);
expect(baseElement).toBeTruthy();
- expect(baseElement.querySelector('h1')?.textContent).toEqual('Welcome Guest');
+ expect(baseElement.querySelector('h1')?.textContent).toEqual(
+ 'Welcome Guest',
+ );
});
test('should render with mock data', async () => {
@@ -37,6 +38,8 @@ describe('Home', () => {
await act(async () => {
expect(baseElement).toBeTruthy();
});
- expect(baseElement.querySelector('h1')?.textContent).toEqual('Welcome John Doe');
+ expect(baseElement.querySelector('h1')?.textContent).toEqual(
+ 'Welcome John Doe',
+ );
});
});
diff --git a/src/app/pages/home/home.tsx b/src/pages/home/home.tsx
similarity index 75%
rename from src/app/pages/home/home.tsx
rename to src/pages/home/home.tsx
index 9aeb369..73754d1 100644
--- a/src/app/pages/home/home.tsx
+++ b/src/pages/home/home.tsx
@@ -1,7 +1,7 @@
+import { Alert } from '@metrostar/comet-uswds';
+import { getDisplayName } from '@src/utils/auth';
import React from 'react';
import useAuth from '../../hooks/use-auth';
-import { Alert } from '@metrostar/comet-uswds';
-import { getDisplayName } from '../../helpers/auth';
export const Home = (): React.ReactElement => {
const { isSignedIn, currentUserData } = useAuth();
@@ -9,14 +9,18 @@ export const Home = (): React.ReactElement => {
-
Welcome {currentUserData ? getDisplayName(currentUserData) : 'Guest'}
+
+ Welcome{' '}
+ {currentUserData ? getDisplayName(currentUserData) : 'Guest'}
+
{!isSignedIn && (
- You are not currently signed in. Please Sign In to access the Dashboard.
+ You are not currently signed in. Please Sign In to access the
+ Dashboard.
diff --git a/src/app/pages/sign-in/sign-in.test.tsx b/src/pages/sign-in/sign-in.test.tsx
similarity index 81%
rename from src/app/pages/sign-in/sign-in.test.tsx
rename to src/pages/sign-in/sign-in.test.tsx
index 0d0758f..c563fd1 100644
--- a/src/app/pages/sign-in/sign-in.test.tsx
+++ b/src/pages/sign-in/sign-in.test.tsx
@@ -1,12 +1,11 @@
-import React from 'react';
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { BrowserRouter } from 'react-router-dom';
-import { SignIn } from './sign-in';
import { RecoilRoot } from 'recoil';
import * as useAuthMock from '../../hooks/use-auth';
import { User } from '../../types/user';
+import { SignIn } from './sign-in';
describe('SignIn', () => {
const signInComponent = (
@@ -24,21 +23,27 @@ describe('SignIn', () => {
test('should simulate a login attempt with blank fields', async () => {
const { baseElement } = render(signInComponent);
- await userEvent.click(screen.getByText('Sign In', { selector: 'button[type=submit]' }));
+ await userEvent.click(
+ screen.getByText('Sign In', { selector: 'button[type=submit]' }),
+ );
expect(baseElement.querySelectorAll('.usa-error-message').length).toBe(2);
});
test('should simulate a login attempt with blank username', async () => {
const { baseElement } = render(signInComponent);
await userEvent.type(screen.getByLabelText('Password'), 'b');
- await userEvent.click(screen.getByText('Sign In', { selector: 'button[type=submit]' }));
+ await userEvent.click(
+ screen.getByText('Sign In', { selector: 'button[type=submit]' }),
+ );
expect(baseElement.querySelectorAll('.usa-error-message').length).toBe(1);
});
test('should simulate a login attempt with blank password', async () => {
const { baseElement } = render(signInComponent);
await userEvent.type(screen.getByLabelText('Username'), 'a');
- await userEvent.click(screen.getByText('Sign In', { selector: 'button[type=submit]' }));
+ await userEvent.click(
+ screen.getByText('Sign In', { selector: 'button[type=submit]' }),
+ );
expect(baseElement.querySelectorAll('.usa-error-message').length).toBe(1);
});
@@ -56,7 +61,9 @@ describe('SignIn', () => {
await userEvent.type(screen.getByLabelText('Password'), 'b');
await act(async () => {
- await userEvent.click(screen.getByText('Sign In', { selector: 'button[type=submit]' }));
+ await userEvent.click(
+ screen.getByText('Sign In', { selector: 'button[type=submit]' }),
+ );
});
expect(baseElement.querySelectorAll('.usa-error-message').length).toBe(0);
});
@@ -75,7 +82,9 @@ describe('SignIn', () => {
await userEvent.type(screen.getByLabelText('Password'), 'b');
await act(async () => {
- await userEvent.click(screen.getByText('Sign In', { selector: 'button[type=submit]' }));
+ await userEvent.click(
+ screen.getByText('Sign In', { selector: 'button[type=submit]' }),
+ );
});
expect(baseElement.querySelectorAll('.usa-error-message').length).toBe(0);
});
@@ -94,14 +103,18 @@ describe('SignIn', () => {
await userEvent.type(screen.getByLabelText('Password'), 'b');
await act(async () => {
- await userEvent.click(screen.getByText('Sign In', { selector: 'button[type=submit]' }));
+ await userEvent.click(
+ screen.getByText('Sign In', { selector: 'button[type=submit]' }),
+ );
});
expect(baseElement.querySelectorAll('.usa-alert').length).toBe(1);
});
test('should cancel a login attempt', async () => {
const { baseElement } = render(signInComponent);
- await userEvent.click(screen.getByText('Cancel', { selector: 'button[type=button]' }));
+ await userEvent.click(
+ screen.getByText('Cancel', { selector: 'button[type=button]' }),
+ );
expect(baseElement.querySelectorAll('.usa-error-message').length).toBe(0);
});
});
diff --git a/src/app/pages/sign-in/sign-in.tsx b/src/pages/sign-in/sign-in.tsx
similarity index 85%
rename from src/app/pages/sign-in/sign-in.tsx
rename to src/pages/sign-in/sign-in.tsx
index 9498b03..f8c7c66 100644
--- a/src/app/pages/sign-in/sign-in.tsx
+++ b/src/pages/sign-in/sign-in.tsx
@@ -1,16 +1,16 @@
-import React, { FormEvent, useEffect, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
import {
+ Alert,
+ Button,
+ ButtonGroup,
+ ErrorMessages,
Form,
FormGroup,
Label,
TextInput,
- Button,
- Alert,
- ErrorMessages,
- ButtonGroup,
} from '@metrostar/comet-uswds';
-import { REQUIRED_FIELD_MESSAGE } from '../../constants';
+import { REQUIRED_FIELD_MESSAGE } from '@src/utils/constants';
+import React, { FormEvent, useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
import useAuth from '../../hooks/use-auth';
export const SignIn = (): React.ReactElement => {
@@ -27,7 +27,7 @@ export const SignIn = (): React.ReactElement => {
setHasLoginError(false);
navigate('/');
}
- }, [isSignedIn]);
+ }, [isSignedIn, navigate]);
useEffect(() => {
if (error) {
@@ -37,8 +37,12 @@ export const SignIn = (): React.ReactElement => {
const handleLogin = (event: FormEvent): void => {
event.preventDefault();
- username === '' ? setUsernameErrors([REQUIRED_FIELD_MESSAGE]) : setUsernameErrors([]);
- password === '' ? setPasswordErrors([REQUIRED_FIELD_MESSAGE]) : setPasswordErrors([]);
+ username === ''
+ ? setUsernameErrors([REQUIRED_FIELD_MESSAGE])
+ : setUsernameErrors([]);
+ password === ''
+ ? setPasswordErrors([REQUIRED_FIELD_MESSAGE])
+ : setPasswordErrors([]);
if (username.length === 0 || password.length === 0) {
setHasLoginError(true);
@@ -94,7 +98,12 @@ export const SignIn = (): React.ReactElement => {
Sign In
-
+
Cancel
diff --git a/src/uswds/uswds-settings.scss b/src/providers/uswds/uswds-settings.scss
similarity index 78%
rename from src/uswds/uswds-settings.scss
rename to src/providers/uswds/uswds-settings.scss
index 1a8330e..373ab37 100644
--- a/src/uswds/uswds-settings.scss
+++ b/src/providers/uswds/uswds-settings.scss
@@ -1,3 +1,4 @@
+// node_modules\@uswds\uswds\packages\uswds-core
@use 'uswds-core' with (
$theme-show-notifications: false,
$theme-font-path: 'node_modules/@uswds/uswds/dist/fonts/',
diff --git a/src/uswds/uswds.scss b/src/providers/uswds/uswds.scss
similarity index 87%
rename from src/uswds/uswds.scss
rename to src/providers/uswds/uswds.scss
index 9676441..91c18f4 100644
--- a/src/uswds/uswds.scss
+++ b/src/providers/uswds/uswds.scss
@@ -1,5 +1,5 @@
// Include a USWDS settings file (required)
-@forward 'uswds-settings.scss';
+@forward './uswds-settings.scss';
// Point to the USWDS source code (required)
@forward 'node_modules/@uswds/uswds/packages/uswds';
diff --git a/src/app/store.ts b/src/store.ts
similarity index 55%
rename from src/app/store.ts
rename to src/store.ts
index 30f7b4e..33140a8 100644
--- a/src/app/store.ts
+++ b/src/store.ts
@@ -1,14 +1,14 @@
+import { User } from '@src/types/user';
import { atom } from 'recoil';
-import { User } from './types/user';
const signedIn = atom({
key: 'signedIn',
default: false,
});
-const currentUser = atom({
+const currentUser = atom({
key: 'currentUser',
default: undefined,
});
-export { signedIn, currentUser };
+export { currentUser, signedIn };
diff --git a/src/styles.scss b/src/styles.scss
index 8f21931..8dee0fd 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1,5 +1,3 @@
-@forward 'uswds/uswds.scss';
-
.usa-section {
padding-top: 1em !important;
}
diff --git a/src/types.d.ts b/src/types.d.ts
index 73894fb..8f3ce30 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -1,5 +1 @@
-declare module '*.module.scss';
-declare module '*.svg';
-declare module '*.png';
-
declare module '@uswds/uswds/js/usa-header';
diff --git a/src/app/types/launch.ts b/src/types/launch.ts
similarity index 100%
rename from src/app/types/launch.ts
rename to src/types/launch.ts
diff --git a/src/app/types/user.ts b/src/types/user.ts
similarity index 100%
rename from src/app/types/user.ts
rename to src/types/user.ts
diff --git a/src/app/helpers/auth.test.ts b/src/utils/auth.test.ts
similarity index 92%
rename from src/app/helpers/auth.test.ts
rename to src/utils/auth.test.ts
index 9c95f9c..7b74ca0 100644
--- a/src/app/helpers/auth.test.ts
+++ b/src/utils/auth.test.ts
@@ -1,5 +1,5 @@
-import { userData } from '../data/user';
-import { User } from '../types/user';
+import { userData } from '@src/data/user';
+import { User } from '@src/types/user';
import { getDisplayName } from './auth';
describe('Auth Helpers', () => {
diff --git a/src/app/helpers/auth.ts b/src/utils/auth.ts
similarity index 88%
rename from src/app/helpers/auth.ts
rename to src/utils/auth.ts
index 421d885..8458d19 100644
--- a/src/app/helpers/auth.ts
+++ b/src/utils/auth.ts
@@ -1,4 +1,4 @@
-import { User } from '../types/user';
+import { User } from '@src/types/user';
export const getDisplayName = (user: User): string => {
if (user.displayName) {
diff --git a/src/app/axios.ts b/src/utils/axios.ts
similarity index 70%
rename from src/app/axios.ts
rename to src/utils/axios.ts
index f528a2e..4feecfa 100644
--- a/src/app/axios.ts
+++ b/src/utils/axios.ts
@@ -1,7 +1,7 @@
import axios from 'axios';
const instance = axios.create({
- baseURL: '/api',
+ baseURL: process.env.API_BASE_URL,
});
export default instance;
diff --git a/src/app/constants.ts b/src/utils/constants.ts
similarity index 55%
rename from src/app/constants.ts
rename to src/utils/constants.ts
index 6b6142c..95ac541 100644
--- a/src/app/constants.ts
+++ b/src/utils/constants.ts
@@ -1,2 +1,6 @@
export const APP_TITLE = 'Starter App';
export const REQUIRED_FIELD_MESSAGE = 'This field is required.';
+
+export const REQUIRED_FORM_FIELDS_RULES = {
+ required: REQUIRED_FIELD_MESSAGE,
+};
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/tsconfig.json b/tsconfig.json
index 4907102..07c5bb1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,17 +1,38 @@
{
"compilerOptions": {
- "target": "ESNext",
- "module": "umd",
- "lib": ["ESNext", "dom"],
- "jsx": "react",
+ "baseUrl": ".",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
"noEmit": true,
- "sourceMap": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
"strict": true,
- "noImplicitAny": true,
- "strictNullChecks": true,
- "moduleResolution": "node",
- "forceConsistentCasingInFileNames": true,
- "esModuleInterop": true
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+
+ /* Paths */
+ "paths": {
+ "@src/components/*": ["src/components/*"],
+ "@src/data/*": ["src/data/*"],
+ "@src/features/*": ["src/features/*"],
+ "@src/hooks/*": ["src/hooks/*"],
+ "@src/pages/*": ["src/pages/*"],
+ "@src/types/*": ["src/types/*"],
+ "@src/utils/*": ["src/utils/*"]
+ }
},
- "include": ["src"]
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..42872c5
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..8772734
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,21 @@
+import react from '@vitejs/plugin-react';
+import { defineConfig } from 'vite';
+import EnvironmentPlugin from 'vite-plugin-environment';
+import eslint from 'vite-plugin-eslint';
+import tsconfigPaths from 'vite-tsconfig-paths';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react(), tsconfigPaths(), eslint(), EnvironmentPlugin('all')],
+ server: {
+ open: true,
+ port: 8080,
+ proxy: {
+ '/api': {
+ target: 'https://ll.thespacedevs.com/2.2.0/launch/',
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, ''),
+ },
+ },
+ },
+});
diff --git a/webpack.common.js b/webpack.common.js
deleted file mode 100644
index 1c47220..0000000
--- a/webpack.common.js
+++ /dev/null
@@ -1,68 +0,0 @@
-const path = require('path');
-const HtmlWebpackPlugin = require('html-webpack-plugin')
-const CopyWebpackPlugin = require('copy-webpack-plugin')
-
-const ROOT_DIRECTORY = path.join(__dirname, './')
-const SRC_DIRECTORY = path.join(ROOT_DIRECTORY, 'public')
-
-module.exports = {
- entry: './src/main.tsx',
- plugins: [
- new HtmlWebpackPlugin({
- template: path.join(SRC_DIRECTORY, 'index.html')
- }),
- new CopyWebpackPlugin(
- {
- patterns: [
- {
- from: path.resolve(SRC_DIRECTORY),
- globOptions: {
- ignore: ["index.html"],
- },
- },
- ]
- }
- )
- ],
- module: {
- rules: [
- {
- test: /\.(js|ts)x?$/,
- exclude: /node_modules/,
- use: {
- loader: 'babel-loader',
- },
- },
- {
- test: /\.scss$/,
- use: [
- {
- loader: 'style-loader',
- },
- {
- loader: 'css-loader',
- },
- {
- loader: 'sass-loader',
- options: {
- sassOptions: {
- includePaths: ['./node_modules/@uswds/uswds/packages'],
- },
- },
- },
- ],
- },
- {
- test: /\.(png|jpe?g|gif|svg|woff2)$/i,
- use: [
- {
- loader: 'file-loader',
- },
- ],
- },
- ],
- },
- resolve: {
- extensions: ['.tsx', '.ts', '.jsx', '.js'],
- },
-};
diff --git a/webpack.dev.js b/webpack.dev.js
deleted file mode 100644
index e8003c6..0000000
--- a/webpack.dev.js
+++ /dev/null
@@ -1,20 +0,0 @@
-const { merge } = require("webpack-merge");
-const path = require('path');
-const common = require("./webpack.common.js");
-module.exports = merge(common, {
- mode: "development",
- devServer: {
- historyApiFallback: true,
- static: {
- directory: path.resolve(__dirname, './public'),
- },
- open: true,
- proxy: {
- '/api': {
- target: 'https://ll.thespacedevs.com/2.2.0/launch/',
- changeOrigin: true,
- pathRewrite: { '^/api': '' },
- },
- },
- },
-});
\ No newline at end of file
diff --git a/webpack.prod.js b/webpack.prod.js
deleted file mode 100644
index 513fb1d..0000000
--- a/webpack.prod.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const { merge } = require("webpack-merge");
-const path = require('path');
-const common = require("./webpack.common.js");
-
-module.exports = merge(common, {
- mode: "production",
- devtool: false,
- output: {
- path: path.resolve(__dirname, './dist'),
- filename: "[name].[contenthash].bundle.js",
- },
-});
\ No newline at end of file