@stanlemon/app-template 0.2.5 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app.js CHANGED
@@ -5,19 +5,21 @@ import {
5
5
  } from "@stanlemon/server-with-auth";
6
6
  import { Low, JSONFile } from "lowdb";
7
7
 
8
+ const adapter = new JSONFile("./db.json");
9
+
8
10
  const app = createAppServer({
9
11
  webpack: "http://localhost:8080",
10
12
  secure: ["/api/"],
11
- ...new SimpleUsersDao(),
13
+ ...new SimpleUsersDao([], adapter),
12
14
  });
13
15
 
14
- const db = new Low(new JSONFile("./db.json"));
16
+ const db = new Low(adapter);
15
17
  await db.read();
16
18
  db.data.items ||= [];
17
19
 
18
20
  app.get(
19
21
  "/api/items",
20
- handler(() => ({ items: db.data.items }))
22
+ handler(() => db.data.items)
21
23
  );
22
24
 
23
25
  app.post(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stanlemon/app-template",
3
- "version": "0.2.5",
3
+ "version": "0.2.9",
4
4
  "description": "A template for creating apps using the webdev package.",
5
5
  "author": "Stan Lemon <stanlemon@users.noreply.github.com>",
6
6
  "license": "MIT",
@@ -20,12 +20,13 @@
20
20
  "dependencies": {
21
21
  "@stanlemon/server-with-auth": "0.1.4",
22
22
  "@stanlemon/webdev": "*",
23
- "react": "^18.0.0",
24
- "react-dom": "^18.0.0"
23
+ "react": "^18.1.0",
24
+ "react-dom": "^18.1.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@testing-library/react": "^13.1.1",
28
- "@types/react": "^18.0.5",
29
- "@types/react-dom": "^18.0.1"
28
+ "@testing-library/user-event": "^14.1.1",
29
+ "@types/react": "^18.0.8",
30
+ "@types/react-dom": "^18.0.3"
30
31
  }
31
32
  }
package/src/App.test.tsx CHANGED
@@ -7,40 +7,67 @@ import {
7
7
  } from "@testing-library/react";
8
8
  import userEvent from "@testing-library/user-event";
9
9
  import App from "./App";
10
+ import { SessionContext } from "./Session";
10
11
 
11
- global.fetch = jest.fn(() =>
12
- Promise.resolve({
12
+ const output = ["item one", "item two"];
13
+ global.fetch = jest.fn((url, opts: { method: string; body: string }) => {
14
+ if (opts.method === "post") {
15
+ output.push(JSON.parse(opts.body) as string);
16
+ }
17
+ return Promise.resolve({
13
18
  ok: true,
14
- json: () =>
15
- Promise.resolve({
16
- token: "token",
17
- user: {
18
- name: "Test Tester",
19
- email: "test@test.com",
20
- username: "test",
21
- password: "password",
22
- },
23
- }),
24
- })
25
- ) as jest.Mock;
19
+ json: () => Promise.resolve(output),
20
+ });
21
+ }) as jest.Mock;
26
22
 
27
23
  test("<App/>", async () => {
28
24
  act(() => {
29
- render(<App />);
25
+ render(
26
+ <SessionContext.Provider
27
+ value={{
28
+ session: {
29
+ token: "abcd",
30
+ user: {
31
+ username: "user",
32
+ password: "password",
33
+ name: "user",
34
+ email: "user@example.com",
35
+ },
36
+ },
37
+ setSession: () => {},
38
+ }}
39
+ >
40
+ <App />
41
+ </SessionContext.Provider>
42
+ );
30
43
  });
31
44
 
32
- // A fetch request will be made, and then the page will be initialized, wait for that
33
- await waitFor(() => {
34
- // The header is present
35
- expect(screen.getByRole("heading")).toHaveTextContent("Hello World!");
36
- });
45
+ // The auth text is present
46
+ expect(screen.getByText("You logged in as user")).toBeInTheDocument();
47
+
48
+ // The header is present
49
+ expect(
50
+ screen.getByRole("heading", { name: "Hello World!" })
51
+ ).toBeInTheDocument();
52
+
53
+ expect(
54
+ await screen.findByText("item one", { selector: "li" })
55
+ ).toBeInTheDocument();
56
+
57
+ expect(
58
+ await screen.findByText("item two", { selector: "li" })
59
+ ).toBeInTheDocument();
37
60
 
38
61
  // Type some data into the input
39
- await userEvent.type(screen.getByLabelText("Item"), "The first item");
62
+ await userEvent.type(screen.getByLabelText("Item"), "item three");
40
63
 
41
64
  // Click the add button
42
- fireEvent.click(screen.getByText("Add", { selector: "button" }));
65
+ act(() => {
66
+ fireEvent.click(screen.getByText("Add", { selector: "button" }));
67
+ });
43
68
 
44
69
  // Now we should have a list item with the text we entered
45
- expect(screen.getByRole("listitem")).toHaveTextContent("The first item");
70
+ expect(
71
+ await screen.findByText("item three", { selector: "li" })
72
+ ).toBeInTheDocument();
46
73
  });
package/src/App.tsx CHANGED
@@ -1,15 +1,11 @@
1
- import { useState, useEffect, createContext } from "react";
1
+ import { useState, useContext, useEffect } from "react";
2
2
  import "./App.less";
3
+ import { SessionContext } from "./Session";
3
4
  import Header from "./Header";
4
5
  import Input from "./Input";
5
6
  import Login from "./Login";
6
7
  import Register from "./Register";
7
8
 
8
- export const SessionContext = createContext<{
9
- session: Session | null;
10
- setSession: React.Dispatch<React.SetStateAction<Session | null>>;
11
- } | null>(null);
12
-
13
9
  export type ErrorMessage = {
14
10
  message: string;
15
11
  };
@@ -18,68 +14,35 @@ export type FormErrors = {
18
14
  errors: Record<string, string>;
19
15
  };
20
16
 
21
- export type Session = {
22
- token: string | null;
23
- user: User | null;
24
- };
25
-
26
- export type User = {
27
- name: string | null;
28
- email: string | null;
29
- username: string;
30
- password: string;
31
- };
32
-
33
17
  export default function App() {
34
- const [initialized, setInitialized] = useState<boolean>(false);
35
- const [session, setSession] = useState<Session | null>(null);
36
18
  const [value, setValue] = useState<string>("");
37
19
  const [items, setItems] = useState<string[]>([]);
38
20
 
39
- const contextValue = { session, setSession };
21
+ const { session } = useContext(SessionContext);
40
22
 
23
+ const itemsJson = JSON.stringify(items);
41
24
  useEffect(() => {
42
- fetch("/auth/session", {
43
- headers: {
44
- Authorization: `Bearer ${session?.token || ""}`,
45
- Accept: "application/json",
46
- "Content-Type": "application/json",
47
- },
48
- })
49
- .then((response) => {
50
- setInitialized(true);
51
-
52
- if (!response.ok) {
53
- throw new Error(response.statusText);
54
- }
55
- return response;
25
+ fetchApi("/api/items", session?.token || "")
26
+ .then((items: string[]) => {
27
+ setItems(items);
56
28
  })
57
- .then((response) => response.json())
58
- .then((session: Session) => {
59
- setSession(session);
60
- })
61
- .catch((err) => {
62
- console.error(err);
63
- });
64
- }, [session?.token, initialized]);
29
+ .catch((err) => console.error(err));
30
+ }, [itemsJson, session?.token]);
65
31
 
66
32
  const addItem = () => {
67
- setItems([...items, value]);
68
33
  setValue("");
69
- };
70
34
 
71
- if (!initialized) {
72
- return (
73
- <div>
74
- <em>Loading...</em>
75
- </div>
76
- );
77
- }
35
+ fetchApi("/api/items", session?.token || "", "post", value)
36
+ .then((items: string[]) => {
37
+ setItems(items);
38
+ })
39
+ .catch((err) => console.error(err));
40
+ };
78
41
 
79
42
  return (
80
- <SessionContext.Provider value={contextValue}>
43
+ <>
81
44
  <Header />
82
- {!session && (
45
+ {!session.user && (
83
46
  <Row>
84
47
  <Column>
85
48
  <h2>Login</h2>
@@ -91,10 +54,10 @@ export default function App() {
91
54
  </Column>
92
55
  </Row>
93
56
  )}
94
- {session?.user && (
57
+ {session.user && (
95
58
  <>
96
59
  <p>
97
- <em>You logged in as {session.user.username}.</em>
60
+ <em>You logged in as {session.user?.username}</em>
98
61
  </p>
99
62
  <Input
100
63
  label="Item"
@@ -111,7 +74,7 @@ export default function App() {
111
74
  </ul>
112
75
  </>
113
76
  )}
114
- </SessionContext.Provider>
77
+ </>
115
78
  );
116
79
  }
117
80
 
@@ -144,3 +107,27 @@ function Column({ children }: { children: React.ReactNode }) {
144
107
  </div>
145
108
  );
146
109
  }
110
+
111
+ function fetchApi(
112
+ url: string,
113
+ token: string,
114
+ method = "get",
115
+ data?: any
116
+ ): Promise<any> {
117
+ return fetch(url, {
118
+ method: method,
119
+ headers: {
120
+ Authorization: `Bearer ${token}`,
121
+ Accept: "application/json",
122
+ "Content-Type": "application/json",
123
+ },
124
+ body: JSON.stringify(data),
125
+ })
126
+ .then((response) => {
127
+ if (!response.ok) {
128
+ throw new Error(response.statusText);
129
+ }
130
+ return response;
131
+ })
132
+ .then((response) => response.json());
133
+ }
package/src/Login.tsx CHANGED
@@ -1,19 +1,18 @@
1
1
  import { useState, useContext } from "react";
2
- import { Session, User, SessionContext, ErrorMessage } from "./App";
2
+ import { ErrorMessage } from "./App";
3
+ import { SessionContext, SessionData, UserData } from "./Session";
3
4
  import Input from "./Input";
4
5
 
5
6
  export default function Login() {
6
7
  const [error, setError] = useState<string | null>(null);
7
- const [values, setValues] = useState<User>({
8
+ const [values, setValues] = useState<UserData>({
8
9
  name: "",
9
10
  email: "",
10
11
  username: "",
11
12
  password: "",
12
13
  });
13
14
 
14
- const { setSession } = useContext(SessionContext) || {
15
- setSession: () => {},
16
- };
15
+ const { setSession } = useContext(SessionContext);
17
16
 
18
17
  const onSubmit = () => {
19
18
  setError(null);
@@ -43,7 +42,7 @@ export default function Login() {
43
42
  data: Record<string, unknown>;
44
43
  }) => {
45
44
  if (ok) {
46
- setSession(data as Session);
45
+ setSession(data as SessionData);
47
46
  } else {
48
47
  setError((data as ErrorMessage).message);
49
48
  }
package/src/Register.tsx CHANGED
@@ -1,20 +1,19 @@
1
1
  import { useState, useContext } from "react";
2
- import { Session, User, SessionContext, FormErrors } from "./App";
2
+ import { FormErrors } from "./App";
3
+ import { SessionData, UserData, SessionContext } from "./Session";
3
4
  import Input from "./Input";
4
5
 
5
6
  // eslint-disable-next-line max-lines-per-function
6
7
  export default function Register() {
7
8
  const [errors, setErrors] = useState<Record<string, string>>({});
8
- const [values, setValues] = useState<User>({
9
+ const [values, setValues] = useState<UserData>({
9
10
  name: "",
10
11
  email: "",
11
12
  username: "",
12
13
  password: "",
13
14
  });
14
15
 
15
- const { setSession } = useContext(SessionContext) || {
16
- setSession: () => {},
17
- };
16
+ const { setSession } = useContext(SessionContext);
18
17
 
19
18
  const onSubmit = () => {
20
19
  setErrors({});
@@ -44,7 +43,7 @@ export default function Register() {
44
43
  data: Record<string, unknown>;
45
44
  }) => {
46
45
  if (ok) {
47
- setSession(data as Session);
46
+ setSession(data as SessionData);
48
47
  } else {
49
48
  setErrors((data as FormErrors).errors);
50
49
  }
@@ -0,0 +1,73 @@
1
+ import React, { useState, useEffect, createContext } from "react";
2
+
3
+ export const SessionContext = createContext<{
4
+ session: SessionData;
5
+ setSession: React.Dispatch<React.SetStateAction<SessionData>>;
6
+ }>({
7
+ session: {
8
+ token: null,
9
+ user: null,
10
+ },
11
+ setSession: () => {},
12
+ });
13
+
14
+ export type SessionData = {
15
+ token: string | null;
16
+ user: UserData | null;
17
+ };
18
+
19
+ export type UserData = {
20
+ name: string | null;
21
+ email: string | null;
22
+ username: string;
23
+ password: string;
24
+ };
25
+
26
+ export default function Session({ children }: { children: React.ReactChild }) {
27
+ const [initialized, setInitialized] = useState<boolean>(false);
28
+ const [session, setSession] = useState<SessionData>({
29
+ token: null,
30
+ user: null,
31
+ });
32
+
33
+ useEffect(() => {
34
+ fetch("/auth/session", {
35
+ headers: {
36
+ Authorization: `Bearer ${session.token || ""}`,
37
+ Accept: "application/json",
38
+ "Content-Type": "application/json",
39
+ },
40
+ })
41
+ .then((response) => {
42
+ setInitialized(true);
43
+
44
+ if (!response.ok) {
45
+ throw new Error(response.statusText);
46
+ }
47
+ return response;
48
+ })
49
+ .then((response) => response.json())
50
+ .then((session: SessionData) => {
51
+ setSession(session);
52
+ })
53
+ .catch((err) => {
54
+ console.error(err);
55
+ });
56
+ }, [session?.token, initialized]);
57
+
58
+ if (!initialized) {
59
+ return (
60
+ <div>
61
+ <em>Loading...</em>
62
+ </div>
63
+ );
64
+ }
65
+
66
+ const contextValue = { session, setSession };
67
+
68
+ return (
69
+ <SessionContext.Provider value={contextValue}>
70
+ {children}
71
+ </SessionContext.Provider>
72
+ );
73
+ }
package/src/index.tsx CHANGED
@@ -1,10 +1,15 @@
1
1
  import { createRoot } from "react-dom/client";
2
2
  import App from "./App";
3
+ import Session from "./Session";
3
4
 
4
5
  const root = createRoot(
5
6
  document.body.appendChild(document.createElement("div"))
6
7
  );
7
- root.render(<App />);
8
+ root.render(
9
+ <Session>
10
+ <App />
11
+ </Session>
12
+ );
8
13
 
9
14
  const link = document.createElement("link");
10
15
  link.setAttribute("rel", "stylesheet");