@stanlemon/app-template 0.3.20 → 0.3.22

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.
@@ -0,0 +1,84 @@
1
+ import { useState, useEffect, useContext } from "react";
2
+ import { SessionContext } from "../Session";
3
+ import { Input, Row, Spacer } from "../components/";
4
+ import { fetchApi } from "../helpers/fetchApi";
5
+
6
+ export type ItemData = {
7
+ id: string;
8
+ item: string;
9
+ };
10
+
11
+ export function Items() {
12
+ const { token, setError } = useContext(SessionContext);
13
+ const [items, setItems] = useState<ItemData[]>([]);
14
+ const [value, setValue] = useState<string>("");
15
+
16
+ const catchError = (err: Error) => {
17
+ setError(err.message);
18
+ };
19
+
20
+ const saveItem = (item: string) => {
21
+ fetchApi<ItemData[], { item: string }>("/api/items", token, "post", {
22
+ item,
23
+ })
24
+ .then((items) => {
25
+ setItems(items);
26
+ })
27
+ .catch(catchError);
28
+ };
29
+
30
+ const deleteItem = (id: string) => {
31
+ fetchApi<ItemData[], string>(`/api/items/${id}`, token, "delete")
32
+ .then((items) => {
33
+ setItems(items);
34
+ })
35
+ .catch(catchError);
36
+ };
37
+
38
+ const addItem = () => {
39
+ if (value.trim() === "") {
40
+ return;
41
+ }
42
+ saveItem(value);
43
+ setValue("");
44
+ };
45
+
46
+ useEffect(() => {
47
+ fetchApi<ItemData[], null>("/api/items", token)
48
+ .then((items) => {
49
+ setItems(items ?? []);
50
+ })
51
+ .catch(catchError);
52
+ }, []);
53
+
54
+ return (
55
+ <>
56
+ <h2>New Item</h2>
57
+ <Input
58
+ label="Item"
59
+ name="item"
60
+ value={value}
61
+ onChange={(value) => setValue(value)}
62
+ onEnter={addItem}
63
+ />
64
+ <button onClick={addItem}>Add</button>
65
+ <Spacer />
66
+ <h2>My Items</h2>
67
+ <ul style={{ padding: 0 }}>
68
+ {items.map(({ item, id }, i) => (
69
+ <Row key={i} as="li">
70
+ <button
71
+ style={{ marginLeft: "auto", order: 2 }}
72
+ onClick={() => deleteItem(id)}
73
+ >
74
+ Delete
75
+ </button>
76
+ <div>{item}</div>
77
+ </Row>
78
+ ))}
79
+ </ul>
80
+ </>
81
+ );
82
+ }
83
+
84
+ export default Items;
@@ -0,0 +1,57 @@
1
+ import { useState, useContext } from "react";
2
+ import { Input, Spacer } from "../components/";
3
+ import { ProfileData, SessionContext, SessionData } from "../Session";
4
+ import fetchApi, { ApiError } from "../helpers/fetchApi";
5
+
6
+ export type LoginForm = {
7
+ username: string;
8
+ password: string;
9
+ };
10
+
11
+ export function Login() {
12
+ const [values, setValues] = useState<LoginForm>({
13
+ username: "",
14
+ password: "",
15
+ });
16
+
17
+ const { setToken, setUser, setError } = useContext(SessionContext);
18
+
19
+ const onSubmit = () => {
20
+ setError(null);
21
+ fetchApi<SessionData, LoginForm>("/auth/login", null, "post", values)
22
+ .then((session: SessionData) => {
23
+ setToken(session.token as string);
24
+ setUser(session.user as ProfileData);
25
+ setError(null);
26
+ })
27
+ .catch((err: ApiError) => {
28
+ if (err.message === "Unauthorized") {
29
+ setError(err.body.message as string);
30
+ }
31
+ });
32
+ };
33
+
34
+ return (
35
+ <>
36
+ <Input
37
+ name="username"
38
+ label="Username"
39
+ value={values.username}
40
+ onChange={(value) => setValues({ ...values, username: value })}
41
+ autoCapitalize="off"
42
+ />
43
+ <Input
44
+ name="password"
45
+ type="password"
46
+ label="Password"
47
+ value={values.password}
48
+ onChange={(value) => setValues({ ...values, password: value })}
49
+ onEnter={onSubmit}
50
+ />
51
+ <Spacer />
52
+ <button onClick={onSubmit}>Login</button>
53
+ </>
54
+ );
55
+ }
56
+
57
+ export default Login;
@@ -0,0 +1,89 @@
1
+ import { useContext, useState } from "react";
2
+ import { SessionContext } from "../Session";
3
+ import { Header, Input } from "../components";
4
+ import fetchApi, { ApiError } from "../helpers/fetchApi";
5
+
6
+ export type PasswordForm = {
7
+ current_password: string;
8
+ new_password1: string;
9
+ new_password2: string;
10
+ };
11
+
12
+ export type PasswordRequest = {
13
+ current_password: string;
14
+ password: string;
15
+ };
16
+
17
+ export const DEFAULT_PASSWORD_DATA: PasswordForm = {
18
+ current_password: "",
19
+ new_password1: "",
20
+ new_password2: "",
21
+ };
22
+
23
+ export function Password() {
24
+ const { token, setMessage, setError } = useContext(SessionContext);
25
+ const [password, setPassword] = useState<PasswordForm>(DEFAULT_PASSWORD_DATA);
26
+ const [errors, setErrors] = useState<Partial<PasswordForm>>({});
27
+
28
+ const storePassword = (key: keyof PasswordForm, value: string) => {
29
+ setPassword({ ...password, [key]: value });
30
+ };
31
+ const savePassword = () => {
32
+ setErrors({});
33
+
34
+ if (password.new_password1 !== password.new_password2) {
35
+ setErrors({ new_password2: "Passwords do not match" });
36
+ return;
37
+ }
38
+
39
+ fetchApi<null, PasswordRequest>("/auth/password", token, "post", {
40
+ current_password: password.current_password,
41
+ password: password.new_password1,
42
+ })
43
+ .then(() => {
44
+ setPassword(DEFAULT_PASSWORD_DATA);
45
+ setErrors({});
46
+ setMessage("Password updated.");
47
+ setError(null);
48
+ })
49
+ .catch((err: ApiError) => {
50
+ if (err.code === 400) {
51
+ setErrors({ ...(err.body.errors as PasswordForm) });
52
+ }
53
+ });
54
+ };
55
+
56
+ return (
57
+ <>
58
+ <Header level={2}>Password</Header>
59
+ <Input
60
+ name="current_password"
61
+ type="password"
62
+ label="Current Password"
63
+ value={password.current_password}
64
+ error={errors.current_password}
65
+ onChange={(value) => storePassword("current_password", value)}
66
+ />
67
+ <Input
68
+ name="new_password1"
69
+ type="password"
70
+ label="New Password"
71
+ value={password.new_password1}
72
+ error={errors.new_password1}
73
+ onChange={(value) => storePassword("new_password1", value)}
74
+ />
75
+ <Input
76
+ name="new_password2"
77
+ type="password"
78
+ label="Repeat New Password"
79
+ value={password.new_password2}
80
+ error={errors.new_password2}
81
+ onChange={(value) => storePassword("new_password2", value)}
82
+ />
83
+
84
+ <button onClick={savePassword}>Update</button>
85
+ </>
86
+ );
87
+ }
88
+
89
+ export default Password;
@@ -0,0 +1,66 @@
1
+ import { useContext, useState } from "react";
2
+ import { ProfileData, SessionContext } from "../Session";
3
+ import { Header, Input } from "../components";
4
+ import fetchApi, { ApiError } from "../helpers/fetchApi";
5
+
6
+ export type ProfileForm = {
7
+ name: string;
8
+ email: string;
9
+ };
10
+ export const DEFAULT_PROFILE_DATA: ProfileForm = {
11
+ name: "",
12
+ email: "",
13
+ };
14
+
15
+ export function Profile() {
16
+ const { token, user, setUser, setMessage } = useContext(SessionContext);
17
+ const [errors, setErrors] = useState<Partial<ProfileForm>>({});
18
+ const [profile, setProfile] = useState<ProfileForm>({
19
+ ...DEFAULT_PROFILE_DATA,
20
+ name: user?.name ?? "",
21
+ email: user?.email ?? "",
22
+ });
23
+
24
+ const storeProfile = (key: keyof ProfileForm, value: string) => {
25
+ setProfile({ ...profile, [key]: value });
26
+ };
27
+
28
+ const saveProfile = () => {
29
+ setErrors({});
30
+ fetchApi<ProfileData, ProfileForm>("/auth/user", token, "put", profile)
31
+ .then((newProfile) => {
32
+ setProfile(newProfile);
33
+ setUser(newProfile);
34
+ setErrors({});
35
+ setMessage("Profile saved.");
36
+ })
37
+ .catch((err: ApiError) => {
38
+ if (err.code === 400) {
39
+ setErrors({ ...(err.body.errors as ProfileForm) });
40
+ }
41
+ });
42
+ };
43
+
44
+ return (
45
+ <>
46
+ <Header level={2}>Profile</Header>
47
+ <Input
48
+ name="name"
49
+ label="Name"
50
+ value={profile.name}
51
+ error={errors.name}
52
+ onChange={(value) => storeProfile("name", value)}
53
+ />
54
+ <Input
55
+ name="email"
56
+ label="Email"
57
+ value={profile.email}
58
+ error={errors.email}
59
+ onChange={(value) => storeProfile("email", value)}
60
+ />
61
+ <button onClick={saveProfile}>Save</button>
62
+ </>
63
+ );
64
+ }
65
+
66
+ export default Profile;
@@ -0,0 +1,82 @@
1
+ import { useState, useContext } from "react";
2
+ import { Input, Spacer } from "../components/";
3
+ import { SessionContext, ProfileData, SessionData } from "../Session";
4
+ import fetchApi, { ApiError } from "../helpers/fetchApi";
5
+
6
+ export type FormErrors = {
7
+ errors: Record<string, string>;
8
+ };
9
+
10
+ export type SignUpForm = {
11
+ name: string;
12
+ email: string;
13
+ username: string;
14
+ password: string;
15
+ };
16
+
17
+ export function SignUp() {
18
+ const [errors, setErrors] = useState<Record<string, string>>({});
19
+ const [values, setValues] = useState<SignUpForm>({
20
+ name: "",
21
+ email: "",
22
+ username: "",
23
+ password: "",
24
+ });
25
+
26
+ const { setToken, setUser, setError } = useContext(SessionContext);
27
+
28
+ const onSubmit = () => {
29
+ setErrors({});
30
+ fetchApi<SessionData, SignUpForm>("/auth/signup", null, "post", values)
31
+ .then((session: SessionData) => {
32
+ setToken(session.token as string);
33
+ setUser(session.user as ProfileData);
34
+ })
35
+ .catch((err: ApiError) => {
36
+ if (err.message !== "Unauthorized") {
37
+ setError(err.body.message as string);
38
+ }
39
+ });
40
+ };
41
+
42
+ return (
43
+ <>
44
+ <Input
45
+ name="username"
46
+ label="Username"
47
+ value={values.username}
48
+ onChange={(value) => setValues({ ...values, username: value })}
49
+ error={errors?.username}
50
+ autoCapitalize="off"
51
+ />
52
+ <Input
53
+ name="name"
54
+ label="Name"
55
+ value={values.name}
56
+ onChange={(value) => setValues({ ...values, name: value })}
57
+ error={errors?.name}
58
+ autoCapitalize="off"
59
+ />
60
+ <Input
61
+ name="email"
62
+ label="Email"
63
+ value={values.email}
64
+ onChange={(value) => setValues({ ...values, email: value })}
65
+ error={errors?.email}
66
+ autoCapitalize="off"
67
+ />
68
+ <Input
69
+ name="password"
70
+ type="password"
71
+ label="Password"
72
+ value={values.password}
73
+ onChange={(value) => setValues({ ...values, password: value })}
74
+ error={errors?.password}
75
+ />
76
+ <Spacer />
77
+ <button onClick={onSubmit}>Sign Up</button>
78
+ </>
79
+ );
80
+ }
81
+
82
+ export default SignUp;
@@ -0,0 +1,24 @@
1
+ import { useContext, useEffect } from "react";
2
+ import fetchApi, { ApiError } from "../helpers/fetchApi";
3
+ import { SessionContext } from "../Session";
4
+
5
+ export function Verify({ token = null }: { token: string | null }) {
6
+ const { setError, setMessage } = useContext(SessionContext);
7
+
8
+ useEffect(() => {
9
+ fetchApi(`/auth/verify/${token}`)
10
+ .then((res) => {
11
+ setMessage("Successfully verified your account!");
12
+ })
13
+ .catch((err: ApiError) => {
14
+ if (err.message === "Bad Request") {
15
+ setError(err.body.message as string);
16
+ console.error(err, err.message, err.body);
17
+ }
18
+ });
19
+ }, []);
20
+
21
+ return <></>;
22
+ }
23
+
24
+ export default Verify;
@@ -0,0 +1,7 @@
1
+ export * from "./Login";
2
+ export * from "./SignUp";
3
+ export * from "./Items";
4
+ export * from "./Account";
5
+ export * from "./Password";
6
+ export * from "./Profile";
7
+ export * from "./Verify";
package/src/Header.jsx DELETED
@@ -1,4 +0,0 @@
1
- // While typescript is preferred, you can also use good ol javascript too!
2
- export default function Header() {
3
- return <h1>Hello World!</h1>;
4
- }
package/src/Login.tsx DELETED
@@ -1,82 +0,0 @@
1
- import { useState, useContext } from "react";
2
- import { ErrorResponse, Spacer } from "./App";
3
- import { SessionContext, SessionData, UserData } from "./Session";
4
- import Input from "./Input";
5
-
6
- export default function Login() {
7
- const [error, setError] = useState<string | null>(null);
8
- const [values, setValues] = useState<UserData>({
9
- name: "",
10
- email: "",
11
- username: "",
12
- password: "",
13
- });
14
-
15
- const { setSession } = useContext(SessionContext);
16
-
17
- const onSubmit = () => {
18
- setError(null);
19
- fetch("/auth/login", {
20
- headers: {
21
- Accept: "application/json",
22
- "Content-Type": "application/json",
23
- },
24
- method: "POST",
25
- body: JSON.stringify(values),
26
- })
27
- .then((response) =>
28
- response.json().then((data: Record<string, unknown>) => ({
29
- ok: response.ok,
30
- status: response.status,
31
- data,
32
- }))
33
- )
34
- .then(
35
- ({
36
- ok,
37
- status,
38
- data,
39
- }: {
40
- ok: boolean;
41
- status: number;
42
- data: Record<string, unknown>;
43
- }) => {
44
- if (ok) {
45
- setSession(data as SessionData);
46
- } else {
47
- setError((data as ErrorResponse).message);
48
- }
49
- }
50
- )
51
- .catch((err: Error) => {
52
- setError(err.message);
53
- });
54
- };
55
-
56
- return (
57
- <>
58
- {error && (
59
- <div>
60
- <strong>{error}</strong>
61
- </div>
62
- )}
63
- <Input
64
- name="username"
65
- label="Username"
66
- value={values.username}
67
- onChange={(value) => setValues({ ...values, username: value })}
68
- autoCapitalize="off"
69
- />
70
- <Input
71
- name="password"
72
- type="password"
73
- label="Password"
74
- value={values.password}
75
- onChange={(value) => setValues({ ...values, password: value })}
76
- onEnter={onSubmit}
77
- />
78
- <Spacer />
79
- <button onClick={onSubmit}>Login</button>
80
- </>
81
- );
82
- }
package/src/Register.tsx DELETED
@@ -1,82 +0,0 @@
1
- import { useState, useContext } from "react";
2
- import { FormErrors, ErrorMessage, Spacer } from "./App";
3
- import { SessionData, UserData, SessionContext } from "./Session";
4
- import Input from "./Input";
5
-
6
- // eslint-disable-next-line max-lines-per-function
7
- export default function Register() {
8
- const [error, setError] = useState<string | boolean>(false);
9
- const [errors, setErrors] = useState<Record<string, string>>({});
10
- const [values, setValues] = useState<UserData>({
11
- name: "",
12
- email: "",
13
- username: "",
14
- password: "",
15
- });
16
-
17
- const { setSession } = useContext(SessionContext);
18
-
19
- const onSubmit = () => {
20
- setErrors({});
21
- fetch("/auth/register", {
22
- headers: {
23
- Accept: "application/json",
24
- "Content-Type": "application/json",
25
- },
26
- method: "POST",
27
- body: JSON.stringify(values),
28
- })
29
- .then((response) =>
30
- response.json().then((data: Record<string, unknown>) => ({
31
- ok: response.ok,
32
- status: response.status,
33
- data,
34
- }))
35
- )
36
- .then(
37
- ({
38
- ok,
39
- status,
40
- data,
41
- }: {
42
- ok: boolean;
43
- status: number;
44
- data: Record<string, unknown>;
45
- }) => {
46
- if (ok) {
47
- setSession(data as SessionData);
48
- } else {
49
- setErrors((data as FormErrors).errors);
50
- }
51
- }
52
- )
53
- .catch((err: Error) => {
54
- // Put any generis error onto the username field
55
- setError(err.message);
56
- });
57
- };
58
-
59
- return (
60
- <>
61
- <ErrorMessage error={error} />
62
- <Input
63
- name="username"
64
- label="Username"
65
- value={values.username}
66
- onChange={(value) => setValues({ ...values, username: value })}
67
- error={errors.username}
68
- autoCapitalize="off"
69
- />
70
- <Input
71
- name="password"
72
- type="password"
73
- label="Password"
74
- value={values.password}
75
- onChange={(value) => setValues({ ...values, password: value })}
76
- error={errors.password}
77
- />
78
- <Spacer />
79
- <button onClick={onSubmit}>Register</button>
80
- </>
81
- );
82
- }