Backing up

This commit is contained in:
2025-09-16 00:32:01 +05:30
parent eaeb00c564
commit a003aa44d2
53 changed files with 296 additions and 20108 deletions

31
build-tools/build.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Get the directory of the current script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Default values
DEV_FLAG=""
# Parse arguments
for arg in "$@"
do
case $arg in
--dev)
DEV_FLAG="--dev"
shift # Remove --dev from processing
;;
esac
done
echo "[BUILD:SH] Dev flag is: $DEV_FLAG"
echo "[BUILD:SH] Removing build if exists"
rm -rf build
echo "[BUILD:SH] Checking TS Types"
npx tsc
echo "[BUILD:SH] Initiating build..."
# Conditionally use vite-dev.config.ts if --dev flag is present
if [ "$DEV_FLAG" == "--dev" ]; then
vite build --config vite-dev.config.ts
else
vite build
fi

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" ar-theme="th-light-1">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
@@ -8,7 +8,7 @@
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<div id="root" style="height:100vh;"></div>
<script type="module" src="/src/Test.tsx"></script>
</body>
</html>

19327
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,22 +3,20 @@
"private": true,
"version": "0.0.0",
"type": "module",
"main": "./build/cjs/Tasks.js",
"module": "./build/es/Tasks.js",
"types": "./build/types/Tasks.d.ts",
"scripts": {
"dev": "vite",
"start": "vite",
"build": "tsc && vite build",
"generate": "plop",
"atom": "plop atom",
"molecule": "plop molecule",
"component": "plop component",
"page": "plop page",
"preview": "vite preview",
"test": "vitest",
"format": "prettier --write .",
"lint": "eslint .",
"type-check": "tsc"
"dev": "NODE_ENV=development vite --config vite-run.config.ts",
"start": "serve -s build",
"build": "./build-tools/build.sh",
"build:sm": "./build-tools/build.sh --dev"
},
"dependencies": {
"peerDependencies": {
"@armco/types": "^0.0.18",
"@armco/configs": "^0.0.11",
"@armco/utils": "^0.0.29",
"@armco/components": "^0.0.60",
"@reduxjs/toolkit": "^1.8.1",
"react": "^18.2.0",
"react-app-polyfill": "^3.0.0",
@@ -27,27 +25,6 @@
"react-redux": "^8.0.1",
"react-router-dom": "^6.13.0"
},
"devDependencies": {
"@testing-library/dom": "^9.2.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.2.5",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/testing-library__jest-dom": "^5.14.5",
"@vitejs/plugin-react": "^4.0.0",
"eslint": "^8.0.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^4.2.1",
"jsdom": "^21.1.0",
"plop": "^3.1.2",
"prettier": "^2.7.1",
"prettier-config-nick": "^1.0.2",
"sass": "^1.63.4",
"typescript": "^5.0.2",
"vite": "^4.0.0",
"vitest": "^0.30.1"
},
"eslintConfig": {
"extends": [
"react-app",
@@ -62,10 +39,9 @@
}
},
"prettier": "prettier-config-nick",
"main": "index.tsx",
"repository": {
"type": "git",
"url": "git+https://github.com/ReStruct-Corporate-Advantage/.git"
"url": "git+https://github.com/ReStruct-Corporate-Advantage/taskerclient.git"
},
"keywords": [
"components",
@@ -75,7 +51,7 @@
],
"license": "ISC",
"bugs": {
"url": "https://github.com/ReStruct-Corporate-Advantage/react-vite-rtk-template/issues"
"url": "https://github.com/ReStruct-Corporate-Advantage/taskerclient/issues"
},
"homepage": "https://github.com/ReStruct-Corporate-Advantage/react-vite-rtk-template#readme"
"homepage": "https://github.com/ReStruct-Corporate-Advantage/taskerclient#readme"
}

View File

@@ -1,3 +0,0 @@
.c-{{pascalCase name}} {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import {{pascalCase name}} from "./{{pascalCase name}}"
describe("{{pascalCase name}}", () => {
it("renders without error", () => {
})
})

View File

@@ -1,10 +0,0 @@
import React from "react"
import "./{{pascalCase name}}.component.scss"
interface {{pascalCase name}}Props {}
const {{pascalCase name}} = (props: {{pascalCase name}}Props): JSX.Element => {
return <div className="c-{{pascalCase name}}">In Component {{pascalCase name}}</div>
}
export default {{pascalCase name}}

View File

@@ -1,3 +0,0 @@
import {{pascalCase name}} from "./{{pascalCase name}}.jsx"
export default {{pascalCase name}}

View File

@@ -1,3 +0,0 @@
.c-{{pascalCase name}} {
}

View File

@@ -1,18 +0,0 @@
import { createSlice } from "@reduxjs/toolkit"
export interface {{pascalCase name}}State {}
const initialState: {{pascalCase name}}State = {}
export const {{snakeCase name}}Slice = createSlice({
name: "{{snakeCase name}}",
initialState,
reducers: {
increment: (state) => {},
},
extraReducers: (builder) => {},
})
export const { increment } = {{snakeCase name}}Slice.actions
export default {{snakeCase name}}Slice.reducer

View File

@@ -1,8 +0,0 @@
import React from "react"
import {{pascalCase name}} from "./{{pascalCase name}}"
describe("{{pascalCase name}}", () => {
it("renders without error", () => {
})
})

View File

@@ -1,10 +0,0 @@
import React from "react"
import "./{{pascalCase name}}.module.scss"
interface {{pascalCase name}}Props {}
const {{pascalCase name}} = (props: {{pascalCase name}}Props): JSX.Element => {
return <div className="c-{{pascalCase name}}">In Page {{pascalCase name}}</div>
}
export default {{pascalCase name}}

View File

@@ -1,3 +0,0 @@
import {{pascalCase name}} from "./{{pascalCase name}}.jsx"
export default {{pascalCase name}}

View File

@@ -1,5 +0,0 @@
/* PLOP_INJECT_IMPORT */
export {
/* PLOP_INJECT_EXPORT */
}

View File

@@ -1,207 +0,0 @@
module.exports = (plop) => {
plop.setGenerator("component", {
description: "Create a component",
prompts: [
{
type: "input",
name: "name",
message: "What is your component name?",
},
],
actions: [
{
type: "add",
path: "src/app/components/{{pascalCase name}}/{{pascalCase name}}.tsx",
templateFile: "plop-templates/Component/Component.tsx.hbs",
},
{
type: "add",
path: "src/app/components/{{pascalCase name}}/{{pascalCase name}}.test.ts",
templateFile: "plop-templates/Component/Component.test.ts.hbs",
},
{
type: "add",
path: "src/app/components/{{pascalCase name}}/{{pascalCase name}}.component.scss",
templateFile: "plop-templates/Component/Component.component.scss.hbs",
},
{
type: "add",
path: "src/app/components/{{pascalCase name}}/index.ts",
templateFile: "plop-templates/Component/index.ts.hbs",
},
{
type: "add",
path: "src/app/components/index.ts",
templateFile: "plop-templates/injectable-index.ts.hbs",
skipIfExists: true,
},
{
type: "append",
path: "src/app/components/index.ts",
pattern: `/* PLOP_INJECT_IMPORT */`,
template: `import {{pascalCase name}} from "./{{pascalCase name}}"`,
},
{
type: "append",
path: "src/app/components/index.ts",
pattern: `/* PLOP_INJECT_EXPORT */`,
template: `\t{{pascalCase name}},`,
},
],
})
plop.setGenerator("atom", {
description: "Create a component",
prompts: [
{
type: "input",
name: "name",
message: "What is your component name?",
},
],
actions: [
{
type: "add",
path: "src/app/components/atoms/{{pascalCase name}}/{{pascalCase name}}.tsx",
templateFile: "plop-templates/Component/Component.tsx.hbs",
},
{
type: "add",
path: "src/app/components/atoms/{{pascalCase name}}/{{pascalCase name}}.test.ts",
templateFile: "plop-templates/Component/Component.test.ts.hbs",
},
{
type: "add",
path: "src/app/components/atoms/{{pascalCase name}}/{{pascalCase name}}.component.scss",
templateFile: "plop-templates/Component/Component.component.scss.hbs",
},
{
type: "add",
path: "src/app/components/atoms/{{pascalCase name}}/index.ts",
templateFile: "plop-templates/Component/index.ts.hbs",
},
{
type: "add",
path: "src/app/components/index.ts",
templateFile: "plop-templates/injectable-index.ts.hbs",
skipIfExists: true,
},
{
type: "append",
path: "src/app/components/index.ts",
pattern: `/* PLOP_INJECT_IMPORT */`,
template: `import {{pascalCase name}} from "./atoms/{{pascalCase name}}"`,
},
{
type: "append",
path: "src/app/components/index.ts",
pattern: `/* PLOP_INJECT_EXPORT */`,
template: `\t{{pascalCase name}},`,
},
],
})
plop.setGenerator("molecule", {
description: "Create a rich component",
prompts: [
{
type: "input",
name: "name",
message: "What is your component name?",
},
],
actions: [
{
type: "add",
path: "src/app/components/molecules/{{pascalCase name}}/{{pascalCase name}}.tsx",
templateFile: "plop-templates/Component/Component.tsx.hbs",
},
{
type: "add",
path: "src/app/components/molecules/{{pascalCase name}}/{{pascalCase name}}.test.ts",
templateFile: "plop-templates/Component/Component.test.ts.hbs",
},
{
type: "add",
path: "src/app/components/molecules/{{pascalCase name}}/{{pascalCase name}}.component.scss",
templateFile: "plop-templates/Component/Component.component.scss.hbs",
},
{
type: "add",
path: "src/app/components/molecules/{{pascalCase name}}/index.ts",
templateFile: "plop-templates/Component/index.ts.hbs",
},
{
type: "add",
path: "src/app/components/index.ts",
templateFile: "plop-templates/injectable-index.ts.hbs",
skipIfExists: true,
},
{
type: "append",
path: "src/app/components/index.ts",
pattern: `/* PLOP_INJECT_IMPORT */`,
template: `import {{pascalCase name}} from "./molecules/{{pascalCase name}}"`,
},
{
type: "append",
path: "src/app/components/index.ts",
pattern: `/* PLOP_INJECT_EXPORT */`,
template: `\t{{pascalCase name}},`,
},
],
})
plop.setGenerator("page", {
description: "Create a page",
prompts: [
{
type: "input",
name: "name",
message: "What is your page name?",
},
],
actions: [
{
type: "add",
path: "src/app/pages/{{pascalCase name}}/{{pascalCase name}}.tsx",
templateFile: "plop-templates/Page/Page.tsx.hbs",
},
{
type: "add",
path: "src/app/pages/{{pascalCase name}}/{{pascalCase name}}.test.ts",
templateFile: "plop-templates/Page/Page.test.ts.hbs",
},
{
type: "add",
path: "src/app/pages/{{pascalCase name}}/{{pascalCase name}}.module.scss",
templateFile: "plop-templates/Page/Page.module.scss.hbs",
},
{
type: "add",
path: "src/app/pages/{{pascalCase name}}/index.ts",
templateFile: "plop-templates/Page/index.ts.hbs",
},
{
type: "add",
path: "src/app/pages/{{pascalCase name}}/{{pascalCase name}}.slice.ts",
templateFile: "plop-templates/Page/Page.slice.ts.hbs",
},
{
type: "add",
path: "src/app/pages/index.ts",
templateFile: "plop-templates/injectable-index.ts.hbs",
skipIfExists: true,
},
{
type: "append",
path: "src/app/pages/index.ts",
pattern: `/* PLOP_INJECT_IMPORT */`,
template: `import {{pascalCase name}} from "./{{pascalCase name}}"`,
},
{
type: "append",
path: "src/app/pages/index.ts",
pattern: `/* PLOP_INJECT_EXPORT */`,
template: `\t{{pascalCase name}},`,
},
],
})
}

View File

@@ -3,12 +3,14 @@ import {
ArButtonVariants,
ArPlacement,
ArSizes,
ArThemes,
FunctionType,
Task,
TaskListProps,
} from "@armco/types"
import { Button, SidePanel } from ".."
import "./TaskList.component.scss"
import { useTheme } from "@armco/utils/hooks"
import { Button, SidePanel } from "@armco/components"
import TaskViewer from "./TaskViewer"
interface TaskItemProps {
setSelectedTask: FunctionType
@@ -17,11 +19,13 @@ interface TaskItemProps {
const TaskItem = (props: TaskItemProps) => {
const { setSelectedTask, task } = props
const { theme } = useTheme()
return (
<Button
variant={ArButtonVariants.LINK}
onClick={() => setSelectedTask(task)}
content={task.name}
color={theme === ArThemes.DARK1 ? "white" : "black"}
/>
)
}
@@ -72,7 +76,7 @@ const TaskList = (props: TaskListProps): JSX.Element => {
) : (
<>
<div className="ar-TaskList__no-tasks-prompt m-3 flex-grow-1">
<small className="ar-TaskList__no-tasks-help-text d-inline-block mb-3">
<small className="ar-TaskList__no-tasks-help-text d-block mb-3">
You have not created any tasks yet, create your first one now.
</small>
<Button
@@ -88,16 +92,17 @@ const TaskList = (props: TaskListProps): JSX.Element => {
{taskViewerDisplayed && (
<SidePanel
placement={ArPlacement.RIGHT}
componentName="TaskViewer"
componentProps={{
hideSelf: () => {
setSelectedTask(undefined)
displayTaskViewer(false)
},
isNew: !selectedTask,
selectedTask,
taskFetcher,
}}
component={
<TaskViewer
hideSelf={() => {
setSelectedTask(undefined)
displayTaskViewer(false)
}}
isNew={!selectedTask}
selectedTask={selectedTask}
taskFetcher={taskFetcher}
/>
}
/>
)}
</div>

View File

@@ -1,17 +1,20 @@
import { ArButtonVariants, TaskLoginPromptProps } from "@armco/types"
import { Button, LoadableIcon, setRightPanelContent, useAppDispatch } from ".."
import { usePanelContent } from "@armco/utils/hooks"
import { Button, Icon } from "@armco/components"
import "./TaskLoginPrompt.component.scss"
const TaskLoginPrompt = (props: TaskLoginPromptProps): JSX.Element => {
const dispatch = useAppDispatch()
const { setPanelContent: setRightPanelContent } = usePanelContent(false)
return (
<div className="ar-TaskLoginPrompt p-3 border container">
<div className="flex-h-center flex-column">
<h6 className="flex-v-center" style={{ color: "#ffbf00" }}>
<LoadableIcon
classes="me-2"
<Icon
icon="fa/FaExclamationTriangle"
color="#ffbf00"
attributes={{
classes: "me-2",
colors: { fillColor: "#ffbf00" },
}}
/>
Please login to continue
</h6>
@@ -51,7 +54,7 @@ const TaskLoginPrompt = (props: TaskLoginPromptProps): JSX.Element => {
variant={ArButtonVariants.SUCCESS}
postIcon="io5/IoArrowForwardCircle"
onClick={() => {
dispatch(setRightPanelContent({ name: "LoginProvider" }))
setRightPanelContent({ name: "LoginProvider" })
}}
/>
</div>

View File

@@ -5,31 +5,27 @@ import {
ArButtonVariants,
ArSizes,
CronType,
JobStatus,
ArJobStatus,
ObjectType,
Task,
TaskViewerProps,
User,
} from "@armco/types"
import { API_CONFIG, ENDPOINTS } from "@armco/configs/endpoints"
import { useUser, useNotification } from "@armco/utils/hooks"
import { post, put } from "@armco/utils/network"
import { validateTask } from "@armco/utils/validators"
import {
API_CONFIG,
Button,
Card,
Checkbox,
CronTab,
DatePicker,
Dropdown,
ENDPOINTS,
getUser,
LoadableIcon,
Network,
notify,
Icon,
TextArea,
TextInput,
useAppDispatch,
useAppSelector,
Validator,
} from ".."
} from "@armco/components"
import "./TaskViewer.component.scss"
const emptyTask = (user?: User) => ({
@@ -37,19 +33,19 @@ const emptyTask = (user?: User) => ({
schedule: { cron: "" },
target: { endpoint: "" },
name: "",
status: JobStatus.STOPPED,
status: ArJobStatus.STOPPED,
})
const TaskViewer = (props: TaskViewerProps): JSX.Element => {
const { classes, hideSelf, isNew, selectedTask, taskFetcher } = props
const user = useAppSelector<User | undefined>(getUser)
const { user } = useUser()
const { notify } = useNotification()
const [formData, setFormData] = useState<
| {
[key: string]: string | number | boolean | CronType
}
| Task
>({})
const dispatch = useAppDispatch()
useEffect(() => {
if (selectedTask) {
@@ -85,8 +81,8 @@ const TaskViewer = (props: TaskViewerProps): JSX.Element => {
? formData.target
: (formData as Task).target.endpoint) as string,
}
const operation = isNew ? Network.post : Network.put
if (Validator.validateTask(payload)) {
const operation = isNew ? post : put
if (validateTask(payload)) {
operation(
API_CONFIG.TASKER[process.env.NODE_ENV] +
ENDPOINTS.TASKER.ROOT +
@@ -95,50 +91,42 @@ const TaskViewer = (props: TaskViewerProps): JSX.Element => {
)
.then((res) => {
if (res.status === (isNew ? 201 : 200)) {
dispatch(
notify({
show: true,
message: `${
isNew ? "Registered" : "Updated"
} cron task successfully`,
type: ArAlertType.SUCCESS,
uid: uuid(),
}),
)
notify({
show: true,
message: `${
isNew ? "Registered" : "Updated"
} cron task successfully`,
type: ArAlertType.SUCCESS,
uid: uuid(),
})
taskFetcher()
hideSelf()
}
})
.catch((e) => {
if (e.status === 409) {
dispatch(
notify({
show: true,
message: "Job with give name already exists!",
type: ArAlertType.ERROR,
uid: uuid(),
}),
)
notify({
show: true,
message: "Job with give name already exists!",
type: ArAlertType.ERROR,
uid: uuid(),
})
} else {
dispatch(
notify({
show: true,
message: "Failed to register the task",
type: ArAlertType.ERROR,
uid: uuid(),
}),
)
notify({
show: true,
message: "Failed to register the task",
type: ArAlertType.ERROR,
uid: uuid(),
})
}
})
} else {
dispatch(
notify({
show: true,
message: "Please review and fix the task details",
type: ArAlertType.ERROR,
uid: uuid(),
}),
)
notify({
show: true,
message: "Please review and fix the task details",
type: ArAlertType.ERROR,
uid: uuid(),
})
}
}
@@ -148,10 +136,14 @@ const TaskViewer = (props: TaskViewerProps): JSX.Element => {
classes ? " " + classes : ""
}`}
>
<LoadableIcon
classes="ar-TaskViewer__close-button position-absolute"
<Icon
icon="io5/IoClose"
onClick={hideSelf}
attributes={{
classes: "ar-TaskViewer__close-button position-absolute",
}}
events={{
onClick: hideSelf,
}}
/>
<header className="mb-3 fw-bold h6">Task Details</header>
<Card>

68
src/Tasks.tsx Executable file
View File

@@ -0,0 +1,68 @@
import { useEffect, useState } from "react"
import { v4 as uuid } from "uuid"
import { ArAlertType, FunctionType, Task } from "@armco/types"
import { ErrorBoundary, Footer, Main, Modal } from "@armco/components"
import { API_CONFIG, ENDPOINTS } from "@armco/configs/endpoints"
import {
useLoggedIn,
useModalState,
useNotification,
usePanelContent,
} from "@armco/utils/hooks"
import { get } from "@armco/utils/network"
import TaskList from "./TaskList"
import TaskLoginPrompt from "./TaskLoginPrompt"
const fetchTasks = (cb: FunctionType, notify: FunctionType) => {
get(
API_CONFIG.TASKER[process.env.NODE_ENV] +
ENDPOINTS.TASKER.ROOT +
ENDPOINTS.TASKER.FETCHALL,
)
.then((res) => {
if (res.status === 200) {
cb(res.body.jobs)
}
})
.catch((e) => {
notify({
show: true,
message: "Failed to fetch your tasks",
type: ArAlertType.ERROR,
uid: uuid(),
})
})
}
const Tasks = (): JSX.Element => {
const { isLoggedIn } = useLoggedIn()
const { panelContent: rightPanelContent } = usePanelContent(false)
const { modalState } = useModalState()
const { notify } = useNotification()
const [tasks, setTasks] = useState<Array<Task>>()
useEffect(() => {
isLoggedIn && fetchTasks(setTasks, notify)
}, [isLoggedIn, notify])
return (
<div className="ar-TasksPage">
<Main
contentClasses="p-2"
mainContent={
<ErrorBoundary>
{isLoggedIn ? (
<TaskList list={tasks} taskFetcher={fetchTasks} />
) : (
<TaskLoginPrompt />
)}
</ErrorBoundary>
}
rightPanelContent={rightPanelContent}
/>
<Modal {...modalState} />
</div>
)
}
export default Tasks

View File

@@ -1,19 +1,17 @@
import React from "react"
import ReactDOM from "react-dom/client"
import { BrowserRouter } from "react-router-dom"
import { Provider } from "react-redux"
import { store } from "./app/store"
import Router from "./app/Router"
import "./app/static/styles/global.scss"
import { ArProvider } from "@armco/utils/providers"
import Tasks from "./Tasks"
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
root.render(
<React.StrictMode>
<BrowserRouter>
<Provider store={store}>
<Router />
</Provider>
<ArProvider>
<Tasks />
</ArProvider>
</BrowserRouter>
</React.StrictMode>,
)

View File

@@ -1,12 +0,0 @@
import { useRoutes } from "react-router-dom"
import * as pages from "./pages"
import Helper from "./utils/helper"
import ROUTES from "./routes"
Helper.populateComponentsInRoutes(ROUTES, pages)
interface RouterProps {}
const Router = (props: RouterProps): JSX.Element | null => useRoutes(ROUTES)
export default Router

View File

@@ -1,3 +0,0 @@
.ar-TaskList {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import TaskList from "./TaskList"
describe("TaskList", () => {
it("renders without error", () => {
})
})

View File

@@ -1,3 +0,0 @@
import TaskList from "./TaskList"
export default TaskList

View File

@@ -1,8 +0,0 @@
import React from "react"
import TaskLoginPrompt from "./TaskLoginPrompt"
describe("TaskLoginPrompt", () => {
it("renders without error", () => {
})
})

View File

@@ -1,3 +0,0 @@
import TaskLoginPrompt from "./TaskLoginPrompt"
export default TaskLoginPrompt

View File

@@ -1,8 +0,0 @@
import React from "react"
import TaskViewer from "./TaskViewer"
describe("TaskViewer", () => {
it("renders without error", () => {
})
})

View File

@@ -1,3 +0,0 @@
import TaskViewer from "./TaskViewer"
export default TaskViewer

View File

@@ -1,6 +0,0 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
import type { RootState, AppDispatch } from "./store"
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

View File

@@ -1,3 +0,0 @@
.c-Home {
}

View File

@@ -1,18 +0,0 @@
import { createSlice } from "@reduxjs/toolkit"
export interface HomeState {}
const initialState: HomeState = {}
export const homeSlice = createSlice({
name: "home",
initialState,
reducers: {
increment: (state) => {},
},
extraReducers: (builder) => {},
})
export const { increment } = homeSlice.actions
export default homeSlice.reducer

View File

@@ -1,8 +0,0 @@
import React from "react"
import Home from "./Home"
describe("Home", () => {
it("renders without error", () => {
})
})

View File

@@ -1,14 +0,0 @@
import React from "react"
import "./Home.module.scss"
interface HomeProps {}
const Home = props => {
return (
<div className="c-Home">
In Page Home
</div>
)
}
export default Home

View File

@@ -1,3 +0,0 @@
import Home from "./Home.jsx"
export default Home

View File

@@ -1,7 +0,0 @@
/* PLOP_INJECT_IMPORT */
import Home from "./Home"
export {
/* PLOP_INJECT_EXPORT */
Home,
}

View File

@@ -1,9 +0,0 @@
const ROUTES = [
{
path: "/",
class: "landing",
element: "Home",
},
]
export default ROUTES

View File

@@ -1,18 +0,0 @@
html, body, #root {
height: 100%;
width: 100%;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -1,17 +0,0 @@
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"
import counterReducer from "../features/counter/counterSlice"
export const store = configureStore({
reducer: {
counter: counterReducer,
},
})
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>

View File

@@ -1,6 +0,0 @@
interface RouteConfig {
path: String
class?: String
element: String | JSX.Element | null
children?: Array<RouteConfig>
}

View File

@@ -1,15 +0,0 @@
class Helper {
static populateComponentsInRoutes(routes: RouteConfig[], components: any) {
routes &&
routes.forEach((route) => {
const Component: JSX.ElementType =
components[route.element as keyof object]
route.element = <Component />
if (route.children) {
Helper.populateComponentsInRoutes(route.children, components)
}
})
}
}
export default Helper

View File

@@ -1,67 +0,0 @@
import { useState } from "react"
import { useAppSelector, useAppDispatch } from "../../app/hooks"
import {
decrement,
increment,
incrementByAmount,
incrementAsync,
incrementIfOdd,
selectCount,
} from "./counterSlice"
export function Counter() {
const count = useAppSelector(selectCount)
const dispatch = useAppDispatch()
const [incrementAmount, setIncrementAmount] = useState("2")
const incrementValue = Number(incrementAmount) || 0
return (
<div>
<div className="row">
<button
className="button"
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
<span className="value">{count}</span>
<button
className="button"
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
</div>
<div className="row">
<input
className="textbox"
aria-label="Set increment amount"
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
className="button"
onClick={() => dispatch(incrementByAmount(incrementValue))}
>
Add Amount
</button>
<button
className="asyncButton"
onClick={() => dispatch(incrementAsync(incrementValue))}
>
Add Async
</button>
<button
className="button"
onClick={() => dispatch(incrementIfOdd(incrementValue))}
>
Add If Odd
</button>
</div>
</div>
)
}

View File

@@ -1,6 +0,0 @@
// A mock function to mimic making an async request for data
export function fetchCount(amount = 1) {
return new Promise<{ data: number }>((resolve) =>
setTimeout(() => resolve({ data: amount }), 500),
)
}

View File

@@ -1,34 +0,0 @@
import counterReducer, {
CounterState,
increment,
decrement,
incrementByAmount,
} from "./counterSlice"
describe("counter reducer", () => {
const initialState: CounterState = {
value: 3,
status: "idle",
}
it("should handle initial state", () => {
expect(counterReducer(undefined, { type: "unknown" })).toEqual({
value: 0,
status: "idle",
})
})
it("should handle increment", () => {
const actual = counterReducer(initialState, increment())
expect(actual.value).toEqual(4)
})
it("should handle decrement", () => {
const actual = counterReducer(initialState, decrement())
expect(actual.value).toEqual(2)
})
it("should handle incrementByAmount", () => {
const actual = counterReducer(initialState, incrementByAmount(2))
expect(actual.value).toEqual(5)
})
})

View File

@@ -1,84 +0,0 @@
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"
import { RootState, AppThunk } from "../../app/store"
import { fetchCount } from "./counterAPI"
export interface CounterState {
value: number
status: "idle" | "loading" | "failed"
}
const initialState: CounterState = {
value: 0,
status: "idle",
}
// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
"counter/fetchCount",
async (amount: number) => {
const response = await fetchCount(amount)
// The value we return becomes the `fulfilled` action payload
return response.data
},
)
export const counterSlice = createSlice({
name: "counter",
initialState,
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn"t actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
// The `extraReducers` field lets the slice handle actions defined elsewhere,
// including actions generated by createAsyncThunk or in other slices.
extraReducers: (builder) => {
builder
.addCase(incrementAsync.pending, (state) => {
state.status = "loading"
})
.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = "idle"
state.value += action.payload
})
.addCase(incrementAsync.rejected, (state) => {
state.status = "failed"
})
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they"re used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state: RootState) => state.counter.value
// We can also write thunks by hand, which may contain both sync and async logic.
// Here"s an example of conditionally dispatching actions based on current state.
export const incrementIfOdd =
(amount: number): AppThunk =>
(dispatch, getState) => {
const currentValue = selectCount(getState())
if (currentValue % 2 === 1) {
dispatch(incrementByAmount(amount))
}
}
export default counterSlice.reducer

View File

@@ -4,7 +4,7 @@
declare namespace NodeJS {
interface ProcessEnv {
readonly NODE_ENV: "development" | "production" | "test"
readonly NODE_ENV: "development" | "production"
readonly PUBLIC_URL: string
}
}

View File

@@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom"

View File

@@ -21,6 +21,5 @@
"jsx": "react-jsx"
},
"include": [
"src"
]
"src" ]
}

42
vite-dev.config.ts Normal file
View File

@@ -0,0 +1,42 @@
import { resolve } from "path"
import { glob } from "glob"
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import dts from "vite-plugin-dts"
import { libInjectCss } from "vite-plugin-lib-inject-css"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
build: {
outDir: "build",
lib: {
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|Test).{ts,tsx}")),
},
sourcemap: true,
rollupOptions: {
treeshake: true,
external: [
new RegExp("react*"),
new RegExp("highcharts*"),
new RegExp("@armco/*"),
"d3",
"uuid",
],
output: [
{
format: "es",
dir: "build/es",
entryFileNames: "[name].js",
chunkFileNames: "[name]-chunk.js",
},
{
format: "cjs",
dir: "build/cjs",
entryFileNames: "[name].js",
chunkFileNames: "[name]-chunk.js",
},
],
},
},
})

26
vite-run.config.ts Normal file
View File

@@ -0,0 +1,26 @@
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import { config } from "dotenv"
config()
// https://vitejs.dev/config/
export default defineConfig({
define: {
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
},
plugins: [react()],
server: {
open: true,
},
build: {
outDir: "build",
sourcemap: true,
},
test: {
globals: true,
environment: "jsdom",
setupFiles: "src/setupTests",
mockReset: true,
},
})

View File

@@ -1,20 +1,41 @@
import { resolve } from "path"
import { glob } from "glob"
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import dts from "vite-plugin-dts"
import { libInjectCss } from "vite-plugin-lib-inject-css"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
open: true,
},
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
build: {
outDir: "build",
sourcemap: true,
},
test: {
globals: true,
environment: "jsdom",
setupFiles: "src/setupTests",
mockReset: true,
lib: {
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|Test).{ts,tsx}")),
},
rollupOptions: {
treeshake: true,
external: [
new RegExp("react*"),
new RegExp("highcharts*"),
new RegExp("@armco/*"),
"d3",
"uuid",
],
output: [
{
format: "es",
dir: "build/es",
entryFileNames: "[name].js",
chunkFileNames: "[name]-chunk.js",
},
{
format: "cjs",
dir: "build/cjs",
entryFileNames: "[name].js",
chunkFileNames: "[name]-chunk.js",
},
],
},
},
})