initial commit
This commit is contained in:
commit
f0e51571ed
|
@ -0,0 +1,148 @@
|
||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=node
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
### Node Patch ###
|
||||||
|
# Serverless Webpack directories
|
||||||
|
.webpack/
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
# SvelteKit build / generate output
|
||||||
|
.svelte-kit
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/node
|
||||||
|
|
||||||
|
|
||||||
|
*.csv
|
||||||
|
.vite
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
|
||||||
|
<title>Solid App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script src="/src/index.tsx" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "vite-template-solid",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "vite",
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"serve": "vite preview",
|
||||||
|
"test": "vitest"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@solidjs/testing-library": "^0.6.0",
|
||||||
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
|
"@types/testing-library__jest-dom": "^5.14.5",
|
||||||
|
"jsdom": "^21.1.0",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"vite": "^4.1.1",
|
||||||
|
"vite-plugin-solid": "^2.5.0",
|
||||||
|
"vitest": "^0.28.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@solidjs/router": "^0.8.2",
|
||||||
|
"solid-js": "^1.6.10"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { A } from "@solidjs/router";
|
||||||
|
|
||||||
|
export const Navbar = () => {
|
||||||
|
return (
|
||||||
|
<nav>
|
||||||
|
{" "}
|
||||||
|
<A href="/"> Home </A>{" "}
|
||||||
|
<A href="/mail">Mail Management</A>{" "}
|
||||||
|
<A href="/template">Template Management</A>{" "}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { Router, Routes, Route } from "@solidjs/router";
|
||||||
|
import { TemplateManagement } from "./createTemplate";
|
||||||
|
import { HomePage } from "./homePage";
|
||||||
|
import { Navbar } from "./Navbar";
|
||||||
|
import { MailManagement } from "./sendMail";
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<Navbar />
|
||||||
|
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" component={HomePage} />
|
||||||
|
<Route path="/mail" component={MailManagement} />
|
||||||
|
<Route path="/template" component={TemplateManagement} />
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { createEffect, createSignal, For } from "solid-js";
|
||||||
|
|
||||||
|
export const TemplateManagement = () => {
|
||||||
|
const [htmlBody, setHtmlBody] = createSignal("");
|
||||||
|
const [subject, setSubject] = createSignal("");
|
||||||
|
const [templateName, setTemplateName] = createSignal("");
|
||||||
|
|
||||||
|
const [templates, setTemplates] = createSignal<string[]>([]);
|
||||||
|
|
||||||
|
const submitForm = (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
setTemplates(["template1", "template2", "template3"]);
|
||||||
|
}, [templates]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Template Management Page</h1>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<form onsubmit={submitForm}>
|
||||||
|
<h2>Create Template </h2>
|
||||||
|
<div>
|
||||||
|
HTML Template: <br />
|
||||||
|
<textarea
|
||||||
|
value={htmlBody()}
|
||||||
|
onInput={(e) => setHtmlBody(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Subject: <br />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={subject()}
|
||||||
|
onInput={(e) => setSubject(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Template Name: <br />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={templateName()}
|
||||||
|
onInput={(e) => setTemplateName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="submit" value="Submit" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h2>Templates</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<For each={templates()}>{
|
||||||
|
|
||||||
|
(template) => <li>{template}</li>
|
||||||
|
}</For>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const HomePage = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1> Welcome to the SES utility software. </h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { render } from 'solid-js/web';
|
||||||
|
import { Router } from '@solidjs/router';
|
||||||
|
|
||||||
|
import { App } from './app';
|
||||||
|
|
||||||
|
const root = document.getElementById('root');
|
||||||
|
|
||||||
|
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
|
||||||
|
throw new Error(
|
||||||
|
'Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got mispelled?',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(() => <App />, root!);
|
|
@ -0,0 +1,131 @@
|
||||||
|
import { createSignal, For } from "solid-js";
|
||||||
|
|
||||||
|
type TemplateList = Array<Record<string, string>>;
|
||||||
|
|
||||||
|
export const MailManagement = () => {
|
||||||
|
const [templateList, setTemplateList] = createSignal<TemplateList>([]);
|
||||||
|
|
||||||
|
const [receiverKey, setReceiverKey] = createSignal("");
|
||||||
|
|
||||||
|
const [variables, setVariables] = createSignal<string[]>([]);
|
||||||
|
|
||||||
|
const [rawCsv, setRawCsv] = createSignal("");
|
||||||
|
|
||||||
|
const onCsvSubmit = (e: Event) => {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
|
||||||
|
const input = target.files?.[0];
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const text = e.target?.result as string;
|
||||||
|
const lines = text.split("\n");
|
||||||
|
|
||||||
|
const header = lines[0].split(",").map((header) => header.trim());
|
||||||
|
setRawCsv(text);
|
||||||
|
setVariables(header);
|
||||||
|
|
||||||
|
const templates = [];
|
||||||
|
|
||||||
|
for (let i of lines.slice(1)) {
|
||||||
|
if (i.length === 0) continue;
|
||||||
|
|
||||||
|
const row = i.split(",").map((row) => row.trim());
|
||||||
|
const obj: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (let j = 0; j < header.length; j++) {
|
||||||
|
obj[header[j]] = row[j];
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTemplateList(templates);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(input);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendMail = (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
console.log(templateList());
|
||||||
|
|
||||||
|
const receivers = templateList().map((template) => template[receiverKey()]);
|
||||||
|
|
||||||
|
console.log(receivers);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeReceiverColumn = (e: Event) => {
|
||||||
|
const el = e.target as HTMLSelectElement;
|
||||||
|
|
||||||
|
const value = el.value;
|
||||||
|
|
||||||
|
setReceiverKey(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Mail Management Page</h1>
|
||||||
|
|
||||||
|
<form onsubmit={sendMail}>
|
||||||
|
<div>
|
||||||
|
Sender: <br />
|
||||||
|
<input type="text" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Template Name: <br />
|
||||||
|
<input type="text" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Recipient List (CSV Only Accepted): <br />
|
||||||
|
<input type="file" accept=".csv" onChange={onCsvSubmit} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{variables().length > 0 && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
Receiver Email Column: <br />
|
||||||
|
<select onchange={onChangeReceiverColumn}>
|
||||||
|
<For each={variables()}>
|
||||||
|
{(variable) => <option value={variable}>{variable}</option>}
|
||||||
|
</For>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<table border={1}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<For each={variables()}>
|
||||||
|
{(variable) => <th>{variable}</th>}
|
||||||
|
</For>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{templateList().map((template) => (
|
||||||
|
<tr>
|
||||||
|
<For each={variables()}>
|
||||||
|
{(variable) => <td>{template[variable]}</td>}
|
||||||
|
</For>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<input type="submit" value="Send" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { render, fireEvent } from '@solidjs/testing-library';
|
||||||
|
|
||||||
|
import { TodoList } from './todo-list';
|
||||||
|
|
||||||
|
describe('<TodoList />', () => {
|
||||||
|
test('it will render an text input and a button', () => {
|
||||||
|
const { getByPlaceholderText, getByText } = render(() => <TodoList />);
|
||||||
|
expect(getByPlaceholderText('new todo here')).toBeInTheDocument();
|
||||||
|
expect(getByText('Add Todo')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it will add a new todo', async () => {
|
||||||
|
const { getByPlaceholderText, getByText } = render(() => <TodoList />);
|
||||||
|
const input = getByPlaceholderText('new todo here') as HTMLInputElement;
|
||||||
|
const button = getByText('Add Todo');
|
||||||
|
input.value = 'test new todo';
|
||||||
|
fireEvent.click(button as HTMLInputElement);
|
||||||
|
expect(input.value).toBe('');
|
||||||
|
expect(getByText(/test new todo/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it will mark a todo as completed', async () => {
|
||||||
|
const { getByPlaceholderText, findByRole, getByText } = render(() => (
|
||||||
|
<TodoList />
|
||||||
|
));
|
||||||
|
const input = getByPlaceholderText('new todo here') as HTMLInputElement;
|
||||||
|
const button = getByText('Add Todo') as HTMLButtonElement;
|
||||||
|
input.value = 'mark new todo as completed';
|
||||||
|
fireEvent.click(button);
|
||||||
|
const completed = (await findByRole('checkbox')) as HTMLInputElement;
|
||||||
|
expect(completed?.checked).toBe(false);
|
||||||
|
fireEvent.click(completed);
|
||||||
|
expect(completed?.checked).toBe(true);
|
||||||
|
const text = getByText('mark new todo as completed') as HTMLSpanElement;
|
||||||
|
expect(text).toHaveStyle({ 'text-decoration': 'line-through' });
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js",
|
||||||
|
"types": ["vite/client", "@testing-library/jest-dom"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/// <reference types="vitest" />
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import solidPlugin from 'vite-plugin-solid';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [solidPlugin()],
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
globals: true,
|
||||||
|
transformMode: { web: [/\.[jt]sx?$/] },
|
||||||
|
setupFiles: ['node_modules/@testing-library/jest-dom/extend-expect.js'],
|
||||||
|
// otherwise, solid would be loaded twice:
|
||||||
|
deps: { registerNodeLoader: true },
|
||||||
|
// if you have few tests, try commenting one
|
||||||
|
// or both out to improve performance:
|
||||||
|
threads: false,
|
||||||
|
isolate: false,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
target: 'esnext',
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
conditions: ['development', 'browser'],
|
||||||
|
},
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue