@unitedstatespowersquadrons/components 1.3.11 → 1.3.12
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/.github/workflows/main.yml +3 -3
- package/Admin/AdminItem.tsx +43 -0
- package/Admin/Impersonation.tsx +141 -0
- package/Admin/handlers.tsx +67 -0
- package/Admin/index.tsx +4 -0
- package/Admin/types.ts +10 -0
- package/index.tsx +2 -0
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
steps:
|
|
19
19
|
- name: Checkout Code
|
|
20
|
-
uses: actions/checkout@
|
|
20
|
+
uses: actions/checkout@v6
|
|
21
21
|
- name: Install Packages
|
|
22
22
|
run: yarn
|
|
23
23
|
- name: Run Eslint
|
|
@@ -26,7 +26,7 @@ jobs:
|
|
|
26
26
|
runs-on: ubuntu-latest
|
|
27
27
|
steps:
|
|
28
28
|
- name: Checkout Code
|
|
29
|
-
uses: actions/checkout@
|
|
29
|
+
uses: actions/checkout@v6
|
|
30
30
|
- name: Install Packages
|
|
31
31
|
run: yarn
|
|
32
32
|
- name: Run TSC
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
runs-on: ubuntu-latest
|
|
36
36
|
steps:
|
|
37
37
|
- name: Checkout Code
|
|
38
|
-
uses: actions/checkout@
|
|
38
|
+
uses: actions/checkout@v6
|
|
39
39
|
- name: Install Packages
|
|
40
40
|
run: yarn
|
|
41
41
|
- name: Run Tests
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import { OnMouseMoveHandler } from "../types";
|
|
5
|
+
import Styles from "../Styles";
|
|
6
|
+
|
|
7
|
+
const useStyles = createUseStyles({
|
|
8
|
+
adminMenuItem: {
|
|
9
|
+
"&:not(:last-child)": {
|
|
10
|
+
padding: "0.5em 0.1em",
|
|
11
|
+
},
|
|
12
|
+
...Styles.flexRow,
|
|
13
|
+
borderRadius: "0.5em",
|
|
14
|
+
justifyContent: "left",
|
|
15
|
+
width: "100%",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
className?: string;
|
|
22
|
+
onClick?: OnMouseMoveHandler;
|
|
23
|
+
onMouseEnter?: OnMouseMoveHandler;
|
|
24
|
+
onMouseLeave?: OnMouseMoveHandler;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const AdminItem = (props: Props) => {
|
|
28
|
+
const { children, className, onClick, onMouseEnter, onMouseLeave } = props;
|
|
29
|
+
const classes = useStyles();
|
|
30
|
+
|
|
31
|
+
return(
|
|
32
|
+
<div
|
|
33
|
+
className={classNames(classes.adminMenuItem, className)}
|
|
34
|
+
onClick={onClick}
|
|
35
|
+
onMouseEnter={onMouseEnter}
|
|
36
|
+
onMouseLeave={onMouseLeave}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default AdminItem;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import React, { useRef } from "react";
|
|
2
|
+
import { createUseStyles } from "react-jss";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import AdminItem from "./AdminItem";
|
|
5
|
+
import { AdminAction, Impersonation as ImpersonationType } from "./types";
|
|
6
|
+
import ActionButton from "../ActionButton";
|
|
7
|
+
import FontAwesomeIcon from "../FontAwesomeIcon";
|
|
8
|
+
import IconButton from "../IconButton";
|
|
9
|
+
import Styles from "../Styles";
|
|
10
|
+
import { OnClickHandler, OnClickOrKeyboardHandler } from "../types";
|
|
11
|
+
|
|
12
|
+
const useStyles = createUseStyles({
|
|
13
|
+
button: { paddingRight: "0.75em !important" },
|
|
14
|
+
center: {
|
|
15
|
+
"& svg": { margin: "0 auto" },
|
|
16
|
+
justifyContent: "center",
|
|
17
|
+
},
|
|
18
|
+
gray: { ...Styles.gray },
|
|
19
|
+
impersonation: {
|
|
20
|
+
animation: "300ms ease-in",
|
|
21
|
+
display: "flex",
|
|
22
|
+
flexDirection: "column",
|
|
23
|
+
gap: "0.25em",
|
|
24
|
+
paddingBottom: "0.5em",
|
|
25
|
+
},
|
|
26
|
+
inactive: {
|
|
27
|
+
"& > div": {
|
|
28
|
+
display: "none",
|
|
29
|
+
opacity: "0",
|
|
30
|
+
transition: "opacity 0.5s ease-in-out",
|
|
31
|
+
},
|
|
32
|
+
"& > svg": { display: "block" },
|
|
33
|
+
"&:hover > div": {
|
|
34
|
+
display: "flex",
|
|
35
|
+
opacity: "1",
|
|
36
|
+
},
|
|
37
|
+
"&:hover > svg": { display: "none" },
|
|
38
|
+
},
|
|
39
|
+
left: { textAlign: "left" },
|
|
40
|
+
viewAsIcon: {
|
|
41
|
+
...Styles.purple,
|
|
42
|
+
fontSize: "2em",
|
|
43
|
+
},
|
|
44
|
+
viewingAsIcon: {
|
|
45
|
+
"&:hover": { ...Styles.hover.red },
|
|
46
|
+
...Styles.red,
|
|
47
|
+
fontSize: "1.5em",
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
interface Hooks {
|
|
52
|
+
useImpersonation: () => { impersonation?: ImpersonationType | null };
|
|
53
|
+
useDispatch: () => (action: AdminAction) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const buildImpersonation = (hooks: Hooks) => {
|
|
57
|
+
const { useImpersonation, useDispatch } = hooks;
|
|
58
|
+
|
|
59
|
+
const Impersonation = () => {
|
|
60
|
+
const { impersonation } = useImpersonation();
|
|
61
|
+
const dispatch = useDispatch();
|
|
62
|
+
const classes = useStyles();
|
|
63
|
+
|
|
64
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
65
|
+
|
|
66
|
+
const handleImpersonate: OnClickOrKeyboardHandler = event => {
|
|
67
|
+
if ("key" in event && event.key !== "Enter") { return; }
|
|
68
|
+
|
|
69
|
+
if (!inputRef.current) return;
|
|
70
|
+
|
|
71
|
+
const certificate = inputRef.current.value;
|
|
72
|
+
|
|
73
|
+
dispatch({ certificate, type: "impersonate" });
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleCancel: OnClickHandler = () => {
|
|
77
|
+
dispatch({ type: "cancelImpersonation" });
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (impersonation) {
|
|
81
|
+
const { certificate, name } = impersonation;
|
|
82
|
+
|
|
83
|
+
return(
|
|
84
|
+
<AdminItem className={classes.impersonation} key="impersonating">
|
|
85
|
+
<div className="bold">Viewing As:</div>
|
|
86
|
+
<IconButton
|
|
87
|
+
className={classes.viewingAsIcon}
|
|
88
|
+
icon="user-group"
|
|
89
|
+
mode="duotone"
|
|
90
|
+
onClick={handleCancel}
|
|
91
|
+
title="Cancel Impersonation"
|
|
92
|
+
/>
|
|
93
|
+
<div>{name}</div>
|
|
94
|
+
<div className={classes.gray}>{certificate}</div>
|
|
95
|
+
</AdminItem>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const collapsed = (
|
|
100
|
+
<FontAwesomeIcon
|
|
101
|
+
css={classes.viewAsIcon}
|
|
102
|
+
icon="user-group"
|
|
103
|
+
mode="duotone"
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const expanded = (
|
|
108
|
+
<div className={classNames(classes.impersonation, classes.center)}>
|
|
109
|
+
<div className="bold">View As:</div>
|
|
110
|
+
<FontAwesomeIcon css={classes.viewAsIcon} icon="user-group" mode="duotone" />
|
|
111
|
+
<input
|
|
112
|
+
maxLength={7}
|
|
113
|
+
onKeyDown={handleImpersonate}
|
|
114
|
+
placeholder="Certificate"
|
|
115
|
+
ref={inputRef}
|
|
116
|
+
type="text"
|
|
117
|
+
/>
|
|
118
|
+
<ActionButton
|
|
119
|
+
className={classes.button}
|
|
120
|
+
color="purple"
|
|
121
|
+
icon="shuffle"
|
|
122
|
+
mode="duotone"
|
|
123
|
+
onClick={handleImpersonate}
|
|
124
|
+
size="small"
|
|
125
|
+
text="Impersonate"
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
return(
|
|
131
|
+
<AdminItem className={classes.inactive}>
|
|
132
|
+
{collapsed}
|
|
133
|
+
{expanded}
|
|
134
|
+
</AdminItem>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return Impersonation;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export default buildImpersonation;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { AdminAction } from "./types";
|
|
2
|
+
import { AppActionType, DispatchFunc, DispatchHandler, GenericAppAction, wrapDispatch } from "../Reducer";
|
|
3
|
+
|
|
4
|
+
// Callers' AppAction union must include AdminAction members. We don't encode
|
|
5
|
+
// that in the generic (TS can't express "A is a supertype of AdminAction"
|
|
6
|
+
// without distribution headaches), so we narrow via type predicates at
|
|
7
|
+
// runtime instead of casting.
|
|
8
|
+
|
|
9
|
+
const isImpersonate = (action: GenericAppAction): action is Extract<AdminAction, { type: "impersonate" }> =>
|
|
10
|
+
action.type === "impersonate";
|
|
11
|
+
|
|
12
|
+
export const wrapImpersonate = <A extends GenericAppAction>(dispatch: DispatchFunc<A>): DispatchFunc<A> => {
|
|
13
|
+
const actionType = "impersonate" as AppActionType<A>;
|
|
14
|
+
|
|
15
|
+
const handler: DispatchHandler<A> = action => {
|
|
16
|
+
if (!isImpersonate(action)) return;
|
|
17
|
+
|
|
18
|
+
const { certificate } = action;
|
|
19
|
+
|
|
20
|
+
return { bodyJson: { certificate }, href: "/admin/impersonate" };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return wrapDispatch(dispatch, [actionType], handler);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const wrapCancelImpersonation = <A extends GenericAppAction>(dispatch: DispatchFunc<A>): DispatchFunc<A> => {
|
|
27
|
+
const actionType = "cancelImpersonation" as AppActionType<A>;
|
|
28
|
+
|
|
29
|
+
const handler: DispatchHandler<A> = action => {
|
|
30
|
+
if (action.type !== actionType) return;
|
|
31
|
+
|
|
32
|
+
return { href: "/admin/cancel", method: "DELETE" };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return wrapDispatch(dispatch, [actionType], handler);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const wrapExpand = <A extends GenericAppAction>(dispatch: DispatchFunc<A>): DispatchFunc<A> => {
|
|
39
|
+
const actionType = "expand" as AppActionType<A>;
|
|
40
|
+
|
|
41
|
+
const handler: DispatchHandler<A> = action => {
|
|
42
|
+
if (action.type !== actionType) return;
|
|
43
|
+
|
|
44
|
+
return { href: "/admin/expand" };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return wrapDispatch(dispatch, [actionType], handler);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const wrapCollapse = <A extends GenericAppAction>(dispatch: DispatchFunc<A>): DispatchFunc<A> => {
|
|
51
|
+
const actionType = "collapse" as AppActionType<A>;
|
|
52
|
+
|
|
53
|
+
const handler: DispatchHandler<A> = action => {
|
|
54
|
+
if (action.type !== actionType) return;
|
|
55
|
+
|
|
56
|
+
return { href: "/admin/collapse" };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return wrapDispatch(dispatch, [actionType], handler);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const adminMenuHandlers = {
|
|
63
|
+
wrapCancelImpersonation,
|
|
64
|
+
wrapCollapse,
|
|
65
|
+
wrapExpand,
|
|
66
|
+
wrapImpersonate,
|
|
67
|
+
};
|
package/Admin/index.tsx
ADDED
package/Admin/types.ts
ADDED
package/index.tsx
CHANGED