From a70a0e018cb6aeaa0b10b6b9beaf65ee4e428d85 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Tue, 5 Sep 2023 15:06:29 -0500
Subject: [PATCH 01/83] Copy to portfolio not Remix
---
cypress/e2e/Portfolio/ShareActivities.cy.js | 8 ++++----
src/Tools/_framework/Paths/PortfolioActivityViewer.jsx | 6 +++---
src/Tools/_framework/Paths/PublicEditor.jsx | 8 ++++----
3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/cypress/e2e/Portfolio/ShareActivities.cy.js b/cypress/e2e/Portfolio/ShareActivities.cy.js
index ce2af6f785..8102834de4 100644
--- a/cypress/e2e/Portfolio/ShareActivities.cy.js
+++ b/cypress/e2e/Portfolio/ShareActivities.cy.js
@@ -106,7 +106,7 @@ describe("Share Activities Using Portfolio", function () {
cy.get('[data-test="heading2"]').contains("Public Course Activities");
cy.go("back");
- cy.get('[data-test="Remix Button"]').click();
+ cy.get('[data-test="Copy to Portfolio Button"]').click();
cy.log("rename the 2nd activity and make it public");
cy.get('[data-test="Controls Button"]').click();
@@ -146,7 +146,7 @@ describe("Share Activities Using Portfolio", function () {
cy.get('[data-test="heading2"]').contains("User Portfolio");
cy.go("back");
- cy.get('[data-test="Remix Button"]').click();
+ cy.get('[data-test="Copy to Portfolio Button"]').click();
cy.log("label the third activity and examine public portfolio info");
@@ -424,7 +424,7 @@ describe("Share Activities Using Portfolio", function () {
cy.get(cesc2("#/_p2")).should("have.text", "Hello, Mom!");
cy.log("Remix");
- cy.get('[data-test="Remix Button"]').click();
+ cy.get('[data-test="Copy to Portfolio Button"]').click();
cy.get(cesc2("#/_p2")).should("have.text", "Hello, !");
cy.get(cesc2("#/draft")).should("not.exist");
@@ -502,7 +502,7 @@ describe("Share Activities Using Portfolio", function () {
cy.get(cesc2("#/_p2")).should("have.text", "Hello, Bro!");
cy.log("Remix");
- cy.get('[data-test="Remix Button"]').click();
+ cy.get('[data-test="Copy to Portfolio Button"]').click();
cy.get(cesc2("#/_p2")).should("have.text", "Hello, !");
cy.get(cesc2("#/draft")).should("have.text", "Draft content");
diff --git a/src/Tools/_framework/Paths/PortfolioActivityViewer.jsx b/src/Tools/_framework/Paths/PortfolioActivityViewer.jsx
index 336fac9dd6..5ffbe8b229 100644
--- a/src/Tools/_framework/Paths/PortfolioActivityViewer.jsx
+++ b/src/Tools/_framework/Paths/PortfolioActivityViewer.jsx
@@ -189,12 +189,12 @@ export function PortfolioActivityViewer() {
@@ -213,7 +213,7 @@ export function PortfolioActivityViewer() {
});
}}
>
- Sign In To Remix
+ Sign In To Copy
)}
diff --git a/src/Tools/_framework/Paths/PublicEditor.jsx b/src/Tools/_framework/Paths/PublicEditor.jsx
index e630107c0e..fe98c84514 100644
--- a/src/Tools/_framework/Paths/PublicEditor.jsx
+++ b/src/Tools/_framework/Paths/PublicEditor.jsx
@@ -248,12 +248,12 @@ export function PublicEditor() {
- This is a public editor. Remix to save changes.
+ This is a public editor. Copy to portfolio to save changes.
{signedIn ? (
) : (
)}
From 8035017eb5d49f5f149a4fe33138e6b550764447 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Wed, 6 Sep 2023 13:28:16 -0500
Subject: [PATCH 02/83] New Add Activity
---
src/Tools/_framework/Paths/Portfolio.jsx | 46 +++++++++++++------
.../RecoilActivityCard.jsx | 34 ++++++++++++--
2 files changed, 60 insertions(+), 20 deletions(-)
diff --git a/src/Tools/_framework/Paths/Portfolio.jsx b/src/Tools/_framework/Paths/Portfolio.jsx
index 5b8f4f9686..cb63cca9ad 100644
--- a/src/Tools/_framework/Paths/Portfolio.jsx
+++ b/src/Tools/_framework/Paths/Portfolio.jsx
@@ -59,9 +59,10 @@ export async function action({ request }) {
if (response.ok) {
let { doenetId, pageDoenetId } = await response.json();
- return redirect(
- `/portfolioeditor/${doenetId}?tool=editor&doenetId=${doenetId}&pageId=${pageDoenetId}`,
- );
+ return { _action: formObj?._action, doenetId, pageDoenetId };
+ // return redirect(
+ // `/portfolioeditor/${doenetId}?tool=editor&doenetId=${doenetId}&pageId=${pageDoenetId}`,
+ // );
} else {
throw Error(response.message);
}
@@ -237,14 +238,29 @@ export function Portfolio() {
let data = useLoaderData();
const [doenetId, setDoenetId] = useState();
const controlsBtnRef = useRef(null);
-
- const navigate = useNavigate();
+ const fetcher = useFetcher();
const {
isOpen: settingsAreOpen,
onOpen: settingsOnOpen,
onClose: settingsOnClose,
} = useDisclosure();
+ const settingsOpenedForDoenetId = useRef(null);
+
+ if (fetcher.state == "loading" && fetcher.data?._action == "Add Activity") {
+ if (fetcher.data.doenetId !== doenetId) {
+ setDoenetId(fetcher.data.doenetId);
+ }
+ } else if (
+ fetcher.state == "idle" &&
+ fetcher.data?._action == "Add Activity"
+ ) {
+ if (!settingsAreOpen && settingsOpenedForDoenetId.current != doenetId) {
+ settingsOpenedForDoenetId.current = doenetId;
+ settingsOnOpen();
+ }
+ }
+
useEffect(() => {
document.title = `Portfolio - Doenet`;
}, []);
@@ -290,16 +306,11 @@ export function Portfolio() {
data-test="Add Activity"
size="xs"
colorScheme="blue"
- onClick={async () => {
- //Create a portfilio activity and redirect to the editor for it
- let response = await fetch("/api/createPortfolioActivity.php");
-
- if (response.ok) {
- let { doenetId, pageDoenetId } = await response.json();
- navigate(`/portfolioeditor/${doenetId}/${pageDoenetId}`);
- } else {
- throw Error(response.message);
- }
+ onClick={() => {
+ fetcher.submit(
+ { _action: "Add Activity", doenetId },
+ { method: "post" },
+ );
}}
>
Add Activity
@@ -368,6 +379,10 @@ export function Portfolio() {
) : (
<>
{data.privateActivities.map((activity) => {
+ let isNewActivity = false;
+ if (settingsOpenedForDoenetId.current == activity.doenetId) {
+ isNewActivity = true;
+ }
return (
);
})}
diff --git a/src/_reactComponents/PanelHeaderComponents/RecoilActivityCard.jsx b/src/_reactComponents/PanelHeaderComponents/RecoilActivityCard.jsx
index 529215a6b0..c089afd645 100644
--- a/src/_reactComponents/PanelHeaderComponents/RecoilActivityCard.jsx
+++ b/src/_reactComponents/PanelHeaderComponents/RecoilActivityCard.jsx
@@ -12,6 +12,9 @@ import {
MenuButton,
Icon,
MenuList,
+ Center,
+ VStack,
+ useTheme,
} from "@chakra-ui/react";
import { GoKebabVertical } from "react-icons/go";
import { Link, useFetcher } from "react-router-dom";
@@ -35,14 +38,11 @@ export default function RecoilActivityCard({
setDoenetId,
onClose,
onOpen,
+ isNewActivity = false,
}) {
const fetcher = useFetcher();
- // const setItemByDoenetId = useSetRecoilState(itemByDoenetId(doenetId));
const { compileActivity, updateAssignItem } = useCourse(courseId);
- // const [recoilPageToolView, setRecoilPageToolView] =
- // useRecoilState(pageToolViewAtom);
-
let navigateTo = useRef("");
if (navigateTo.current != "") {
@@ -52,7 +52,7 @@ export default function RecoilActivityCard({
location.href = newHref;
}
- return (
+ const cardJSX = (
);
+
+ if (isNewActivity) {
+ return (
+
+ {cardJSX}
+
+ NEW
+
+
+ );
+ } else {
+ return <>{cardJSX}>;
+ }
}
From 49e8aaddc9c84430e98aaf3c334fe029d3a9107b Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Wed, 6 Sep 2023 15:11:06 -0500
Subject: [PATCH 03/83] portfolioViewer refactored to publicOverview
---
cypress/e2e/DoenetML/tagSpecific/ref.cy.js | 2 +-
cypress/e2e/Portfolio/ShareActivities.cy.js | 6 +++---
src/Tools/_framework/Panels/NewSupportPanel.jsx | 2 +-
src/Tools/_framework/Paths/Admin.jsx | 2 +-
src/Tools/_framework/Paths/Community.jsx | 6 +++---
.../_framework/Paths/CourseActivityEditor.jsx | 6 +++---
.../_framework/Paths/CourseLinkPageViewer.jsx | 2 +-
src/Tools/_framework/Paths/Home.jsx | 6 +++---
.../_framework/Paths/PortfolioActivityEditor.jsx | 6 +++---
...ewer.jsx => PublicActivityOverviewViewer.jsx} | 2 +-
src/Tools/_framework/Paths/PublicEditor.jsx | 6 +++---
src/Tools/_framework/Paths/PublicPortfolio.jsx | 2 +-
src/Tools/cypressTest/CypressTest.jsx | 2 +-
src/Tools/test/DoenetTest.jsx | 2 +-
src/Viewer/PageViewer.jsx | 2 +-
.../PanelHeaderComponents/Carousel.jsx | 10 ++++++++--
.../PanelHeaderComponents/RecoilActivityCard.jsx | 1 -
src/index.jsx | 16 ++++++++--------
18 files changed, 43 insertions(+), 38 deletions(-)
rename src/Tools/_framework/Paths/{PortfolioActivityViewer.jsx => PublicActivityOverviewViewer.jsx} (99%)
diff --git a/cypress/e2e/DoenetML/tagSpecific/ref.cy.js b/cypress/e2e/DoenetML/tagSpecific/ref.cy.js
index b9ee80cb21..51df33e407 100644
--- a/cypress/e2e/DoenetML/tagSpecific/ref.cy.js
+++ b/cypress/e2e/DoenetML/tagSpecific/ref.cy.js
@@ -181,7 +181,7 @@ describe("ref Tag Tests", function () {
cy.get(cesc("#\\/_ref1"))
.should("have.text", "a Doenet doc")
.invoke("attr", "href")
- .then((href) => expect(href).eq("/portfolioviewer/abcdefg"));
+ .then((href) => expect(href).eq("/publicOverview/abcdefg"));
});
it("url with no link text", () => {
diff --git a/cypress/e2e/Portfolio/ShareActivities.cy.js b/cypress/e2e/Portfolio/ShareActivities.cy.js
index 8102834de4..f861fa3970 100644
--- a/cypress/e2e/Portfolio/ShareActivities.cy.js
+++ b/cypress/e2e/Portfolio/ShareActivities.cy.js
@@ -690,7 +690,7 @@ describe("Share Activities Using Portfolio", function () {
cy.get('[data-test="Viewer Update Button"]').click();
cy.get(cesc2("#/toDoc")).invoke("removeAttr", "target").click();
- cy.url().should("contain", "portfolioviewer");
+ cy.url().should("contain", "publicOverview");
cy.get(cesc2("#/theP")).should("have.text", "Link to this page!");
cy.go("back");
@@ -724,7 +724,7 @@ describe("Share Activities Using Portfolio", function () {
.click();
cy.get(cesc2("#/toDoc")).invoke("removeAttr", "target").click();
- cy.url().should("contain", "portfolioviewer");
+ cy.url().should("contain", "publicOverview");
cy.get(cesc2("#/theP")).should("have.text", "Link to this page!");
cy.go("back");
@@ -749,7 +749,7 @@ describe("Share Activities Using Portfolio", function () {
.click();
cy.get(cesc2("#/toDoc")).invoke("removeAttr", "target").click();
- cy.url().should("contain", "portfolioviewer");
+ cy.url().should("contain", "publicOverview");
cy.get(cesc2("#/theP")).should("have.text", "Link to this page!");
cy.go("back");
diff --git a/src/Tools/_framework/Panels/NewSupportPanel.jsx b/src/Tools/_framework/Panels/NewSupportPanel.jsx
index 364346ee8a..73da9bb966 100644
--- a/src/Tools/_framework/Panels/NewSupportPanel.jsx
+++ b/src/Tools/_framework/Panels/NewSupportPanel.jsx
@@ -165,7 +165,7 @@ export default function SupportPanel({ hide, children }) {
value="Documentation"
onClick={() =>
window.open(
- "https://www.doenet.org/portfolioviewer/_7KL7tiBBS2MhM6k1OrPt4",
+ "https://www.doenet.org/publicOverview/_7KL7tiBBS2MhM6k1OrPt4",
)
}
/>
diff --git a/src/Tools/_framework/Paths/Admin.jsx b/src/Tools/_framework/Paths/Admin.jsx
index 522af03749..99f997b267 100644
--- a/src/Tools/_framework/Paths/Admin.jsx
+++ b/src/Tools/_framework/Paths/Admin.jsx
@@ -80,7 +80,7 @@ export function Admin() {
<>
{publicActivities.map((activity) => {
const { doenetId, label, imagePath } = activity;
- const imageLink = `/portfolioviewer/${doenetId}`;
+ const imageLink = `/publicOverview/${doenetId}`;
return (
{
const { doenetId, imagePath, label, fullName } = activityObj;
//{ activityLink, doenetId, imagePath, label, fullName }
- const imageLink = `/portfolioviewer/${doenetId}`;
+ const imageLink = `/publicOverview/${doenetId}`;
return (
+
+
+
+
+
+
+ {file.fileType == "text/csv" ? (
+ <>DoenetML Name needed to use file>
+ ) : (
+ <>Alt Text Description required to use file>
+ )}
+
+
+ {
+ updateFileDescription({
+ cid: file.cid,
+ description: e?.target?.value,
+ });
+ }}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ updateFileDescription({
+ cid: file.cid,
+ description: e?.target?.value,
+ });
+ }
+ }}
+ />
+
+ {/* Fires on blur */}
+
+ Submit
+
+
+
+
+
+
+
+
+
);
}
+
return (
{/* TODO: Make this editable */}
{
- fetcher.submit(
- {
- _action: "update description",
- description: value,
- doenetId,
- cid: file.cid,
- },
- { method: "post" },
- );
+ updateFileDescription({
+ cid: file.cid,
+ description: value,
+ });
}}
>
- {/*
- {file.description}
- */}
{file.fileType == "text/csv" ? (
<>{file.fileType} >
@@ -1423,6 +1422,7 @@ function PortfolioActivitySettingsDrawer({
//Need fetcher at this level to get label refresh
//when close drawer after changing label
const fetcher = useFetcher();
+ let [alerts, setAlerts] = useState([]);
return (
- {/* */}
Activity Controls
+ {alerts.length > 0 ? (
+
+ ) : (
+
+ )}
@@ -1457,9 +1461,6 @@ function PortfolioActivitySettingsDrawer({
>
Support Files
- {/* (controlsTabsLastIndex.current = 2)}>
- Pages & Orders
- */}
@@ -1470,14 +1471,16 @@ function PortfolioActivitySettingsDrawer({
activityData={activityData}
courseId={courseId}
setPublicAndDraftAreTheSame={setPublicAndDraftAreTheSame}
+ setAlerts={setAlerts}
/>
-
+
- {/*
- Enable Pages & Orders
- */}
From e164b76a68800326ea5017face2f4ef79b3dfc13 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Fri, 13 Oct 2023 11:39:26 -0500
Subject: [PATCH 48/83] Fixed documentation link
---
src/Tools/_framework/Paths/PortfolioActivity.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Tools/_framework/Paths/PortfolioActivity.jsx b/src/Tools/_framework/Paths/PortfolioActivity.jsx
index 112abad95d..f6a0bf0b05 100644
--- a/src/Tools/_framework/Paths/PortfolioActivity.jsx
+++ b/src/Tools/_framework/Paths/PortfolioActivity.jsx
@@ -2073,7 +2073,7 @@ function EditorPanel({
From 395262aadccbc42bbf1abf7dfe4804ad5ef6c09a Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Fri, 13 Oct 2023 11:57:53 -0500
Subject: [PATCH 49/83] Tooltip and disabled edit button in public mode
---
.../_framework/Paths/PortfolioActivity.jsx | 34 ++++++++++++-------
1 file changed, 22 insertions(+), 12 deletions(-)
diff --git a/src/Tools/_framework/Paths/PortfolioActivity.jsx b/src/Tools/_framework/Paths/PortfolioActivity.jsx
index f6a0bf0b05..5dd0ca0a0f 100644
--- a/src/Tools/_framework/Paths/PortfolioActivity.jsx
+++ b/src/Tools/_framework/Paths/PortfolioActivity.jsx
@@ -1746,8 +1746,8 @@ export function PortfolioActivity() {
}
}}
>
-
+
)}
>
@@ -1868,20 +1868,30 @@ function ViewerPanel({
/>
)}
- {editMode || layer == "public" ? (
+ {editMode ? (
) : (
- }
- onClick={() => {
- setSearchParams({ edit: "true" }, { replace: true });
- setEditMode(true);
- }}
+
- Edit
-
+ }
+ onClick={() => {
+ setSearchParams({ edit: "true" }, { replace: true });
+ setEditMode(true);
+ }}
+ >
+ Edit
+
+
)}
From f9c366928e69a3d844866bce66ba87922e8555af Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Fri, 13 Oct 2023 12:17:10 -0500
Subject: [PATCH 50/83] Spinner and disabled until activity is added
---
src/Tools/_framework/Paths/Portfolio.jsx | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/Tools/_framework/Paths/Portfolio.jsx b/src/Tools/_framework/Paths/Portfolio.jsx
index cb63cca9ad..20b80c0ffe 100644
--- a/src/Tools/_framework/Paths/Portfolio.jsx
+++ b/src/Tools/_framework/Paths/Portfolio.jsx
@@ -14,13 +14,13 @@ import {
DrawerContent,
DrawerOverlay,
Drawer,
+ Spinner,
} from "@chakra-ui/react";
import React, { useEffect, useRef, useState } from "react";
import {
redirect,
useOutletContext,
useLoaderData,
- useNavigate,
useFetcher,
} from "react-router-dom";
import styled from "styled-components";
@@ -247,6 +247,8 @@ export function Portfolio() {
const settingsOpenedForDoenetId = useRef(null);
+ const [addingActivity, setAddingActivity] = useState(false);
+
if (fetcher.state == "loading" && fetcher.data?._action == "Add Activity") {
if (fetcher.data.doenetId !== doenetId) {
setDoenetId(fetcher.data.doenetId);
@@ -257,6 +259,7 @@ export function Portfolio() {
) {
if (!settingsAreOpen && settingsOpenedForDoenetId.current != doenetId) {
settingsOpenedForDoenetId.current = doenetId;
+ setAddingActivity(false);
settingsOnOpen();
}
}
@@ -304,16 +307,18 @@ export function Portfolio() {
{
+ setAddingActivity(true);
fetcher.submit(
{ _action: "Add Activity", doenetId },
{ method: "post" },
);
}}
>
- Add Activity
+ Add Activity {addingActivity && }
From d0ad4934789b94060cdf6f0cf3d87e89015d52ed Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Fri, 13 Oct 2023 12:46:15 -0500
Subject: [PATCH 51/83] activity settings for new activity
---
src/Tools/_framework/Paths/Portfolio.jsx | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/src/Tools/_framework/Paths/Portfolio.jsx b/src/Tools/_framework/Paths/Portfolio.jsx
index 20b80c0ffe..007fffc1e9 100644
--- a/src/Tools/_framework/Paths/Portfolio.jsx
+++ b/src/Tools/_framework/Paths/Portfolio.jsx
@@ -178,9 +178,8 @@ function PortfolioSettingsDrawer({
doenetId,
data,
courseId,
+ newActivityDoenetId,
}) {
- // const { pageId, activityData } = useLoaderData();
- // console.log({ doenetId, data });
const fetcher = useFetcher();
let activityData;
if (doenetId) {
@@ -214,7 +213,11 @@ function PortfolioSettingsDrawer({
- Activity Settings
+ {newActivityDoenetId == doenetId ? (
+ Activity Settings For New Activity
+ ) : (
+ Activity Settings
+ )}
@@ -248,10 +251,12 @@ export function Portfolio() {
const settingsOpenedForDoenetId = useRef(null);
const [addingActivity, setAddingActivity] = useState(false);
+ const [newActivityDoenetId, setNewActivityDoenetId] = useState("");
if (fetcher.state == "loading" && fetcher.data?._action == "Add Activity") {
if (fetcher.data.doenetId !== doenetId) {
setDoenetId(fetcher.data.doenetId);
+ setNewActivityDoenetId(fetcher.data.doenetId);
}
} else if (
fetcher.state == "idle" &&
@@ -282,6 +287,7 @@ export function Portfolio() {
doenetId={doenetId}
data={data}
courseId={data.courseId}
+ newActivityDoenetId={newActivityDoenetId}
/>
{data.privateActivities.map((activity) => {
- let isNewActivity = false;
- if (settingsOpenedForDoenetId.current == activity.doenetId) {
- isNewActivity = true;
- }
return (
);
})}
From 3d9a8287e727fdad195dee5c875a4ead6f359ac5 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Fri, 13 Oct 2023 13:12:23 -0500
Subject: [PATCH 52/83] logo goes to portfolio
---
src/Tools/_framework/Paths/PortfolioActivity.jsx | 15 +++++++++++----
src/Tools/_framework/Paths/SiteHeader.jsx | 2 +-
src/Tools/_framework/RouterLogo.jsx | 4 ++--
3 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/src/Tools/_framework/Paths/PortfolioActivity.jsx b/src/Tools/_framework/Paths/PortfolioActivity.jsx
index 5dd0ca0a0f..c901116aaa 100644
--- a/src/Tools/_framework/Paths/PortfolioActivity.jsx
+++ b/src/Tools/_framework/Paths/PortfolioActivity.jsx
@@ -89,6 +89,7 @@ import { useSearchParams } from "react-router-dom";
import { FiBook } from "react-icons/fi";
import Papa from "papaparse";
+import RouterLogo from "../RouterLogo";
export async function loader({ params, request }) {
let doenetId = params.doenetId;
@@ -129,7 +130,7 @@ export async function loader({ params, request }) {
}
const response = await axios.get("/api/getPorfolioCourseId.php");
- let { firstName, lastName, email } = response.data;
+ let { firstName, lastName, email, portfolioCourseId } = response.data;
const draftDoenetMLResponse = await axios.get(
`/media/byPageId/${pageId}.doenet`,
@@ -211,6 +212,7 @@ export async function loader({ params, request }) {
supportingFileData,
editModeInit,
onLoadPublicAndDraftAreTheSame,
+ portfolioCourseId,
};
} catch (e) {
return { success: false, message: e.response.data.message };
@@ -1511,8 +1513,8 @@ export function PortfolioActivity() {
activityData,
editModeInit,
onLoadPublicAndDraftAreTheSame,
+ portfolioCourseId,
} = useLoaderData();
-
// const { signedIn } = useOutletContext();
if (!success) {
@@ -1660,8 +1662,13 @@ export function PortfolioActivity() {
overflow="hidden"
background="doenet.canvas"
>
-
-
+
+
+
+
+
+
+
{!narrowMode && (
diff --git a/src/Tools/_framework/Paths/SiteHeader.jsx b/src/Tools/_framework/Paths/SiteHeader.jsx
index b086bda6ba..893394b07f 100644
--- a/src/Tools/_framework/Paths/SiteHeader.jsx
+++ b/src/Tools/_framework/Paths/SiteHeader.jsx
@@ -137,7 +137,7 @@ export function SiteHeader(props) {
{/*
TABS HERE
*/}
-
+
Doenet
diff --git a/src/Tools/_framework/RouterLogo.jsx b/src/Tools/_framework/RouterLogo.jsx
index 7a8b9b6a03..3f95d1c565 100644
--- a/src/Tools/_framework/RouterLogo.jsx
+++ b/src/Tools/_framework/RouterLogo.jsx
@@ -27,7 +27,7 @@ const LogoButton = styled.button`
}
`;
-export default function RouterLogo({ hasLink = true }) {
+export default function RouterLogo({ to, hasLink = true }) {
let navigate = useNavigate();
return (
@@ -35,7 +35,7 @@ export default function RouterLogo({ hasLink = true }) {
hasLink={hasLink}
onClick={() => {
if (hasLink) {
- navigate("/");
+ navigate(to);
}
}}
/>
From 4ecda36dd37ef2d55a209418808802919f3c48fb Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Fri, 13 Oct 2023 14:46:51 -0500
Subject: [PATCH 53/83] Fixed logo
---
src/Tools/_framework/Paths/PortfolioActivity.jsx | 4 ++--
src/Tools/_framework/RouterLogo.jsx | 3 ++-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/Tools/_framework/Paths/PortfolioActivity.jsx b/src/Tools/_framework/Paths/PortfolioActivity.jsx
index c901116aaa..8d98b57c4f 100644
--- a/src/Tools/_framework/Paths/PortfolioActivity.jsx
+++ b/src/Tools/_framework/Paths/PortfolioActivity.jsx
@@ -1664,9 +1664,9 @@ export function PortfolioActivity() {
>
-
+
-
+
diff --git a/src/Tools/_framework/RouterLogo.jsx b/src/Tools/_framework/RouterLogo.jsx
index 3f95d1c565..a958e3905f 100644
--- a/src/Tools/_framework/RouterLogo.jsx
+++ b/src/Tools/_framework/RouterLogo.jsx
@@ -17,9 +17,10 @@ const LogoButton = styled.button`
border-radius: 10px;
align-items: center;
border-style: none;
+
// border-radius: 50%;
// margin-top: 8px;
- // margin-left: 90px;
+ margin-left: 5px;
cursor: ${(props) => (props.hasLink ? "pointer" : "default")};
&:focus {
outline: 2px solid var(--canvastext);
From 402550a0afd31e67e246519428676e4254fb8176 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Wed, 18 Oct 2023 17:27:10 -0500
Subject: [PATCH 54/83] Redesigned Sign in
---
src/Tools/_framework/NewToolRoot.jsx | 1 -
src/Tools/_framework/Paths/SignIn.jsx | 198 ++++++++++++++++++++++++++
src/index.jsx | 25 +++-
3 files changed, 221 insertions(+), 3 deletions(-)
create mode 100644 src/Tools/_framework/Paths/SignIn.jsx
diff --git a/src/Tools/_framework/NewToolRoot.jsx b/src/Tools/_framework/NewToolRoot.jsx
index 810884e23c..165832fc1e 100644
--- a/src/Tools/_framework/NewToolRoot.jsx
+++ b/src/Tools/_framework/NewToolRoot.jsx
@@ -107,7 +107,6 @@ export default function ToolRoot() {
import("./ToolPanels/PublicActivityViewer"),
),
CourseCards: lazy(() => import("./ToolPanels/CourseCards")),
- SignIn: lazy(() => import("./ToolPanels/SignIn")),
SignOut: lazy(() => import("./ToolPanels/SignOut")),
NavigationPanel: lazy(() => import("./ToolPanels/NavigationPanel")),
Dashboard: lazy(() => import("./ToolPanels/Dashboard")),
diff --git a/src/Tools/_framework/Paths/SignIn.jsx b/src/Tools/_framework/Paths/SignIn.jsx
new file mode 100644
index 0000000000..2ca877e882
--- /dev/null
+++ b/src/Tools/_framework/Paths/SignIn.jsx
@@ -0,0 +1,198 @@
+import {
+ AbsoluteCenter,
+ Box,
+ Button,
+ Card,
+ CardBody,
+ CardFooter,
+ Center,
+ Checkbox,
+ Flex,
+ FormControl,
+ FormErrorMessage,
+ FormLabel,
+ Heading,
+ Image,
+ Input,
+ Stack,
+ Text,
+} from "@chakra-ui/react";
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import { useLoaderData } from "react-router";
+import { useFetcher } from "react-router-dom";
+
+export async function loader({ request, params }) {
+ // const url = new URL(request.url);
+ // const from = url.searchParams.get("from");
+ try {
+ return { success: true };
+ } catch (e) {
+ return { success: false, message: e.response.data.message };
+ }
+}
+
+export async function action({ params, request }) {
+ const formData = await request.formData();
+ let formObj = Object.fromEntries(formData);
+ console.log({ formObj });
+
+ try {
+ if (formObj._action == "submit email") {
+ let { data } = axios.get("/api/sendSignInEmail.php", {
+ params: { email: params.emailAddress },
+ });
+ return {
+ _action: formObj._action,
+ success: true,
+ };
+ }
+
+ return { success: true };
+ } catch (e) {
+ return { success: false, message: e.response.data.message };
+ }
+}
+
+function AskForEmailCard() {
+ const [emailAddress, setEmailAddress] = useState("");
+ const [emailError, setEmailError] = useState(null);
+ const [isChecked, setIsChecked] = useState(false);
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ const fetcher = useFetcher();
+
+ return (
+
+
+
+
+
+
+
+
+
+ Sign In via Email
+
+
+ Email address
+ {
+ let nextValue = e.target.value;
+ //Clear error if email is now good
+ if (emailError != null && emailRegex.test(nextValue)) {
+ setEmailError(null);
+ }
+ setEmailAddress(nextValue);
+ }}
+ />
+ {emailError}
+
+
+ setIsChecked(e.target.checked)}
+ >
+ Stay Signed In
+
+
+
+
+
+ {
+ if (emailAddress == "") {
+ setEmailError("Please enter your email address");
+ } else if (!emailRegex.test(emailAddress)) {
+ setEmailError("Invalid email format");
+ } else {
+ setEmailError(null);
+ //Email is correct
+ fetcher.submit(
+ {
+ _action: "submit email",
+ emailAddress,
+ staySignedIn: isChecked,
+ },
+ { method: "post" },
+ );
+ }
+ }}
+ >
+ Send Email
+
+
+
+
+
+ );
+}
+
+export function SignIn() {
+ const { success } = useLoaderData();
+
+ const [stage, setStage] = useState("Init");
+ console.log(success);
+ // const fetcher = useFetcher();
+ // const setActivityByDoenetId = useSetRecoilState(itemByDoenetId(doenetId)); //TODO: remove after recoil is gone
+ // const setPageByDoenetId = useSetRecoilState(itemByDoenetId(pageId)); //TODO: remove after recoil is gone
+
+ // let location = useLocation();
+
+ // const navigate = useNavigate();
+
+ // const [recoilPageToolView, setRecoilPageToolView] =
+ // useRecoilState(pageToolViewAtom);
+
+ // let navigateTo = useRef("");
+
+ // if (navigateTo.current != "") {
+ // const newHref = navigateTo.current;
+ // navigateTo.current = "";
+ // location.href = newHref;
+ // navigate(newHref);
+ // }
+
+ //Optimistic UI
+ // let effectiveLabel = activityData.pageLabel;
+ // if (activityData.isSinglePage) {
+ // effectiveLabel = activityData.label;
+ // if (fetcher.data?._action == "update label") {
+ // effectiveLabel = fetcher.data.label;
+ // }
+ // } else {
+ // if (fetcher.data?._action == "update page label") {
+ // effectiveLabel = fetcher.data.pageLabel;
+ // }
+ // }
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/index.jsx b/src/index.jsx
index 94174762bf..64fc496755 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -5,8 +5,11 @@ import {
redirect,
RouterProvider,
} from "react-router-dom";
+import { ChakraProvider, extendTheme } from "@chakra-ui/react";
+
import { RecoilRoot } from "recoil";
import { createRoot } from "react-dom/client";
+import ErrorPage from "./Tools/_framework/Paths/ErrorPage";
import ToolRoot from "./Tools/_framework/NewToolRoot";
import { MathJaxContext } from "better-react-mathjax";
@@ -46,12 +49,10 @@ import {
action as portfolioActivityViewerAction,
PortfolioActivityViewer,
} from "./Tools/_framework/Paths/PortfolioActivityViewer";
-import { ChakraProvider, extendTheme } from "@chakra-ui/react";
import {
action as editorSupportPanelAction,
loader as editorSupportPanelLoader,
} from "./Tools/_framework/Panels/NewSupportPanel";
-import ErrorPage from "./Tools/_framework/Paths/ErrorPage";
import "@fontsource/jost";
import {
@@ -80,6 +81,11 @@ import {
CourseLinkPageViewer,
loader as courseLinkPageViewerLoader,
} from "./Tools/_framework/Paths/CourseLinkPageViewer";
+import {
+ SignIn,
+ loader as signInLoader,
+ action as signInAction,
+} from "./Tools/_framework/Paths/SignIn";
{
/* TESTING 123 */
@@ -319,6 +325,21 @@ const router = createBrowserRouter([
},
],
},
+ {
+ path: "signin",
+ loader: signInLoader,
+ action: signInAction,
+ errorElement: (
+
+
+
+ ),
+ element: (
+
+
+
+ ),
+ },
{
path: "public",
loader: editorSupportPanelLoader,
From 85e6ce596c97a099f69c7fed7a0d526356deba02 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Wed, 18 Oct 2023 23:17:14 -0500
Subject: [PATCH 55/83] start entering code
---
src/Tools/_framework/Paths/SignIn.jsx | 122 +++++++++++++++++++++++---
1 file changed, 112 insertions(+), 10 deletions(-)
diff --git a/src/Tools/_framework/Paths/SignIn.jsx b/src/Tools/_framework/Paths/SignIn.jsx
index 2ca877e882..2678d18d9f 100644
--- a/src/Tools/_framework/Paths/SignIn.jsx
+++ b/src/Tools/_framework/Paths/SignIn.jsx
@@ -11,12 +11,16 @@ import {
FormControl,
FormErrorMessage,
FormLabel,
+ HStack,
Heading,
Image,
Input,
+ PinInput,
+ PinInputField,
Stack,
Text,
} from "@chakra-ui/react";
+import axios from "axios";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useLoaderData } from "react-router";
import { useFetcher } from "react-router-dom";
@@ -38,11 +42,12 @@ export async function action({ params, request }) {
try {
if (formObj._action == "submit email") {
- let { data } = axios.get("/api/sendSignInEmail.php", {
- params: { email: params.emailAddress },
+ let { data } = await axios.get("/api/sendSignInEmail.php", {
+ params: { email: formObj.emailAddress },
});
return {
_action: formObj._action,
+ deviceName: data.deviceName,
success: true,
};
}
@@ -53,12 +58,11 @@ export async function action({ params, request }) {
}
}
-function AskForEmailCard() {
+function AskForEmailCard({ fetcher }) {
const [emailAddress, setEmailAddress] = useState("");
const [emailError, setEmailError] = useState(null);
const [isChecked, setIsChecked] = useState(false);
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- const fetcher = useFetcher();
return (
+
+
+
+
+
+
+
+
+ Check your email for the code.
+
+
+ Code (9 digit code):
+
+ setCode(code)}>
+
+
+
+
+
+
+
+
+
+
+
+ {codeError}
+
+
+
+
+
+ {
+ if (code == "") {
+ setCodeError(
+ "Please enter the nine digits sent to your email.",
+ );
+ } else if (code.length < 9) {
+ setCodeError("Please enter all nine digits.");
+ } else {
+ setCodeError(null);
+ }
+ //else if (!emailRegex.test(emailAddress)) {
+ // setEmailError("Invalid email format");
+ // } else {
+ // setEmailError(null);
+ // //Email is correct
+ // fetcher.submit(
+ // {
+ // _action: "submit email",
+ // emailAddress,
+ // staySignedIn: isChecked,
+ // },
+ // { method: "post" },
+ // );
+ // }
+ }}
+ >
+ Submit Code
+
+
+
+
+
+ );
+}
+
export function SignIn() {
const { success } = useLoaderData();
+ const fetcher = useFetcher();
+
+ let card = ;
+
+ // console.log("fetcher", fetcher);
+ if (fetcher.state === "idle" && fetcher.data?._action === "submit email") {
+ card = test
;
+ }
+
+ card = ;
- const [stage, setStage] = useState("Init");
- console.log(success);
- // const fetcher = useFetcher();
// const setActivityByDoenetId = useSetRecoilState(itemByDoenetId(doenetId)); //TODO: remove after recoil is gone
// const setPageByDoenetId = useSetRecoilState(itemByDoenetId(pageId)); //TODO: remove after recoil is gone
@@ -189,9 +293,7 @@ export function SignIn() {
return (
<>
-
-
-
+ {card}
>
);
From 817c7c702a0ed04742e30ca89f050ce669ff83f5 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Thu, 19 Oct 2023 16:48:13 -0500
Subject: [PATCH 56/83] email message and start of JWT as axios
---
src/Tools/_framework/Paths/SignIn.jsx | 98 ++++++++++++++++++++-------
src/index.jsx | 9 +++
2 files changed, 83 insertions(+), 24 deletions(-)
diff --git a/src/Tools/_framework/Paths/SignIn.jsx b/src/Tools/_framework/Paths/SignIn.jsx
index 2678d18d9f..4c15cf02b8 100644
--- a/src/Tools/_framework/Paths/SignIn.jsx
+++ b/src/Tools/_framework/Paths/SignIn.jsx
@@ -22,7 +22,7 @@ import {
} from "@chakra-ui/react";
import axios from "axios";
import React, { useCallback, useEffect, useRef, useState } from "react";
-import { useLoaderData } from "react-router";
+import { redirect, useLoaderData } from "react-router";
import { useFetcher } from "react-router-dom";
export async function loader({ request, params }) {
@@ -38,16 +38,48 @@ export async function loader({ request, params }) {
export async function action({ params, request }) {
const formData = await request.formData();
let formObj = Object.fromEntries(formData);
- console.log({ formObj });
try {
if (formObj._action == "submit email") {
let { data } = await axios.get("/api/sendSignInEmail.php", {
- params: { email: formObj.emailAddress },
+ params: { emailaddress: formObj.emailAddress },
});
return {
_action: formObj._action,
deviceName: data.deviceName,
+ emailAddress: formObj.emailAddress,
+ staySignedIn: formObj.staySignedIn,
+ success: true,
+ };
+ } else if (formObj._action == "submit code") {
+ let { data } = await axios.get("/api/checkCredentials.php", {
+ params: {
+ emailaddress: formObj.emailAddress,
+ nineCode: formObj.code,
+ deviceName: formObj.deviceName,
+ },
+ });
+
+ // if (data.hasFullName == 1) {
+ //Only should get here with success
+ //Store cookies!
+ const { data: jwtdata } = await axios.get(
+ `/api/jwt.php?emailaddress=${encodeURIComponent(
+ formObj.emailAddress,
+ )}&nineCode=${encodeURIComponent(formObj.code)}&deviceName=${
+ formObj.deviceName
+ }&newAccount=${data.existed}&stay=${
+ formObj.staySignedIn == "true" ? "1" : "0"
+ }`,
+ { withCredentials: true },
+ );
+
+ console.log("jwtdata", jwtdata);
+ // }
+ return {
+ _action: formObj._action,
+ isNewAccount: data.existed,
+ hasFullName: data.hasFullName,
success: true,
};
}
@@ -152,11 +184,10 @@ function AskForEmailCard({ fetcher }) {
);
}
-function EnterCodeCard({ fetcher }) {
+function EnterCodeCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
const [code, setCode] = useState("");
const [codeError, setCodeError] = useState(null);
- console.log("code", code);
- console.log("codeError", codeError);
+
return (
Check your email for the code.
- Code (9 digit code):
+ Sign-in code (9 digit code):
setCode(code)}>
@@ -219,21 +250,17 @@ function EnterCodeCard({ fetcher }) {
setCodeError("Please enter all nine digits.");
} else {
setCodeError(null);
+ fetcher.submit(
+ {
+ _action: "submit code",
+ emailAddress,
+ deviceName,
+ staySignedIn,
+ code,
+ },
+ { method: "post" },
+ );
}
- //else if (!emailRegex.test(emailAddress)) {
- // setEmailError("Invalid email format");
- // } else {
- // setEmailError(null);
- // //Email is correct
- // fetcher.submit(
- // {
- // _action: "submit email",
- // emailAddress,
- // staySignedIn: isChecked,
- // },
- // { method: "post" },
- // );
- // }
}}
>
Submit Code
@@ -248,15 +275,38 @@ function EnterCodeCard({ fetcher }) {
export function SignIn() {
const { success } = useLoaderData();
const fetcher = useFetcher();
+ console.log("fetcher", fetcher);
+
+ let emailAddress = useRef(null);
+ let deviceName = useRef(null);
+ let staySignedIn = useRef(null);
let card = ;
- // console.log("fetcher", fetcher);
if (fetcher.state === "idle" && fetcher.data?._action === "submit email") {
- card = test
;
+ emailAddress.current = fetcher.data.emailAddress;
+ staySignedIn.current = fetcher.data.staySignedIn;
+ deviceName.current = fetcher.data.deviceName;
+
+ card = (
+
+ );
+ } else if (
+ fetcher.state === "idle" &&
+ fetcher.data?._action === "submit code"
+ ) {
+ // if (fetcher.data?.hasFullName == 1) {
+ // }
+ // console.log("fetcher", fetcher);
+ card = Full name;
}
- card = ;
+ // card = ;
// const setActivityByDoenetId = useSetRecoilState(itemByDoenetId(doenetId)); //TODO: remove after recoil is gone
// const setPageByDoenetId = useSetRecoilState(itemByDoenetId(pageId)); //TODO: remove after recoil is gone
diff --git a/src/index.jsx b/src/index.jsx
index 64fc496755..54be7bcbd5 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -441,6 +441,15 @@ const router = createBrowserRouter([
),
},
+ // {
+ // path: "/api/",
+ // element: Loading...
,
+ // errorElement: (
+ //
+ //
+ //
+ // ),
+ // },
{
path: "*",
From a929c0c60c1434765043d5ba71c22dd9a7fbfaf9 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Thu, 19 Oct 2023 22:51:49 -0500
Subject: [PATCH 57/83] started all cards
---
public/api/jwt.php | 135 ++++++++++-----------
public/api/sendSignInEmail.php | 2 +-
public/api/signInEmail.html | 9 +-
src/Tools/_framework/Paths/SignIn.jsx | 166 ++++++++++++++++++++++----
4 files changed, 212 insertions(+), 100 deletions(-)
diff --git a/public/api/jwt.php b/public/api/jwt.php
index 7114779b3d..0289a3ab29 100644
--- a/public/api/jwt.php
+++ b/public/api/jwt.php
@@ -1,10 +1,4 @@
10) {
- echo 'Code expired';
-} else {
+ throw new Exception("Code expired.");
+}
$sql = "SELECT signInCode AS nineCode, userId
FROM user_device
WHERE email='$emailaddress' AND deviceName='$deviceName'";
@@ -37,74 +33,75 @@
$row = $result->fetch_assoc();
$userId = $row['userId'];
if ($row['nineCode'] != $nineCode) {
- echo 'Invalid Code';
- } else {
- //Valid code and not expired
- http_response_code(200);
+ throw new Exception("Invalid Code.");
+ }
+ //Valid code and not expired
+ http_response_code(200);
- $expirationTime = 0;
- if ($stay == 1) {
- $expirationTime = 2147483647;
- }
+ $expirationTime = 0;
+ if ($stay == 1) {
+ $expirationTime = 2147483647;
+ }
- $payload = [
- // "email" => $emailaddress,
- 'userId' => $userId,
- 'deviceName' => $deviceName,
- // "expires" => $expirationTime
- ];
- $jwt = JWT::encode($payload, $key);
+ $payload = [
+ // "email" => $emailaddress,
+ 'userId' => $userId,
+ 'deviceName' => $deviceName,
+ // "expires" => $expirationTime
+ ];
+ $jwt = JWT::encode($payload, $key);
- $sql = "UPDATE user_device
- SET signedIn = '1'
- WHERE userId='$userId' AND deviceName='$deviceName'";
- $result = $conn->query($sql);
+ $sql = "UPDATE user_device
+ SET signedIn = '1'
+ WHERE userId='$userId' AND deviceName='$deviceName'";
+ $result = $conn->query($sql);
- $value = $jwt;
+ $value = $jwt;
- $path = '/';
- //$domain = $ini_array['dbhost'];
- $domain = $_SERVER["SERVER_NAME"];
- if ($domain == 'apache'){$domain = 'localhost';}
- $isSecure = true;
- if ($domain == 'apache') {
- $domain = 'localhost';
- }
- if ($domain == 'localhost') {
- $isSecure = false;
- }
- $isHttpOnly = true;
- setcookie(
- 'JWT',
- $value,
- $expirationTime,
- $path,
- $domain,
- $isSecure,
- $isHttpOnly
- );
- setcookie(
- 'JWT_JS',
- 1,
- $expirationTime,
- $path,
- $domain,
- $isSecure,
- false
- );
- header('Location: /signin'); //needs to store profile into localstorage
+ $path = '/';
+ //$domain = $ini_array['dbhost'];
+ $domain = $_SERVER["SERVER_NAME"];
+ if ($domain == 'apache'){$domain = 'localhost';}
+ $isSecure = true;
+ if ($domain == 'apache') {
+ $domain = 'localhost';
+ }
+ if ($domain == 'localhost') {
+ $isSecure = false;
+ }
+ $isHttpOnly = true;
+ setcookie(
+ 'JWT',
+ $value,
+ $expirationTime,
+ $path,
+ $domain,
+ $isSecure,
+ $isHttpOnly
+ );
+ setcookie(
+ 'JWT_JS',
+ 1,
+ $expirationTime,
+ $path,
+ $domain,
+ $isSecure,
+ false
+ );
- // setcookie("JWT", $value, array("expires"=>$expirationTime, "path"=>$path, "domain"=>$domain, "secure"=>$isSecure, "httponly"=>$isHttpOnly, "samesite"=>"strict"));
- // setcookie("JWT_JS", 1, array("expires"=>$expirationTime, "path"=>$path, "domain"=>$domain, "secure"=>$isSecure, "httponly"=>false, "samesite"=>"strict"));
- // if ($newAccount == 1){
- // // header("Location: /accountsettings");
- // header("Location: /library");
- // }else{
- // // header("Location: /dashboard");
- // header("Location: /course");
- // }
- }
+} catch (Exception $e) {
+ $response_arr = [
+ 'success' => false,
+ 'message' => $e->getMessage(),
+ ];
+ http_response_code(400);
+
+} finally {
+ // make it json format
+ echo json_encode($response_arr);
+ $conn->close();
}
$conn->close();
+?>
diff --git a/public/api/sendSignInEmail.php b/public/api/sendSignInEmail.php
index 3e8ccba30e..fe88911f98 100644
--- a/public/api/sendSignInEmail.php
+++ b/public/api/sendSignInEmail.php
@@ -64,7 +64,7 @@
// Generate and modify email content
$htmlContent = file_get_contents("signInEmail.html");
-$htmlContent = str_replace(array("deviceName", "signInCode"), array($deviceName, $signInCode), $htmlContent);
+$htmlContent = str_replace(array("signInCode"), array($signInCode), $htmlContent);
$from = 'noreply@doenet.org';
$fromName = 'Doenet Accounts';
diff --git a/public/api/signInEmail.html b/public/api/signInEmail.html
index 04d0631196..c6e7eed83c 100644
--- a/public/api/signInEmail.html
+++ b/public/api/signInEmail.html
@@ -136,14 +136,11 @@
align="left"
>
-
Welcome to Doenet, you've
- requested a sign-in code for
- deviceName.
+
Welcome to Doenet!
- Access code:
- signInCode
+ ```signInCode```
diff --git a/src/Tools/_framework/Paths/SignIn.jsx b/src/Tools/_framework/Paths/SignIn.jsx
index 4c15cf02b8..901038b434 100644
--- a/src/Tools/_framework/Paths/SignIn.jsx
+++ b/src/Tools/_framework/Paths/SignIn.jsx
@@ -17,6 +17,7 @@ import {
Input,
PinInput,
PinInputField,
+ Spinner,
Stack,
Text,
} from "@chakra-ui/react";
@@ -60,22 +61,22 @@ export async function action({ params, request }) {
},
});
- // if (data.hasFullName == 1) {
- //Only should get here with success
- //Store cookies!
- const { data: jwtdata } = await axios.get(
- `/api/jwt.php?emailaddress=${encodeURIComponent(
- formObj.emailAddress,
- )}&nineCode=${encodeURIComponent(formObj.code)}&deviceName=${
- formObj.deviceName
- }&newAccount=${data.existed}&stay=${
- formObj.staySignedIn == "true" ? "1" : "0"
- }`,
- { withCredentials: true },
- );
-
- console.log("jwtdata", jwtdata);
- // }
+ if (data.hasFullName == 1) {
+ //Only should get here with success
+ //Store cookies!
+ const { data: jwtdata } = await axios.get(
+ `/api/jwt.php?emailaddress=${encodeURIComponent(
+ formObj.emailAddress,
+ )}&nineCode=${encodeURIComponent(formObj.code)}&deviceName=${
+ formObj.deviceName
+ }&newAccount=${data.existed}&stay=${
+ formObj.staySignedIn == "true" ? "1" : "0"
+ }`,
+ { withCredentials: true },
+ );
+
+ // console.log("jwtdata", jwtdata);
+ }
return {
_action: formObj._action,
isNewAccount: data.existed,
@@ -94,6 +95,7 @@ function AskForEmailCard({ fetcher }) {
const [emailAddress, setEmailAddress] = useState("");
const [emailError, setEmailError] = useState(null);
const [isChecked, setIsChecked] = useState(false);
+ const [isDisabled, setIsDisabled] = useState(false);
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return (
@@ -155,6 +157,8 @@ function AskForEmailCard({ fetcher }) {
: undefined}
colorScheme="blue"
onClick={() => {
if (emailAddress == "") {
@@ -163,6 +167,7 @@ function AskForEmailCard({ fetcher }) {
setEmailError("Invalid email format");
} else {
setEmailError(null);
+ setIsDisabled(true);
//Email is correct
fetcher.submit(
{
@@ -187,6 +192,7 @@ function AskForEmailCard({ fetcher }) {
function EnterCodeCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
const [code, setCode] = useState("");
const [codeError, setCodeError] = useState(null);
+ const [isDisabled, setIsDisabled] = useState(false);
return (
: undefined}
onClick={() => {
if (code == "") {
setCodeError(
@@ -250,6 +258,7 @@ function EnterCodeCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
setCodeError("Please enter all nine digits.");
} else {
setCodeError(null);
+ setIsDisabled(true);
fetcher.submit(
{
_action: "submit code",
@@ -272,6 +281,110 @@ function EnterCodeCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
);
}
+function AskForNameCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
+ const [firstName, setFirstName] = useState("");
+ const [firstNameError, setFirstNameError] = useState(null);
+ const [lastName, setLastName] = useState("");
+ const [lastNameError, setLastNameError] = useState(null);
+ const [isDisabled, setIsDisabled] = useState(false);
+
+ return (
+
+
+
+
+
+
+
+
+
+ Please Enter Your Name.
+
+
+ First Name:
+ {
+ if (firstName != "") {
+ setFirstNameError(null);
+ }
+ setFirstName(firstName);
+ }}
+ />
+ {firstNameError}
+
+
+ Last Name:
+ {
+ if (lastName != "") {
+ setLastNameError(null);
+ }
+ setLastName(lastName);
+ }}
+ />
+ {lastNameError}
+
+
+
+
+
+ : undefined}
+ onClick={() => {
+ if (firstName == "") {
+ setFirstNameError("Please enter your first name.");
+ }
+ if (lastName == "") {
+ setLastNameError("Please enter your last name.");
+ }
+ if (firstName != "" && lastName != "") {
+ setFirstNameError(null);
+ setLastNameError(null);
+ setIsDisabled(true);
+ console.log("firstName", firstName);
+ console.log("lastName", lastName);
+ // fetcher.submit(
+ // {
+ // _action: "submit code",
+ // emailAddress,
+ // deviceName,
+ // staySignedIn,
+ // code,
+ // },
+ // { method: "post" },
+ // );
+ }
+ }}
+ >
+ Submit Name
+
+
+
+
+
+ );
+}
+
export function SignIn() {
const { success } = useLoaderData();
const fetcher = useFetcher();
@@ -280,15 +393,16 @@ export function SignIn() {
let emailAddress = useRef(null);
let deviceName = useRef(null);
let staySignedIn = useRef(null);
-
- let card = ;
+ //card is a ref because we need the card to stay
+ // and not have to track every possible state
+ let card = useRef();
if (fetcher.state === "idle" && fetcher.data?._action === "submit email") {
emailAddress.current = fetcher.data.emailAddress;
staySignedIn.current = fetcher.data.staySignedIn;
deviceName.current = fetcher.data.deviceName;
- card = (
+ card.current = (
Full name;
+ card.current = (
+
+ );
}
// card = ;
@@ -343,7 +461,7 @@ export function SignIn() {
return (
<>
- {card}
+ {card.current}
>
);
From 7a75b6adfa8fcd070389c3c986a54ffccda7491b Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Fri, 20 Oct 2023 08:58:29 -0500
Subject: [PATCH 58/83] process improvements
---
public/api/jwt.php | 6 ++-
src/Tools/_framework/Paths/SignIn.jsx | 78 ++++++++++++++++-----------
2 files changed, 51 insertions(+), 33 deletions(-)
diff --git a/public/api/jwt.php b/public/api/jwt.php
index 0289a3ab29..5731d3ee13 100644
--- a/public/api/jwt.php
+++ b/public/api/jwt.php
@@ -89,6 +89,11 @@
false
);
+ $response_arr = [
+ 'success' => true,
+ ];
+
+ http_response_code(200);
} catch (Exception $e) {
$response_arr = [
@@ -103,5 +108,4 @@
$conn->close();
}
-$conn->close();
?>
diff --git a/src/Tools/_framework/Paths/SignIn.jsx b/src/Tools/_framework/Paths/SignIn.jsx
index 901038b434..52c510e42c 100644
--- a/src/Tools/_framework/Paths/SignIn.jsx
+++ b/src/Tools/_framework/Paths/SignIn.jsx
@@ -53,6 +53,7 @@ export async function action({ params, request }) {
success: true,
};
} else if (formObj._action == "submit code") {
+ //TODO: need check credentials to give back the portfolio course id
let { data } = await axios.get("/api/checkCredentials.php", {
params: {
emailaddress: formObj.emailAddress,
@@ -61,21 +62,24 @@ export async function action({ params, request }) {
},
});
+ //Attempt to store cookies!
+ const { data: jwtdata } = await axios.get(
+ `/api/jwt.php?emailaddress=${encodeURIComponent(
+ formObj.emailAddress,
+ )}&nineCode=${encodeURIComponent(formObj.code)}&deviceName=${
+ formObj.deviceName
+ }&newAccount=${data.existed}&stay=${
+ formObj.staySignedIn == "true" ? "1" : "0"
+ }`,
+ { withCredentials: true },
+ );
+
+ console.log("jwtdata", jwtdata);
+
if (data.hasFullName == 1) {
- //Only should get here with success
- //Store cookies!
- const { data: jwtdata } = await axios.get(
- `/api/jwt.php?emailaddress=${encodeURIComponent(
- formObj.emailAddress,
- )}&nineCode=${encodeURIComponent(formObj.code)}&deviceName=${
- formObj.deviceName
- }&newAccount=${data.existed}&stay=${
- formObj.staySignedIn == "true" ? "1" : "0"
- }`,
- { withCredentials: true },
- );
-
- // console.log("jwtdata", jwtdata);
+ //Redirect if we have their full name
+ //and there wasn't an error with sign in
+ // TODO: Redirect to portfolio
}
return {
_action: formObj._action,
@@ -83,6 +87,18 @@ export async function action({ params, request }) {
hasFullName: data.hasFullName,
success: true,
};
+ } else if (formObj._action == "submit name") {
+ let { data } = await axios.get("/api/saveUsersName.php", {
+ params: {
+ firstName: formObj.firstName,
+ lastName: formObj.lastName,
+ email: formObj.emailAddress,
+ },
+ });
+ console.log("data", data);
+ return true;
+
+ // TODO: Redirect to portfolio
}
return { success: true };
@@ -320,11 +336,11 @@ function AskForNameCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
First Name:
{
- if (firstName != "") {
+ onChange={(e) => {
+ if (e.target.value != "") {
setFirstNameError(null);
}
- setFirstName(firstName);
+ setFirstName(e.target.value);
}}
/>
{firstNameError}
@@ -332,11 +348,11 @@ function AskForNameCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
Last Name:
{
- if (lastName != "") {
+ onChange={(e) => {
+ if (e.target.value != "") {
setLastNameError(null);
}
- setLastName(lastName);
+ setLastName(e.target.value);
}}
/>
{lastNameError}
@@ -361,18 +377,16 @@ function AskForNameCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
setFirstNameError(null);
setLastNameError(null);
setIsDisabled(true);
- console.log("firstName", firstName);
- console.log("lastName", lastName);
- // fetcher.submit(
- // {
- // _action: "submit code",
- // emailAddress,
- // deviceName,
- // staySignedIn,
- // code,
- // },
- // { method: "post" },
- // );
+
+ fetcher.submit(
+ {
+ _action: "submit name",
+ firstName,
+ lastName,
+ emailAddress,
+ },
+ { method: "post" },
+ );
}
}}
>
From 6d8ee9e4c61db5e19e0277462e21972494e4f0da Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Sat, 21 Oct 2023 08:43:45 -0500
Subject: [PATCH 59/83] last in the series
---
src/Tools/_framework/Paths/SignIn.jsx | 171 +++++++++++++++++---------
1 file changed, 116 insertions(+), 55 deletions(-)
diff --git a/src/Tools/_framework/Paths/SignIn.jsx b/src/Tools/_framework/Paths/SignIn.jsx
index 52c510e42c..aef4975b8c 100644
--- a/src/Tools/_framework/Paths/SignIn.jsx
+++ b/src/Tools/_framework/Paths/SignIn.jsx
@@ -38,7 +38,7 @@ export async function loader({ request, params }) {
export async function action({ params, request }) {
const formData = await request.formData();
- let formObj = Object.fromEntries(formData);
+ const formObj = Object.fromEntries(formData);
try {
if (formObj._action == "submit email") {
@@ -103,7 +103,11 @@ export async function action({ params, request }) {
return { success: true };
} catch (e) {
- return { success: false, message: e.response.data.message };
+ return {
+ success: false,
+ message: e.response.data.message,
+ _action: formObj._action,
+ };
}
}
@@ -209,6 +213,20 @@ function EnterCodeCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
const [code, setCode] = useState("");
const [codeError, setCodeError] = useState(null);
const [isDisabled, setIsDisabled] = useState(false);
+ const [isExpired, setIsExpired] = useState(false);
+
+ console.log("EnterCodeCard fetcher", fetcher);
+ //Handle code entry errors
+ if (fetcher.data?.success == false) {
+ //Guard against an infinite loop
+ if (codeError !== fetcher.data.message) {
+ setCodeError(fetcher.data.message);
+ setIsDisabled(false);
+ if (fetcher.data.message == "Code expired.") {
+ setIsExpired(true);
+ }
+ }
+ }
return (
Sign-in code (9 digit code):
- setCode(code)}>
+ setCode(code)}>
@@ -260,36 +278,64 @@ function EnterCodeCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
- : undefined}
- onClick={() => {
- if (code == "") {
- setCodeError(
- "Please enter the nine digits sent to your email.",
- );
- } else if (code.length < 9) {
- setCodeError("Please enter all nine digits.");
- } else {
+ {isExpired ? (
+ {
+ //TODO: WHY NOT WORKING???
+ setIsExpired(false);
setCodeError(null);
- setIsDisabled(true);
+ setCode("");
+ console.log({
+ _action: "submit email",
+ emailAddress,
+ staySignedIn,
+ });
fetcher.submit(
{
- _action: "submit code",
- emailAddress,
- deviceName,
- staySignedIn,
- code,
+ _action: "submit email",
+ emailAddress: "char0042@umn.edu",
+ staySignedIn: true,
},
{ method: "post" },
);
- }
- }}
- >
- Submit Code
-
+ }}
+ >
+ Send New Code
+
+ ) : (
+ : undefined}
+ onClick={() => {
+ if (code == "") {
+ setCodeError(
+ "Please enter the nine digits sent to your email.",
+ );
+ } else if (code.length < 9) {
+ setCodeError("Please enter all nine digits.");
+ } else {
+ setCodeError(null);
+ setIsDisabled(true);
+ fetcher.submit(
+ {
+ _action: "submit code",
+ emailAddress,
+ deviceName,
+ staySignedIn,
+ code,
+ },
+ { method: "post" },
+ );
+ }
+ }}
+ >
+ Submit Code
+
+ )}
@@ -400,42 +446,57 @@ function AskForNameCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
}
export function SignIn() {
- const { success } = useLoaderData();
+ // const { success } = useLoaderData();
const fetcher = useFetcher();
- console.log("fetcher", fetcher);
+ let formObj = {};
+ if (fetcher.formData !== undefined) {
+ formObj = Object.fromEntries(fetcher.formData);
+ }
+ console.log("fetcher.state", fetcher.state);
+ console.log("fetcher.data", fetcher.data);
+ console.log("formObj", formObj);
+ console.log("---------------------\n");
let emailAddress = useRef(null);
let deviceName = useRef(null);
let staySignedIn = useRef(null);
//card is a ref because we need the card to stay
// and not have to track every possible state
- let card = useRef();
- if (fetcher.state === "idle" && fetcher.data?._action === "submit email") {
- emailAddress.current = fetcher.data.emailAddress;
- staySignedIn.current = fetcher.data.staySignedIn;
- deviceName.current = fetcher.data.deviceName;
-
- card.current = (
-
- );
- } else if (
- fetcher.state === "idle" &&
- fetcher.data?._action === "submit code"
- ) {
- card.current = (
-
- );
+ //Enter Email
+ let card = useRef();
+ if (fetcher.state === "idle") {
+ if (
+ (fetcher.data?._action === "submit email" && fetcher.data?.success) ||
+ (fetcher.data?._action === "submit code" && !fetcher.data?.success)
+ ) {
+ //Enter Code
+ emailAddress.current = fetcher.data.emailAddress;
+ staySignedIn.current = fetcher.data.staySignedIn;
+ deviceName.current = fetcher.data.deviceName;
+
+ card.current = (
+
+ );
+ } else if (
+ (fetcher.data?._action === "submit code" && fetcher.data?.success) ||
+ (fetcher.data?._action === "submit name" && !fetcher.data?.success)
+ ) {
+ //Enter Name
+ card.current = (
+
+ );
+ }
}
// card = ;
From 7c15def26356341af298db0675b6aef6d5a20c65 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Sat, 21 Oct 2023 22:56:32 -0500
Subject: [PATCH 60/83] Progress on three path design
---
public/api/checkCredentials.php | 81 +--
public/api/sendSignInEmail.php | 31 +-
src/Tools/_framework/Paths/SignIn.jsx | 592 ++++------------------
src/Tools/_framework/Paths/SignInCode.jsx | 238 +++++++++
src/Tools/_framework/Paths/SignInName.jsx | 533 +++++++++++++++++++
src/index.jsx | 42 +-
6 files changed, 984 insertions(+), 533 deletions(-)
create mode 100644 src/Tools/_framework/Paths/SignInCode.jsx
create mode 100644 src/Tools/_framework/Paths/SignInName.jsx
diff --git a/public/api/checkCredentials.php b/public/api/checkCredentials.php
index 4efc6852aa..e6181dea9a 100644
--- a/public/api/checkCredentials.php
+++ b/public/api/checkCredentials.php
@@ -7,11 +7,23 @@
include "db_connection.php";
-
$emailaddress = mysqli_real_escape_string($conn,$_REQUEST["emailaddress"]);
$nineCode = mysqli_real_escape_string($conn,$_REQUEST["nineCode"]);
$deviceName = mysqli_real_escape_string($conn,$_REQUEST["deviceName"]);
+if(!isset($_REQUEST["emailaddress"])){
+ throw new Exception("Internal Error: missing emailaddress");
+}
+if(!isset($_REQUEST["nineCode"])){
+ throw new Exception("Internal Error: missing nineCode");
+}
+if(!isset($_REQUEST["deviceName"])){
+ throw new Exception("Internal Error: missing deviceName");
+}
+
+$response_arr;
+try {
+
//Check if expired
$sql = "SELECT TIMESTAMPDIFF(MINUTE, timestampOfSignInCode, NOW()) AS minutes
FROM user_device
@@ -20,19 +32,15 @@
$result = $conn->query($sql);
$row = $result->fetch_assoc();
-//Assume success and it already exists
-
-
-$success = 1;
-$existed = 1;
-$hasFullName = 0;
+//Assume it already exists
+$existed = true;
+$hasFullName = false;
$reason = "";
//Check if it took longer than 10 minutes to enter the code
if ($row['minutes'] > 10){
- $success = 0;
- $reason = "Code expired";
-}else{
+ throw new Exception("Code expired");
+}
$sql = "SELECT signInCode AS nineCode
FROM user_device
@@ -41,10 +49,8 @@
$row = $result->fetch_assoc();
if ($row["nineCode"] != $nineCode){
- $success = 0;
- $reason = "Invalid Code";
-
- }else{
+ throw new Exception("Invalid Code");
+ }
//Valid code and not expired
//Update signedIn on user_device table
@@ -54,7 +60,6 @@
$result = $conn->query($sql);
//Test if it's a new account
-
$sql = "SELECT firstName,lastName, screenName
FROM user
WHERE email='$emailaddress'
@@ -63,13 +68,13 @@
$row = $result->fetch_assoc();
if ($row["firstName"] != "" && $row["lastName"] != ""){
- $hasFullName = 1;
+ $hasFullName = true;
}
//Only new accounts won't have a screen name
if ($row["screenName"] === null){
// New Account!
- $existed = 0;
+ $existed = false;
// Make a new profile
// Random screen name
@@ -84,27 +89,37 @@
// Store screen name and profile picture
$sql = "UPDATE user SET screenName='$screen_name',profilePicture='$profile_pic' WHERE email='$emailaddress' ";
$result = $conn->query($sql);
- }
-
-
-
- }
-
-
-}
+ }
+ $sql = "SELECT c.courseId
+ FROM course AS c
+ LEFT JOIN user AS u
+ ON u.userId = c.portfolioCourseForUserId
+ WHERE u.email = '$emailaddress'";
+ $result = $conn->query($sql);
+ $row = $result->fetch_assoc();
+ $portfolioCourseId = "_";
+ if ($result->num_rows > 0) {
+ $portfolioCourseId = $row['courseId'];
+ }
$response_arr = array(
- "success" => $success,
+ "success" => true,
"existed" => $existed,
"hasFullName" => $hasFullName,
- "reason" => $reason,
+ "portfolioCourseId" => $portfolioCourseId,
);
-http_response_code(200);
-
-// make it json format
-echo json_encode($response_arr);
-
-$conn->close();
+} catch (Exception $e) {
+ $response_arr = [
+ 'success' => false,
+ 'message' => $e->getMessage(),
+ ];
+ http_response_code(400);
+
+} finally {
+ // make it json format
+ echo json_encode($response_arr);
+ $conn->close();
+}
diff --git a/public/api/sendSignInEmail.php b/public/api/sendSignInEmail.php
index fe88911f98..c3e616c013 100644
--- a/public/api/sendSignInEmail.php
+++ b/public/api/sendSignInEmail.php
@@ -13,6 +13,8 @@
//Nine digit random number
$signInCode = rand(100000000,999999999);
+$response_arr;
+try {
$sql = "SELECT email, userId
FROM user
@@ -76,18 +78,29 @@
$headers .= 'From: '.$fromName.'<'.$from.'>' . "\r\n";
//SEND EMAIL WITH CODE HERE
-mail($emailaddress,$subject,$htmlContent, $headers);
+$mailSuccess = mail($emailaddress,$subject,$htmlContent, $headers);
-$response_arr = array(
- "success" => 1,
- "deviceName" => $deviceName,
- );
+if (!$mailSuccess && $mode != 'development'){
+ throw new Exception("Sending Email Failed.");
+}
-// set response code - 200 OK
-http_response_code(200);
+$response_arr = [
+ 'success' => true,
+ "deviceName" => $deviceName,
+ ];
-echo json_encode($response_arr);
+ http_response_code(200);
+} catch (Exception $e) {
+ $response_arr = [
+ 'success' => false,
+ 'message' => $e->getMessage(),
+ ];
+ http_response_code(400);
-$conn->close();
+} finally {
+ // make it json format
+ echo json_encode($response_arr);
+ $conn->close();
+}
diff --git a/src/Tools/_framework/Paths/SignIn.jsx b/src/Tools/_framework/Paths/SignIn.jsx
index aef4975b8c..5d897ef721 100644
--- a/src/Tools/_framework/Paths/SignIn.jsx
+++ b/src/Tools/_framework/Paths/SignIn.jsx
@@ -5,38 +5,23 @@ import {
Card,
CardBody,
CardFooter,
- Center,
Checkbox,
Flex,
FormControl,
FormErrorMessage,
FormLabel,
- HStack,
Heading,
Image,
Input,
- PinInput,
- PinInputField,
Spinner,
Stack,
- Text,
} from "@chakra-ui/react";
import axios from "axios";
-import React, { useCallback, useEffect, useRef, useState } from "react";
-import { redirect, useLoaderData } from "react-router";
+import React, { useState } from "react";
+import { redirect } from "react-router";
import { useFetcher } from "react-router-dom";
-export async function loader({ request, params }) {
- // const url = new URL(request.url);
- // const from = url.searchParams.get("from");
- try {
- return { success: true };
- } catch (e) {
- return { success: false, message: e.response.data.message };
- }
-}
-
-export async function action({ params, request }) {
+export async function action({ request }) {
const formData = await request.formData();
const formObj = Object.fromEntries(formData);
@@ -45,63 +30,18 @@ export async function action({ params, request }) {
let { data } = await axios.get("/api/sendSignInEmail.php", {
params: { emailaddress: formObj.emailAddress },
});
- return {
- _action: formObj._action,
- deviceName: data.deviceName,
- emailAddress: formObj.emailAddress,
- staySignedIn: formObj.staySignedIn,
- success: true,
- };
- } else if (formObj._action == "submit code") {
- //TODO: need check credentials to give back the portfolio course id
- let { data } = await axios.get("/api/checkCredentials.php", {
- params: {
- emailaddress: formObj.emailAddress,
- nineCode: formObj.code,
- deviceName: formObj.deviceName,
- },
- });
-
- //Attempt to store cookies!
- const { data: jwtdata } = await axios.get(
- `/api/jwt.php?emailaddress=${encodeURIComponent(
- formObj.emailAddress,
- )}&nineCode=${encodeURIComponent(formObj.code)}&deviceName=${
- formObj.deviceName
- }&newAccount=${data.existed}&stay=${
- formObj.staySignedIn == "true" ? "1" : "0"
- }`,
- { withCredentials: true },
+ return redirect(
+ `/signinCode?email=${formObj.emailAddress}&device=${data.deviceName}&stay=${formObj.staySignedIn}`,
);
- console.log("jwtdata", jwtdata);
-
- if (data.hasFullName == 1) {
- //Redirect if we have their full name
- //and there wasn't an error with sign in
- // TODO: Redirect to portfolio
- }
- return {
- _action: formObj._action,
- isNewAccount: data.existed,
- hasFullName: data.hasFullName,
- success: true,
- };
- } else if (formObj._action == "submit name") {
- let { data } = await axios.get("/api/saveUsersName.php", {
- params: {
- firstName: formObj.firstName,
- lastName: formObj.lastName,
- email: formObj.emailAddress,
- },
- });
- console.log("data", data);
- return true;
-
- // TODO: Redirect to portfolio
+ // return {
+ // _action: formObj._action,
+ // deviceName: data.deviceName,
+ // emailAddress: formObj.emailAddress,
+ // staySignedIn: formObj.staySignedIn,
+ // success: true,
+ // };
}
-
- return { success: true };
} catch (e) {
return {
success: false,
@@ -111,432 +51,106 @@ export async function action({ params, request }) {
}
}
-function AskForEmailCard({ fetcher }) {
+export function SignIn() {
+ const fetcher = useFetcher();
const [emailAddress, setEmailAddress] = useState("");
const [emailError, setEmailError] = useState(null);
const [isChecked, setIsChecked] = useState(false);
const [isDisabled, setIsDisabled] = useState(false);
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- return (
-
-
-
-
-
-
-
-
-
- Sign In via Email
-
-
- Email address
- {
- let nextValue = e.target.value;
- //Clear error if email is now good
- if (emailError != null && emailRegex.test(nextValue)) {
- setEmailError(null);
- }
- setEmailAddress(nextValue);
- }}
- />
- {emailError}
-
-
- setIsChecked(e.target.checked)}
- >
- Stay Signed In
-
-
-
-
-
- : undefined}
- colorScheme="blue"
- onClick={() => {
- if (emailAddress == "") {
- setEmailError("Please enter your email address");
- } else if (!emailRegex.test(emailAddress)) {
- setEmailError("Invalid email format");
- } else {
- setEmailError(null);
- setIsDisabled(true);
- //Email is correct
- fetcher.submit(
- {
- _action: "submit email",
- emailAddress,
- staySignedIn: isChecked,
- },
- { method: "post" },
- );
- }
- }}
- >
- Send Email
-
-
-
-
-
- );
-}
-
-function EnterCodeCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
- const [code, setCode] = useState("");
- const [codeError, setCodeError] = useState(null);
- const [isDisabled, setIsDisabled] = useState(false);
- const [isExpired, setIsExpired] = useState(false);
-
- console.log("EnterCodeCard fetcher", fetcher);
- //Handle code entry errors
- if (fetcher.data?.success == false) {
- //Guard against an infinite loop
- if (codeError !== fetcher.data.message) {
- setCodeError(fetcher.data.message);
- setIsDisabled(false);
- if (fetcher.data.message == "Code expired.") {
- setIsExpired(true);
- }
- }
- }
-
- return (
-
-
-
-
-
-
-
-
-
- Check your email for the code.
-
-
- Sign-in code (9 digit code):
-
- setCode(code)}>
-
-
-
-
-
-
-
-
-
-
-
- {codeError}
-
-
-
-
-
- {isExpired ? (
- {
- //TODO: WHY NOT WORKING???
- setIsExpired(false);
- setCodeError(null);
- setCode("");
- console.log({
- _action: "submit email",
- emailAddress,
- staySignedIn,
- });
- fetcher.submit(
- {
- _action: "submit email",
- emailAddress: "char0042@umn.edu",
- staySignedIn: true,
- },
- { method: "post" },
- );
- }}
- >
- Send New Code
-
- ) : (
- : undefined}
- onClick={() => {
- if (code == "") {
- setCodeError(
- "Please enter the nine digits sent to your email.",
- );
- } else if (code.length < 9) {
- setCodeError("Please enter all nine digits.");
- } else {
- setCodeError(null);
- setIsDisabled(true);
- fetcher.submit(
- {
- _action: "submit code",
- emailAddress,
- deviceName,
- staySignedIn,
- code,
- },
- { method: "post" },
- );
- }
- }}
- >
- Submit Code
-
- )}
-
-
-
-
- );
-}
-
-function AskForNameCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
- const [firstName, setFirstName] = useState("");
- const [firstNameError, setFirstNameError] = useState(null);
- const [lastName, setLastName] = useState("");
- const [lastNameError, setLastNameError] = useState(null);
- const [isDisabled, setIsDisabled] = useState(false);
-
- return (
-
-
-
-
-
-
-
-
-
- Please Enter Your Name.
-
-
- First Name:
- {
- if (e.target.value != "") {
- setFirstNameError(null);
- }
- setFirstName(e.target.value);
- }}
- />
- {firstNameError}
-
-
- Last Name:
- {
- if (e.target.value != "") {
- setLastNameError(null);
- }
- setLastName(e.target.value);
- }}
- />
- {lastNameError}
-
-
-
-
-
- : undefined}
- onClick={() => {
- if (firstName == "") {
- setFirstNameError("Please enter your first name.");
- }
- if (lastName == "") {
- setLastNameError("Please enter your last name.");
- }
- if (firstName != "" && lastName != "") {
- setFirstNameError(null);
- setLastNameError(null);
- setIsDisabled(true);
-
- fetcher.submit(
- {
- _action: "submit name",
- firstName,
- lastName,
- emailAddress,
- },
- { method: "post" },
- );
- }
- }}
- >
- Submit Name
-
-
-
-
-
- );
-}
-
-export function SignIn() {
- // const { success } = useLoaderData();
- const fetcher = useFetcher();
- let formObj = {};
- if (fetcher.formData !== undefined) {
- formObj = Object.fromEntries(fetcher.formData);
- }
- console.log("fetcher.state", fetcher.state);
- console.log("fetcher.data", fetcher.data);
- console.log("formObj", formObj);
- console.log("---------------------\n");
-
- let emailAddress = useRef(null);
- let deviceName = useRef(null);
- let staySignedIn = useRef(null);
- //card is a ref because we need the card to stay
- // and not have to track every possible state
-
- //Enter Email
- let card = useRef();
- if (fetcher.state === "idle") {
- if (
- (fetcher.data?._action === "submit email" && fetcher.data?.success) ||
- (fetcher.data?._action === "submit code" && !fetcher.data?.success)
- ) {
- //Enter Code
- emailAddress.current = fetcher.data.emailAddress;
- staySignedIn.current = fetcher.data.staySignedIn;
- deviceName.current = fetcher.data.deviceName;
-
- card.current = (
-
- );
- } else if (
- (fetcher.data?._action === "submit code" && fetcher.data?.success) ||
- (fetcher.data?._action === "submit name" && !fetcher.data?.success)
- ) {
- //Enter Name
- card.current = (
-
- );
- }
- }
-
- // card = ;
-
- // const setActivityByDoenetId = useSetRecoilState(itemByDoenetId(doenetId)); //TODO: remove after recoil is gone
- // const setPageByDoenetId = useSetRecoilState(itemByDoenetId(pageId)); //TODO: remove after recoil is gone
-
- // let location = useLocation();
-
- // const navigate = useNavigate();
-
- // const [recoilPageToolView, setRecoilPageToolView] =
- // useRecoilState(pageToolViewAtom);
-
- // let navigateTo = useRef("");
-
- // if (navigateTo.current != "") {
- // const newHref = navigateTo.current;
- // navigateTo.current = "";
- // location.href = newHref;
- // navigate(newHref);
- // }
-
- //Optimistic UI
- // let effectiveLabel = activityData.pageLabel;
- // if (activityData.isSinglePage) {
- // effectiveLabel = activityData.label;
- // if (fetcher.data?._action == "update label") {
- // effectiveLabel = fetcher.data.label;
- // }
- // } else {
- // if (fetcher.data?._action == "update page label") {
- // effectiveLabel = fetcher.data.pageLabel;
- // }
- // }
-
return (
<>
- {card.current}
+
+
+
+
+
+
+
+
+
+
+ Sign In via Email
+
+
+ Email address
+ {
+ let nextValue = e.target.value;
+ //Clear error if email is now good
+ if (emailError != null && emailRegex.test(nextValue)) {
+ setEmailError(null);
+ }
+ setEmailAddress(nextValue);
+ }}
+ />
+ {emailError}
+
+
+ setIsChecked(e.target.checked)}
+ >
+ Stay Signed In
+
+
+
+
+
+ : undefined}
+ colorScheme="blue"
+ onClick={() => {
+ if (emailAddress == "") {
+ setEmailError("Please enter your email address");
+ } else if (!emailRegex.test(emailAddress)) {
+ setEmailError("Invalid email format");
+ } else {
+ setEmailError(null);
+ setIsDisabled(true);
+ //Email is correct
+ fetcher.submit(
+ {
+ _action: "submit email",
+ emailAddress,
+ staySignedIn: isChecked,
+ },
+ { method: "post" },
+ );
+ }
+ }}
+ >
+ Send Email
+
+
+
+
+
+
>
);
diff --git a/src/Tools/_framework/Paths/SignInCode.jsx b/src/Tools/_framework/Paths/SignInCode.jsx
new file mode 100644
index 0000000000..b3cdeae43f
--- /dev/null
+++ b/src/Tools/_framework/Paths/SignInCode.jsx
@@ -0,0 +1,238 @@
+import {
+ AbsoluteCenter,
+ Box,
+ Button,
+ Card,
+ CardBody,
+ CardFooter,
+ Center,
+ Checkbox,
+ Flex,
+ FormControl,
+ FormErrorMessage,
+ FormLabel,
+ HStack,
+ Heading,
+ Image,
+ Input,
+ PinInput,
+ PinInputField,
+ Spinner,
+ Stack,
+ Text,
+} from "@chakra-ui/react";
+import axios from "axios";
+import React, { useState } from "react";
+import { redirect, useLoaderData } from "react-router";
+import { useFetcher } from "react-router-dom";
+
+export async function loader({ request }) {
+ //Search Parameters to useLoaderData
+ const url = new URL(request.url);
+ const emailAddress = url.searchParams.get("email");
+ const deviceName = url.searchParams.get("device");
+ const staySignedIn = url.searchParams.get("stay");
+ return { emailAddress, deviceName, staySignedIn };
+}
+
+export async function action({ params, request }) {
+ const formData = await request.formData();
+ const formObj = Object.fromEntries(formData);
+
+ try {
+ if (formObj._action == "send new code") {
+ let { data } = await axios.get("/api/sendSignInEmail.php", {
+ params: { emailaddress: formObj.emailAddress },
+ });
+ return {
+ success: true,
+ _action: formObj._action,
+ };
+ } else if (formObj._action == "submit code") {
+ //TODO: need check credentials to give back the portfolio course id
+ let { data } = await axios.get("/api/checkCredentials.php", {
+ params: {
+ emailaddress: formObj.emailAddress,
+ nineCode: formObj.code,
+ deviceName: formObj.deviceName,
+ },
+ });
+ console.log("submit code data", data);
+
+ //Attempt to store cookies!
+ const { data: jwtdata } = await axios.get(
+ `/api/jwt.php?emailaddress=${encodeURIComponent(
+ formObj.emailAddress,
+ )}&nineCode=${encodeURIComponent(formObj.code)}&deviceName=${
+ formObj.deviceName
+ }&newAccount=${data.existed}&stay=${
+ formObj.staySignedIn == "true" ? "1" : "0"
+ }`,
+ { withCredentials: true },
+ );
+
+ console.log("jwtdata", jwtdata);
+
+ //Redirect to portfolio
+ //or ask for name
+ if (data.hasFullName) {
+ //Redirect to portfolio
+ return redirect(`/portfolio/${data.portfolioCourseId}`);
+ } else {
+ //Redirect to askname
+ return redirect(`/signinName`);
+ }
+ }
+ } catch (e) {
+ return {
+ success: false,
+ message: e.response.data.message,
+ _action: formObj._action,
+ };
+ }
+}
+
+export function SignInCode() {
+ const { emailAddress, deviceName, staySignedIn } = useLoaderData();
+
+ const fetcher = useFetcher();
+ let formObj = {};
+ if (fetcher.formData !== undefined) {
+ formObj = Object.fromEntries(fetcher.formData);
+ }
+ console.log("fetcher.state", fetcher.state);
+ console.log("fetcher.data", fetcher.data);
+ console.log("formObj", formObj);
+ console.log("---------------------\n");
+
+ const [code, setCode] = useState("");
+ const [codeError, setCodeError] = useState(null);
+ const [isDisabled, setIsDisabled] = useState(false);
+ const [isExpired, setIsExpired] = useState(false);
+
+ //Handle code entry errors
+ if (fetcher.data?.success == false) {
+ //Guard against an infinite loop
+ if (codeError !== fetcher.data.message) {
+ setCodeError(fetcher.data.message);
+ setIsDisabled(false);
+ if (fetcher.data.message == "Code expired.") {
+ setIsExpired(true);
+ }
+ }
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ Check your email for the code.
+
+
+ Sign-in code (9 digit code):
+
+ setCode(code)}>
+
+
+
+
+
+
+
+
+
+
+
+ {codeError}
+
+
+
+
+
+ {isExpired ? (
+ {
+ setIsExpired(false);
+ setCodeError(null);
+ setCode("");
+ fetcher.submit(
+ {
+ _action: "send new code",
+ emailAddress: "char0042@umn.edu",
+ staySignedIn: true,
+ },
+ { method: "post" },
+ );
+ }}
+ >
+ Send New Code
+
+ ) : (
+ : undefined}
+ onClick={() => {
+ if (code == "") {
+ setCodeError(
+ "Please enter the nine digits sent to your email.",
+ );
+ } else if (code.length < 9) {
+ setCodeError("Please enter all nine digits.");
+ } else {
+ setCodeError(null);
+ setIsDisabled(true);
+ fetcher.submit(
+ {
+ _action: "submit code",
+ emailAddress,
+ deviceName,
+ staySignedIn,
+ code,
+ },
+ { method: "post" },
+ );
+ }
+ }}
+ >
+ Submit Code
+
+ )}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/Tools/_framework/Paths/SignInName.jsx b/src/Tools/_framework/Paths/SignInName.jsx
new file mode 100644
index 0000000000..b863fc5732
--- /dev/null
+++ b/src/Tools/_framework/Paths/SignInName.jsx
@@ -0,0 +1,533 @@
+import {
+ AbsoluteCenter,
+ Box,
+ Button,
+ Card,
+ CardBody,
+ CardFooter,
+ Center,
+ Checkbox,
+ Flex,
+ FormControl,
+ FormErrorMessage,
+ FormLabel,
+ HStack,
+ Heading,
+ Image,
+ Input,
+ PinInput,
+ PinInputField,
+ Spinner,
+ Stack,
+ Text,
+} from "@chakra-ui/react";
+import axios from "axios";
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import { redirect, useLoaderData } from "react-router";
+import { useFetcher } from "react-router-dom";
+
+export async function action({ params, request }) {
+ const formData = await request.formData();
+ const formObj = Object.fromEntries(formData);
+
+ try {
+ if (formObj._action == "submit email") {
+ let { data } = await axios.get("/api/sendSignInEmail.php", {
+ params: { emailaddress: formObj.emailAddress },
+ });
+ return {
+ _action: formObj._action,
+ deviceName: data.deviceName,
+ emailAddress: formObj.emailAddress,
+ staySignedIn: formObj.staySignedIn,
+ success: true,
+ };
+ } else if (formObj._action == "submit code") {
+ //TODO: need check credentials to give back the portfolio course id
+ let { data } = await axios.get("/api/checkCredentials.php", {
+ params: {
+ emailaddress: formObj.emailAddress,
+ nineCode: formObj.code,
+ deviceName: formObj.deviceName,
+ },
+ });
+
+ //Attempt to store cookies!
+ const { data: jwtdata } = await axios.get(
+ `/api/jwt.php?emailaddress=${encodeURIComponent(
+ formObj.emailAddress,
+ )}&nineCode=${encodeURIComponent(formObj.code)}&deviceName=${
+ formObj.deviceName
+ }&newAccount=${data.existed}&stay=${
+ formObj.staySignedIn == "true" ? "1" : "0"
+ }`,
+ { withCredentials: true },
+ );
+
+ console.log("jwtdata", jwtdata);
+
+ if (data.hasFullName == 1) {
+ //Redirect if we have their full name
+ //and there wasn't an error with sign in
+ // TODO: Redirect to portfolio
+ }
+ return {
+ _action: formObj._action,
+ isNewAccount: data.existed,
+ hasFullName: data.hasFullName,
+ success: true,
+ };
+ } else if (formObj._action == "submit name") {
+ let { data } = await axios.get("/api/saveUsersName.php", {
+ params: {
+ firstName: formObj.firstName,
+ lastName: formObj.lastName,
+ email: formObj.emailAddress,
+ },
+ });
+ console.log("data", data);
+ return true;
+
+ // TODO: Redirect to portfolio
+ }
+
+ return { success: true };
+ } catch (e) {
+ return {
+ success: false,
+ message: e.response.data.message,
+ _action: formObj._action,
+ };
+ }
+}
+
+function AskForEmailCard({ fetcher }) {
+ const [emailAddress, setEmailAddress] = useState("");
+ const [emailError, setEmailError] = useState(null);
+ const [isChecked, setIsChecked] = useState(false);
+ const [isDisabled, setIsDisabled] = useState(false);
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+
+ return (
+
+
+
+
+
+
+
+
+
+ Sign In via Email
+
+
+ Email address
+ {
+ let nextValue = e.target.value;
+ //Clear error if email is now good
+ if (emailError != null && emailRegex.test(nextValue)) {
+ setEmailError(null);
+ }
+ setEmailAddress(nextValue);
+ }}
+ />
+ {emailError}
+
+
+ setIsChecked(e.target.checked)}
+ >
+ Stay Signed In
+
+
+
+
+
+ : undefined}
+ colorScheme="blue"
+ onClick={() => {
+ if (emailAddress == "") {
+ setEmailError("Please enter your email address");
+ } else if (!emailRegex.test(emailAddress)) {
+ setEmailError("Invalid email format");
+ } else {
+ setEmailError(null);
+ setIsDisabled(true);
+ //Email is correct
+ fetcher.submit(
+ {
+ _action: "submit email",
+ emailAddress,
+ staySignedIn: isChecked,
+ },
+ { method: "post" },
+ );
+ }
+ }}
+ >
+ Send Email
+
+
+
+
+
+ );
+}
+
+function EnterCodeCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
+ const [code, setCode] = useState("");
+ const [codeError, setCodeError] = useState(null);
+ const [isDisabled, setIsDisabled] = useState(false);
+ const [isExpired, setIsExpired] = useState(false);
+
+ console.log("EnterCodeCard fetcher", fetcher);
+ //Handle code entry errors
+ if (fetcher.data?.success == false) {
+ //Guard against an infinite loop
+ if (codeError !== fetcher.data.message) {
+ setCodeError(fetcher.data.message);
+ setIsDisabled(false);
+ if (fetcher.data.message == "Code expired.") {
+ setIsExpired(true);
+ }
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ Check your email for the code.
+
+
+ Sign-in code (9 digit code):
+
+ setCode(code)}>
+
+
+
+
+
+
+
+
+
+
+
+ {codeError}
+
+
+
+
+
+ {isExpired ? (
+ {
+ //TODO: WHY NOT WORKING???
+ setIsExpired(false);
+ setCodeError(null);
+ setCode("");
+ console.log({
+ _action: "submit email",
+ emailAddress,
+ staySignedIn,
+ });
+ fetcher.submit(
+ {
+ _action: "submit email",
+ emailAddress: "char0042@umn.edu",
+ staySignedIn: true,
+ },
+ { method: "post" },
+ );
+ }}
+ >
+ Send New Code
+
+ ) : (
+ : undefined}
+ onClick={() => {
+ if (code == "") {
+ setCodeError(
+ "Please enter the nine digits sent to your email.",
+ );
+ } else if (code.length < 9) {
+ setCodeError("Please enter all nine digits.");
+ } else {
+ setCodeError(null);
+ setIsDisabled(true);
+ fetcher.submit(
+ {
+ _action: "submit code",
+ emailAddress,
+ deviceName,
+ staySignedIn,
+ code,
+ },
+ { method: "post" },
+ );
+ }
+ }}
+ >
+ Submit Code
+
+ )}
+
+
+
+
+ );
+}
+
+function AskForNameCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
+ const [firstName, setFirstName] = useState("");
+ const [firstNameError, setFirstNameError] = useState(null);
+ const [lastName, setLastName] = useState("");
+ const [lastNameError, setLastNameError] = useState(null);
+ const [isDisabled, setIsDisabled] = useState(false);
+
+ return (
+
+
+
+
+
+
+
+
+
+ Please Enter Your Name.
+
+
+ First Name:
+ {
+ if (e.target.value != "") {
+ setFirstNameError(null);
+ }
+ setFirstName(e.target.value);
+ }}
+ />
+ {firstNameError}
+
+
+ Last Name:
+ {
+ if (e.target.value != "") {
+ setLastNameError(null);
+ }
+ setLastName(e.target.value);
+ }}
+ />
+ {lastNameError}
+
+
+
+
+
+ : undefined}
+ onClick={() => {
+ if (firstName == "") {
+ setFirstNameError("Please enter your first name.");
+ }
+ if (lastName == "") {
+ setLastNameError("Please enter your last name.");
+ }
+ if (firstName != "" && lastName != "") {
+ setFirstNameError(null);
+ setLastNameError(null);
+ setIsDisabled(true);
+
+ fetcher.submit(
+ {
+ _action: "submit name",
+ firstName,
+ lastName,
+ emailAddress,
+ },
+ { method: "post" },
+ );
+ }
+ }}
+ >
+ Submit Name
+
+
+
+
+
+ );
+}
+
+export function SignInName() {
+ // const { success } = useLoaderData();
+ const fetcher = useFetcher();
+ let formObj = {};
+ if (fetcher.formData !== undefined) {
+ formObj = Object.fromEntries(fetcher.formData);
+ }
+ console.log("fetcher.state", fetcher.state);
+ console.log("fetcher.data", fetcher.data);
+ console.log("formObj", formObj);
+ console.log("---------------------\n");
+
+ let emailAddress = useRef(null);
+ let deviceName = useRef(null);
+ let staySignedIn = useRef(null);
+ //card is a ref because we need the card to stay
+ // and not have to track every possible state
+
+ //Enter Email
+ let card = useRef();
+ if (fetcher.state === "idle") {
+ if (
+ (fetcher.data?._action === "submit email" && fetcher.data?.success) ||
+ (fetcher.data?._action === "submit code" && !fetcher.data?.success)
+ ) {
+ //Enter Code
+ emailAddress.current = fetcher.data.emailAddress;
+ staySignedIn.current = fetcher.data.staySignedIn;
+ deviceName.current = fetcher.data.deviceName;
+
+ card.current = (
+
+ );
+ } else if (
+ (fetcher.data?._action === "submit code" && fetcher.data?.success) ||
+ (fetcher.data?._action === "submit name" && !fetcher.data?.success)
+ ) {
+ //Enter Name
+ card.current = (
+
+ );
+ }
+ }
+
+ // card = ;
+
+ // const setActivityByDoenetId = useSetRecoilState(itemByDoenetId(doenetId)); //TODO: remove after recoil is gone
+ // const setPageByDoenetId = useSetRecoilState(itemByDoenetId(pageId)); //TODO: remove after recoil is gone
+
+ // let location = useLocation();
+
+ // const navigate = useNavigate();
+
+ // const [recoilPageToolView, setRecoilPageToolView] =
+ // useRecoilState(pageToolViewAtom);
+
+ // let navigateTo = useRef("");
+
+ // if (navigateTo.current != "") {
+ // const newHref = navigateTo.current;
+ // navigateTo.current = "";
+ // location.href = newHref;
+ // navigate(newHref);
+ // }
+
+ //Optimistic UI
+ // let effectiveLabel = activityData.pageLabel;
+ // if (activityData.isSinglePage) {
+ // effectiveLabel = activityData.label;
+ // if (fetcher.data?._action == "update label") {
+ // effectiveLabel = fetcher.data.label;
+ // }
+ // } else {
+ // if (fetcher.data?._action == "update page label") {
+ // effectiveLabel = fetcher.data.pageLabel;
+ // }
+ // }
+
+ return (
+ <>
+
+ {card.current}
+
+ >
+ );
+}
diff --git a/src/index.jsx b/src/index.jsx
index 54be7bcbd5..cd4b71e17e 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -83,9 +83,18 @@ import {
} from "./Tools/_framework/Paths/CourseLinkPageViewer";
import {
SignIn,
- loader as signInLoader,
action as signInAction,
} from "./Tools/_framework/Paths/SignIn";
+import {
+ SignInCode,
+ loader as signInCodeLoader,
+ action as signInCodeAction,
+} from "./Tools/_framework/Paths/SignInCode";
+import {
+ SignInName,
+ action as signInNameAction,
+ loader as signInNameLoader,
+} from "./Tools/_framework/Paths/SignInName";
{
/* TESTING 123 */
@@ -327,7 +336,6 @@ const router = createBrowserRouter([
},
{
path: "signin",
- loader: signInLoader,
action: signInAction,
errorElement: (
@@ -340,6 +348,36 @@ const router = createBrowserRouter([
),
},
+ {
+ path: "signinCode",
+ loader: signInCodeLoader,
+ action: signInCodeAction,
+ errorElement: (
+
+
+
+ ),
+ element: (
+
+
+
+ ),
+ },
+ {
+ path: "signinName",
+ loader: signInNameLoader,
+ action: signInNameAction,
+ errorElement: (
+
+
+
+ ),
+ element: (
+
+
+
+ ),
+ },
{
path: "public",
loader: editorSupportPanelLoader,
From 4fa1e9b2106abf51449519f8c056e15b9cb8d74a Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Sun, 22 Oct 2023 15:41:54 -0500
Subject: [PATCH 61/83] basics of three path method
---
public/api/baseModel.php | 26 +-
public/api/sendSignInEmail.php | 40 +-
src/Tools/_framework/Paths/SignInCode.jsx | 63 +--
src/Tools/_framework/Paths/SignInName.jsx | 582 ++++------------------
src/index.jsx | 4 -
5 files changed, 169 insertions(+), 546 deletions(-)
diff --git a/public/api/baseModel.php b/public/api/baseModel.php
index 12235e448a..e74c27cf96 100644
--- a/public/api/baseModel.php
+++ b/public/api/baseModel.php
@@ -28,11 +28,14 @@ public static function runQuery($conn, $query) {
public static function queryFetchAssoc($conn, $query) {
$result = Base_Model::runQuery($conn, $query);
if ($result->num_rows > 0) {
- $rows = [];
+ $data = [];
while($row = $result->fetch_assoc()){
- $rows[] = $row;
+ $data['rows'] = $row;
+ foreach($row as $key => $value){
+ $data[$key][] = $value;
+ }
}
- return $rows;
+ return $data;
} else {
return [];
}
@@ -45,18 +48,23 @@ public static function queryFetchAssoc($conn, $query) {
*
* If more than one row is returned, throws an exception.
*/
- public static function queryExpectingOneRow($conn, $query) {
- $rows = Base_Model::queryFetchAssoc($conn, $query);
-
- if (count($rows) == 1) {
- return $rows[0];
- } else if (count($rows) == 0) {
+ public static function queryOneRowOrError($conn, $query) {
+ $data = Base_Model::queryFetchAssoc($conn, $query);
+
+ if (count($data['rows']) == 1) {
+ return $data;
+ } else if (count($data['rows']) == 0) {
return null;
} else {
+
throw new Exception("Unexpected error, only expected one row from this query.");
+ error_log("Unexpected error, only expected one row from this query." .
+ "\n " . $conn->error .
+ "\n" . $query);
}
}
+
/**
* Validate that a list of keys are present in a given associative array.
*
diff --git a/public/api/sendSignInEmail.php b/public/api/sendSignInEmail.php
index c3e616c013..081e4044bc 100644
--- a/public/api/sendSignInEmail.php
+++ b/public/api/sendSignInEmail.php
@@ -6,39 +6,41 @@
header('Content-Type: application/json');
include "db_connection.php";
+include "baseModel.php";
$emailaddress = mysqli_real_escape_string($conn,$_REQUEST["emailaddress"]);
$deviceNames = include "deviceNames.php";
-//Nine digit random number
-$signInCode = rand(100000000,999999999);
$response_arr;
try {
+ Base_Model::checkForRequiredInputs($_REQUEST,["emailaddress"]);
+
+ //Nine digit random number
+ $signInCode = rand(100000000,999999999);
-$sql = "SELECT email, userId
-FROM user
-WHERE email='$emailaddress'";
+ $sql = "SELECT email, userId
+ FROM user
+ WHERE email='$emailaddress'";
-$result = $conn->query($sql);
+ $userEmailArray = Base_Model::queryFetchAssoc($conn, $sql);
-if ($result->num_rows > 0){
- //Already have an email with this account
- $row = $result->fetch_assoc();
- $user_id = $row['userId'];
- //unique deviceName
- //Remove device names which are already in use
+if (count($userEmailArray) > 0){
+ //We have an email with this account
+
+ $user_id = $userEmailArray['userId'][0];
+ //In order to maintain unique deviceNames
+ //remove device names which are already in use
$sql = "
SELECT deviceName
FROM user_device
WHERE userId='$user_id'
+ AND signedIn=1
";
- $result = $conn->query($sql);
- $used_deviceNames = array();
- while($row = $result->fetch_assoc()){
- array_push($used_deviceNames,$row['deviceName']);
- }
+ $devicesArray = Base_Model::queryFetchAssoc($conn, $sql);
+ $used_deviceNames = $devicesArray['deviceName'] != null ? $devicesArray['deviceName'] : [];
+
$deviceNames = array_values(array_diff($deviceNames,$used_deviceNames));
if (count($deviceNames) < 1){
//Ran out of device names
@@ -54,14 +56,14 @@
//New email address
$user_id = include "randomId.php";
$sql = "INSERT INTO user (userId,email) VALUE ('$user_id','$emailaddress')";
- $result = $conn->query($sql);
+ Base_Model::runQuery($conn,$sql);
//Define device name
$randomNumber = rand(0,(count($deviceNames) - 1));
$deviceName = $deviceNames[$randomNumber];
}
$sql = "INSERT INTO user_device (userId,email,signInCode,timestampOfSignInCode, deviceName)
VALUE ('$user_id','$emailaddress','$signInCode',NOW(),'$deviceName')";
- $result = $conn->query($sql);
+Base_Model::runQuery($conn,$sql);
// Generate and modify email content
diff --git a/src/Tools/_framework/Paths/SignInCode.jsx b/src/Tools/_framework/Paths/SignInCode.jsx
index b3cdeae43f..a82779b44e 100644
--- a/src/Tools/_framework/Paths/SignInCode.jsx
+++ b/src/Tools/_framework/Paths/SignInCode.jsx
@@ -5,8 +5,6 @@ import {
Card,
CardBody,
CardFooter,
- Center,
- Checkbox,
Flex,
FormControl,
FormErrorMessage,
@@ -14,35 +12,28 @@ import {
HStack,
Heading,
Image,
- Input,
PinInput,
PinInputField,
Spinner,
Stack,
- Text,
} from "@chakra-ui/react";
import axios from "axios";
import React, { useState } from "react";
import { redirect, useLoaderData } from "react-router";
import { useFetcher } from "react-router-dom";
-export async function loader({ request }) {
- //Search Parameters to useLoaderData
+export async function action({ request }) {
+ const formData = await request.formData();
+ const formObj = Object.fromEntries(formData);
const url = new URL(request.url);
const emailAddress = url.searchParams.get("email");
const deviceName = url.searchParams.get("device");
const staySignedIn = url.searchParams.get("stay");
- return { emailAddress, deviceName, staySignedIn };
-}
-
-export async function action({ params, request }) {
- const formData = await request.formData();
- const formObj = Object.fromEntries(formData);
try {
if (formObj._action == "send new code") {
let { data } = await axios.get("/api/sendSignInEmail.php", {
- params: { emailaddress: formObj.emailAddress },
+ params: { emailaddress: emailAddress },
});
return {
success: true,
@@ -52,27 +43,24 @@ export async function action({ params, request }) {
//TODO: need check credentials to give back the portfolio course id
let { data } = await axios.get("/api/checkCredentials.php", {
params: {
- emailaddress: formObj.emailAddress,
+ emailaddress: emailAddress,
nineCode: formObj.code,
- deviceName: formObj.deviceName,
+ deviceName: deviceName,
},
});
- console.log("submit code data", data);
//Attempt to store cookies!
const { data: jwtdata } = await axios.get(
`/api/jwt.php?emailaddress=${encodeURIComponent(
- formObj.emailAddress,
- )}&nineCode=${encodeURIComponent(formObj.code)}&deviceName=${
- formObj.deviceName
- }&newAccount=${data.existed}&stay=${
- formObj.staySignedIn == "true" ? "1" : "0"
+ emailAddress,
+ )}&nineCode=${encodeURIComponent(
+ formObj.code,
+ )}&deviceName=${deviceName}&newAccount=${data.existed}&stay=${
+ staySignedIn == "true" ? "1" : "0"
}`,
{ withCredentials: true },
);
- console.log("jwtdata", jwtdata);
-
//Redirect to portfolio
//or ask for name
if (data.hasFullName) {
@@ -80,7 +68,11 @@ export async function action({ params, request }) {
return redirect(`/portfolio/${data.portfolioCourseId}`);
} else {
//Redirect to askname
- return redirect(`/signinName`);
+ return redirect(
+ `/signinName?email=${encodeURIComponent(
+ emailAddress,
+ )}&portfolioId=${encodeURIComponent(data.portfolioCourseId)}`,
+ );
}
}
} catch (e) {
@@ -93,17 +85,15 @@ export async function action({ params, request }) {
}
export function SignInCode() {
- const { emailAddress, deviceName, staySignedIn } = useLoaderData();
-
const fetcher = useFetcher();
- let formObj = {};
- if (fetcher.formData !== undefined) {
- formObj = Object.fromEntries(fetcher.formData);
- }
- console.log("fetcher.state", fetcher.state);
- console.log("fetcher.data", fetcher.data);
- console.log("formObj", formObj);
- console.log("---------------------\n");
+ // let formObj = {};
+ // if (fetcher.formData !== undefined) {
+ // formObj = Object.fromEntries(fetcher.formData);
+ // }
+ // console.log("fetcher.state", fetcher.state);
+ // console.log("fetcher.data", fetcher.data);
+ // console.log("formObj", formObj);
+ // console.log("---------------------\n");
const [code, setCode] = useState("");
const [codeError, setCodeError] = useState(null);
@@ -186,8 +176,6 @@ export function SignInCode() {
fetcher.submit(
{
_action: "send new code",
- emailAddress: "char0042@umn.edu",
- staySignedIn: true,
},
{ method: "post" },
);
@@ -214,9 +202,6 @@ export function SignInCode() {
fetcher.submit(
{
_action: "submit code",
- emailAddress,
- deviceName,
- staySignedIn,
code,
},
{ method: "post" },
diff --git a/src/Tools/_framework/Paths/SignInName.jsx b/src/Tools/_framework/Paths/SignInName.jsx
index b863fc5732..f406e337b1 100644
--- a/src/Tools/_framework/Paths/SignInName.jsx
+++ b/src/Tools/_framework/Paths/SignInName.jsx
@@ -5,90 +5,40 @@ import {
Card,
CardBody,
CardFooter,
- Center,
- Checkbox,
Flex,
FormControl,
FormErrorMessage,
FormLabel,
- HStack,
Heading,
Image,
Input,
- PinInput,
- PinInputField,
Spinner,
Stack,
- Text,
} from "@chakra-ui/react";
import axios from "axios";
-import React, { useCallback, useEffect, useRef, useState } from "react";
+import React, { useState } from "react";
import { redirect, useLoaderData } from "react-router";
import { useFetcher } from "react-router-dom";
-export async function action({ params, request }) {
+export async function action({ request }) {
const formData = await request.formData();
const formObj = Object.fromEntries(formData);
+ const url = new URL(request.url);
+ const portfolioId = url.searchParams.get("portfolioId");
+ const emailAddress = url.searchParams.get("email");
try {
- if (formObj._action == "submit email") {
- let { data } = await axios.get("/api/sendSignInEmail.php", {
- params: { emailaddress: formObj.emailAddress },
- });
- return {
- _action: formObj._action,
- deviceName: data.deviceName,
- emailAddress: formObj.emailAddress,
- staySignedIn: formObj.staySignedIn,
- success: true,
- };
- } else if (formObj._action == "submit code") {
- //TODO: need check credentials to give back the portfolio course id
- let { data } = await axios.get("/api/checkCredentials.php", {
- params: {
- emailaddress: formObj.emailAddress,
- nineCode: formObj.code,
- deviceName: formObj.deviceName,
- },
- });
-
- //Attempt to store cookies!
- const { data: jwtdata } = await axios.get(
- `/api/jwt.php?emailaddress=${encodeURIComponent(
- formObj.emailAddress,
- )}&nineCode=${encodeURIComponent(formObj.code)}&deviceName=${
- formObj.deviceName
- }&newAccount=${data.existed}&stay=${
- formObj.staySignedIn == "true" ? "1" : "0"
- }`,
- { withCredentials: true },
- );
-
- console.log("jwtdata", jwtdata);
-
- if (data.hasFullName == 1) {
- //Redirect if we have their full name
- //and there wasn't an error with sign in
- // TODO: Redirect to portfolio
- }
- return {
- _action: formObj._action,
- isNewAccount: data.existed,
- hasFullName: data.hasFullName,
- success: true,
- };
- } else if (formObj._action == "submit name") {
+ if (formObj._action == "submit name") {
let { data } = await axios.get("/api/saveUsersName.php", {
params: {
firstName: formObj.firstName,
lastName: formObj.lastName,
- email: formObj.emailAddress,
+ email: emailAddress,
},
});
- console.log("data", data);
- return true;
- // TODO: Redirect to portfolio
+ //Redirect to portfolio
+ return redirect(`/portfolio/${portfolioId}`);
}
return { success: true };
@@ -101,432 +51,114 @@ export async function action({ params, request }) {
}
}
-function AskForEmailCard({ fetcher }) {
- const [emailAddress, setEmailAddress] = useState("");
- const [emailError, setEmailError] = useState(null);
- const [isChecked, setIsChecked] = useState(false);
- const [isDisabled, setIsDisabled] = useState(false);
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-
- return (
-
-
-
-
-
-
-
-
-
- Sign In via Email
-
-
- Email address
- {
- let nextValue = e.target.value;
- //Clear error if email is now good
- if (emailError != null && emailRegex.test(nextValue)) {
- setEmailError(null);
- }
- setEmailAddress(nextValue);
- }}
- />
- {emailError}
-
-
- setIsChecked(e.target.checked)}
- >
- Stay Signed In
-
-
-
-
-
- : undefined}
- colorScheme="blue"
- onClick={() => {
- if (emailAddress == "") {
- setEmailError("Please enter your email address");
- } else if (!emailRegex.test(emailAddress)) {
- setEmailError("Invalid email format");
- } else {
- setEmailError(null);
- setIsDisabled(true);
- //Email is correct
- fetcher.submit(
- {
- _action: "submit email",
- emailAddress,
- staySignedIn: isChecked,
- },
- { method: "post" },
- );
- }
- }}
- >
- Send Email
-
-
-
-
-
- );
-}
-
-function EnterCodeCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
- const [code, setCode] = useState("");
- const [codeError, setCodeError] = useState(null);
- const [isDisabled, setIsDisabled] = useState(false);
- const [isExpired, setIsExpired] = useState(false);
-
- console.log("EnterCodeCard fetcher", fetcher);
- //Handle code entry errors
- if (fetcher.data?.success == false) {
- //Guard against an infinite loop
- if (codeError !== fetcher.data.message) {
- setCodeError(fetcher.data.message);
- setIsDisabled(false);
- if (fetcher.data.message == "Code expired.") {
- setIsExpired(true);
- }
- }
- }
-
- return (
-
-
-
-
-
-
-
-
-
- Check your email for the code.
-
-
- Sign-in code (9 digit code):
-
- setCode(code)}>
-
-
-
-
-
-
-
-
-
-
-
- {codeError}
-
-
-
-
-
- {isExpired ? (
- {
- //TODO: WHY NOT WORKING???
- setIsExpired(false);
- setCodeError(null);
- setCode("");
- console.log({
- _action: "submit email",
- emailAddress,
- staySignedIn,
- });
- fetcher.submit(
- {
- _action: "submit email",
- emailAddress: "char0042@umn.edu",
- staySignedIn: true,
- },
- { method: "post" },
- );
- }}
- >
- Send New Code
-
- ) : (
- : undefined}
- onClick={() => {
- if (code == "") {
- setCodeError(
- "Please enter the nine digits sent to your email.",
- );
- } else if (code.length < 9) {
- setCodeError("Please enter all nine digits.");
- } else {
- setCodeError(null);
- setIsDisabled(true);
- fetcher.submit(
- {
- _action: "submit code",
- emailAddress,
- deviceName,
- staySignedIn,
- code,
- },
- { method: "post" },
- );
- }
- }}
- >
- Submit Code
-
- )}
-
-
-
-
- );
-}
+export function SignInName() {
+ const fetcher = useFetcher();
+ // let formObj = {};
+ // if (fetcher.formData !== undefined) {
+ // formObj = Object.fromEntries(fetcher.formData);
+ // }
-function AskForNameCard({ fetcher, emailAddress, deviceName, staySignedIn }) {
const [firstName, setFirstName] = useState("");
const [firstNameError, setFirstNameError] = useState(null);
const [lastName, setLastName] = useState("");
const [lastNameError, setLastNameError] = useState(null);
const [isDisabled, setIsDisabled] = useState(false);
- return (
-
-
-
-
-
-
-
-
-
- Please Enter Your Name.
-
-
- First Name:
- {
- if (e.target.value != "") {
- setFirstNameError(null);
- }
- setFirstName(e.target.value);
- }}
- />
- {firstNameError}
-
-
- Last Name:
- {
- if (e.target.value != "") {
- setLastNameError(null);
- }
- setLastName(e.target.value);
- }}
- />
- {lastNameError}
-
-
-
-
-
- : undefined}
- onClick={() => {
- if (firstName == "") {
- setFirstNameError("Please enter your first name.");
- }
- if (lastName == "") {
- setLastNameError("Please enter your last name.");
- }
- if (firstName != "" && lastName != "") {
- setFirstNameError(null);
- setLastNameError(null);
- setIsDisabled(true);
-
- fetcher.submit(
- {
- _action: "submit name",
- firstName,
- lastName,
- emailAddress,
- },
- { method: "post" },
- );
- }
- }}
- >
- Submit Name
-
-
-
-
-
- );
-}
-
-export function SignInName() {
- // const { success } = useLoaderData();
- const fetcher = useFetcher();
- let formObj = {};
- if (fetcher.formData !== undefined) {
- formObj = Object.fromEntries(fetcher.formData);
- }
- console.log("fetcher.state", fetcher.state);
- console.log("fetcher.data", fetcher.data);
- console.log("formObj", formObj);
- console.log("---------------------\n");
-
- let emailAddress = useRef(null);
- let deviceName = useRef(null);
- let staySignedIn = useRef(null);
- //card is a ref because we need the card to stay
- // and not have to track every possible state
-
- //Enter Email
- let card = useRef();
- if (fetcher.state === "idle") {
- if (
- (fetcher.data?._action === "submit email" && fetcher.data?.success) ||
- (fetcher.data?._action === "submit code" && !fetcher.data?.success)
- ) {
- //Enter Code
- emailAddress.current = fetcher.data.emailAddress;
- staySignedIn.current = fetcher.data.staySignedIn;
- deviceName.current = fetcher.data.deviceName;
-
- card.current = (
-
- );
- } else if (
- (fetcher.data?._action === "submit code" && fetcher.data?.success) ||
- (fetcher.data?._action === "submit name" && !fetcher.data?.success)
- ) {
- //Enter Name
- card.current = (
-
- );
- }
- }
-
- // card = ;
-
- // const setActivityByDoenetId = useSetRecoilState(itemByDoenetId(doenetId)); //TODO: remove after recoil is gone
- // const setPageByDoenetId = useSetRecoilState(itemByDoenetId(pageId)); //TODO: remove after recoil is gone
-
- // let location = useLocation();
-
- // const navigate = useNavigate();
-
- // const [recoilPageToolView, setRecoilPageToolView] =
- // useRecoilState(pageToolViewAtom);
-
- // let navigateTo = useRef("");
-
- // if (navigateTo.current != "") {
- // const newHref = navigateTo.current;
- // navigateTo.current = "";
- // location.href = newHref;
- // navigate(newHref);
- // }
-
- //Optimistic UI
- // let effectiveLabel = activityData.pageLabel;
- // if (activityData.isSinglePage) {
- // effectiveLabel = activityData.label;
- // if (fetcher.data?._action == "update label") {
- // effectiveLabel = fetcher.data.label;
- // }
- // } else {
- // if (fetcher.data?._action == "update page label") {
- // effectiveLabel = fetcher.data.pageLabel;
- // }
- // }
-
return (
<>
- {card.current}
+
+
+
+
+
+
+
+
+
+
+ Please Enter Your Name.
+
+
+ First Name:
+ {
+ if (e.target.value != "") {
+ setFirstNameError(null);
+ }
+ setFirstName(e.target.value);
+ }}
+ />
+ {firstNameError}
+
+
+ Last Name:
+ {
+ if (e.target.value != "") {
+ setLastNameError(null);
+ }
+ setLastName(e.target.value);
+ }}
+ />
+ {lastNameError}
+
+
+
+
+
+ : undefined}
+ onClick={() => {
+ if (firstName == "") {
+ setFirstNameError("Please enter your first name.");
+ }
+ if (lastName == "") {
+ setLastNameError("Please enter your last name.");
+ }
+ if (firstName != "" && lastName != "") {
+ setFirstNameError(null);
+ setLastNameError(null);
+ setIsDisabled(true);
+
+ fetcher.submit(
+ {
+ _action: "submit name",
+ firstName,
+ lastName,
+ },
+ { method: "post" },
+ );
+ }
+ }}
+ >
+ Submit Name
+
+
+
+
+
+
>
);
diff --git a/src/index.jsx b/src/index.jsx
index cd4b71e17e..ba45b22000 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -87,13 +87,11 @@ import {
} from "./Tools/_framework/Paths/SignIn";
import {
SignInCode,
- loader as signInCodeLoader,
action as signInCodeAction,
} from "./Tools/_framework/Paths/SignInCode";
import {
SignInName,
action as signInNameAction,
- loader as signInNameLoader,
} from "./Tools/_framework/Paths/SignInName";
{
@@ -350,7 +348,6 @@ const router = createBrowserRouter([
},
{
path: "signinCode",
- loader: signInCodeLoader,
action: signInCodeAction,
errorElement: (
@@ -365,7 +362,6 @@ const router = createBrowserRouter([
},
{
path: "signinName",
- loader: signInNameLoader,
action: signInNameAction,
errorElement: (
From 3a06d31cfd53418896c4536289c3c64e9953629a Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Sun, 22 Oct 2023 16:14:16 -0500
Subject: [PATCH 62/83] handle code expired
---
public/api/checkCredentials.php | 18 +++----
public/api/jwt.php | 2 +
public/api/saveUsersName.php | 60 +++++++++++++----------
src/Tools/_framework/Paths/SignInCode.jsx | 12 +----
4 files changed, 44 insertions(+), 48 deletions(-)
diff --git a/public/api/checkCredentials.php b/public/api/checkCredentials.php
index e6181dea9a..9743624a69 100644
--- a/public/api/checkCredentials.php
+++ b/public/api/checkCredentials.php
@@ -6,23 +6,15 @@
header('Content-Type: application/json');
include "db_connection.php";
+include "baseModel.php";
$emailaddress = mysqli_real_escape_string($conn,$_REQUEST["emailaddress"]);
$nineCode = mysqli_real_escape_string($conn,$_REQUEST["nineCode"]);
$deviceName = mysqli_real_escape_string($conn,$_REQUEST["deviceName"]);
-if(!isset($_REQUEST["emailaddress"])){
- throw new Exception("Internal Error: missing emailaddress");
-}
-if(!isset($_REQUEST["nineCode"])){
- throw new Exception("Internal Error: missing nineCode");
-}
-if(!isset($_REQUEST["deviceName"])){
- throw new Exception("Internal Error: missing deviceName");
-}
-
$response_arr;
try {
+ Base_Model::checkForRequiredInputs($_REQUEST,["emailaddress","nineCode","deviceName"]);
//Check if expired
$sql = "SELECT TIMESTAMPDIFF(MINUTE, timestampOfSignInCode, NOW()) AS minutes
@@ -36,6 +28,7 @@
$existed = true;
$hasFullName = false;
$reason = "";
+throw new Exception("Code expired"); //Delete me
//Check if it took longer than 10 minutes to enter the code
if ($row['minutes'] > 10){
@@ -110,6 +103,9 @@
"portfolioCourseId" => $portfolioCourseId,
);
+http_response_code(200);
+
+
} catch (Exception $e) {
$response_arr = [
'success' => false,
@@ -122,4 +118,4 @@
echo json_encode($response_arr);
$conn->close();
}
-
+?>
diff --git a/public/api/jwt.php b/public/api/jwt.php
index 5731d3ee13..8110f24052 100644
--- a/public/api/jwt.php
+++ b/public/api/jwt.php
@@ -1,5 +1,6 @@
query($sql);
-
-$response_arr = array(
- 'success' => $success,
- 'message' => $message,
-);
-
-// set response code - 200 OK
-http_response_code(200);
-
-// make it json format
-echo json_encode($response_arr);
-
-$conn->close();
-?>
+$response_arr;
+try {
+ Base_Model::checkForRequiredInputs($_REQUEST,["email","firstName","lastName"]);
+
+ $sql = "
+ UPDATE user
+ SET firstName='$firstName',
+ lastName='$lastName'
+ WHERE email='$email'
+ ";
+ $conn->query($sql);
+
+ $response_arr = array(
+ 'success' => true,
+ 'message' => $message,
+ );
+
+ http_response_code(200);
+
+} catch (Exception $e) {
+ $response_arr = [
+ 'success' => false,
+ 'message' => $e->getMessage(),
+ ];
+ http_response_code(400);
+
+} finally {
+ // make it json format
+ echo json_encode($response_arr);
+ $conn->close();
+}
+?>
\ No newline at end of file
diff --git a/src/Tools/_framework/Paths/SignInCode.jsx b/src/Tools/_framework/Paths/SignInCode.jsx
index a82779b44e..39218e2ec4 100644
--- a/src/Tools/_framework/Paths/SignInCode.jsx
+++ b/src/Tools/_framework/Paths/SignInCode.jsx
@@ -86,14 +86,6 @@ export async function action({ request }) {
export function SignInCode() {
const fetcher = useFetcher();
- // let formObj = {};
- // if (fetcher.formData !== undefined) {
- // formObj = Object.fromEntries(fetcher.formData);
- // }
- // console.log("fetcher.state", fetcher.state);
- // console.log("fetcher.data", fetcher.data);
- // console.log("formObj", formObj);
- // console.log("---------------------\n");
const [code, setCode] = useState("");
const [codeError, setCodeError] = useState(null);
@@ -101,12 +93,12 @@ export function SignInCode() {
const [isExpired, setIsExpired] = useState(false);
//Handle code entry errors
- if (fetcher.data?.success == false) {
+ if (fetcher.data?.success === false && fetcher.state === "idle") {
//Guard against an infinite loop
if (codeError !== fetcher.data.message) {
setCodeError(fetcher.data.message);
setIsDisabled(false);
- if (fetcher.data.message == "Code expired.") {
+ if (fetcher.data.message == "Code expired") {
setIsExpired(true);
}
}
From a7bf41029af225f20d163d9e66d58218895e3311 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Mon, 23 Oct 2023 15:59:17 -0500
Subject: [PATCH 63/83] fixed bugs
---
public/api/checkCredentials.php | 49 +++++----
public/api/jwt.php | 31 +++---
public/api/sendSignInEmail.php | 125 ++++++++++++----------
src/Tools/_framework/Paths/SignInCode.jsx | 7 +-
4 files changed, 122 insertions(+), 90 deletions(-)
diff --git a/public/api/checkCredentials.php b/public/api/checkCredentials.php
index 9743624a69..7c5b3665a9 100644
--- a/public/api/checkCredentials.php
+++ b/public/api/checkCredentials.php
@@ -16,28 +16,37 @@
try {
Base_Model::checkForRequiredInputs($_REQUEST,["emailaddress","nineCode","deviceName"]);
-//Check if expired
-$sql = "SELECT TIMESTAMPDIFF(MINUTE, timestampOfSignInCode, NOW()) AS minutes
-FROM user_device
-WHERE email='$emailaddress' AND deviceName='$deviceName'";
-
-$result = $conn->query($sql);
-$row = $result->fetch_assoc();
-
-//Assume it already exists
-$existed = true;
-$hasFullName = false;
-$reason = "";
-throw new Exception("Code expired"); //Delete me
-
-//Check if it took longer than 10 minutes to enter the code
-if ($row['minutes'] > 10){
- throw new Exception("Code expired");
-}
+ //Check if expired
+ $sql = "SELECT TIMESTAMPDIFF(MINUTE, timestampOfSignInCode, NOW()) AS minutes
+ FROM user_device
+ WHERE email='$emailaddress' AND deviceName='$deviceName'
+ ORDER BY timestampOfSignInCode DESC
+ LIMIT 1
+ ";
+
+ $result = $conn->query($sql);
+ $row = $result->fetch_assoc();
+
+ //Assume it already exists
+ $existed = true;
+ $hasFullName = false;
+ $reason = "";
+
+ // throw new Exception("Code expired"); //DELETE ME!!!
+
+
+ //Check if it took longer than 10 minutes to enter the code
+ if ($row['minutes'] > 10){
+ throw new Exception("Code expired");
+ }
+ //Only the most recent one
$sql = "SELECT signInCode AS nineCode
FROM user_device
- WHERE email='$emailaddress' AND deviceName='$deviceName'";
+ WHERE email='$emailaddress' AND deviceName='$deviceName'
+ ORDER BY timestampOfSignInCode DESC
+ LIMIT 1
+ ";
$result = $conn->query($sql);
$row = $result->fetch_assoc();
@@ -91,7 +100,7 @@
WHERE u.email = '$emailaddress'";
$result = $conn->query($sql);
$row = $result->fetch_assoc();
- $portfolioCourseId = "_";
+ $portfolioCourseId = "not_created";
if ($result->num_rows > 0) {
$portfolioCourseId = $row['courseId'];
}
diff --git a/public/api/jwt.php b/public/api/jwt.php
index 8110f24052..9e3eb4f169 100644
--- a/public/api/jwt.php
+++ b/public/api/jwt.php
@@ -16,21 +16,26 @@
$response_arr;
try {
Base_Model::checkForRequiredInputs($_REQUEST,["emailaddress","nineCode","deviceName","newAccount","stay"]);
-//Check if expired
-$sql = "SELECT TIMESTAMPDIFF(MINUTE, timestampOfSignInCode, NOW()) AS minutes
-FROM user_device
-WHERE email='$emailaddress' AND deviceName='$deviceName'";
+ //Check if expired
+ $sql = "SELECT TIMESTAMPDIFF(MINUTE, timestampOfSignInCode, NOW()) AS minutes
+ FROM user_device
+ WHERE email='$emailaddress' AND deviceName='$deviceName'
+ ORDER BY timestampOfSignInCode DESC
+ LIMIT 1
+ ";
-$result = $conn->query($sql);
-$row = $result->fetch_assoc();
+ $result = $conn->query($sql);
+ $row = $result->fetch_assoc();
-//Check if it took longer than 10 minutes to enter the code
-if ($row['minutes'] > 10) {
- throw new Exception("Code expired.");
-}
- $sql = "SELECT signInCode AS nineCode, userId
- FROM user_device
- WHERE email='$emailaddress' AND deviceName='$deviceName'";
+ //Check if it took longer than 10 minutes to enter the code
+ if ($row['minutes'] > 10) {
+ throw new Exception("Code expired.");
+ }
+ $sql = "SELECT signInCode AS nineCode,userId
+ FROM user_device
+ WHERE email='$emailaddress' AND deviceName='$deviceName'
+ ORDER BY timestampOfSignInCode DESC
+ LIMIT 1";
$result = $conn->query($sql);
$row = $result->fetch_assoc();
$userId = $row['userId'];
diff --git a/public/api/sendSignInEmail.php b/public/api/sendSignInEmail.php
index 081e4044bc..d2e903d3e1 100644
--- a/public/api/sendSignInEmail.php
+++ b/public/api/sendSignInEmail.php
@@ -9,82 +9,99 @@
include "baseModel.php";
$emailaddress = mysqli_real_escape_string($conn,$_REQUEST["emailaddress"]);
+$deviceName = mysqli_real_escape_string($conn,$_REQUEST["deviceName"]);
-$deviceNames = include "deviceNames.php";
$response_arr;
try {
Base_Model::checkForRequiredInputs($_REQUEST,["emailaddress"]);
-
- //Nine digit random number
+
+ //Create a nine digit random number
$signInCode = rand(100000000,999999999);
+ //Do we have an account with this email?
$sql = "SELECT email, userId
FROM user
WHERE email='$emailaddress'";
$userEmailArray = Base_Model::queryFetchAssoc($conn, $sql);
-
-if (count($userEmailArray) > 0){
- //We have an email with this account
-
- $user_id = $userEmailArray['userId'][0];
- //In order to maintain unique deviceNames
- //remove device names which are already in use
- $sql = "
- SELECT deviceName
- FROM user_device
- WHERE userId='$user_id'
- AND signedIn=1
- ";
-
- $devicesArray = Base_Model::queryFetchAssoc($conn, $sql);
- $used_deviceNames = $devicesArray['deviceName'] != null ? $devicesArray['deviceName'] : [];
-
- $deviceNames = array_values(array_diff($deviceNames,$used_deviceNames));
- if (count($deviceNames) < 1){
- //Ran out of device names
- $deviceName = include 'randomId.php';
+ if (count($userEmailArray) < 1){
+ //We need an account created
+ $user_id = include "randomId.php";
+ $sql = "INSERT INTO user (userId,email) VALUE ('$user_id','$emailaddress')";
+ Base_Model::runQuery($conn,$sql);
}else{
- //Pick from what is left
- $randomNumber = rand(0,(count($deviceNames) - 1));
- $deviceName = $deviceNames[$randomNumber];
+ $user_id = $userEmailArray['userId'][0];
}
+ if (array_key_exists("deviceName",$_REQUEST)){
+ //Already have a device name
+ //Just update the signInCode and timestampOfSignInCode
+ //of the latest entry of that device name
+ $sql = "UPDATE user_device
+ SET signInCode = '$signInCode', timestampOfSignInCode = NOW()
+ WHERE (userId, email, deviceName, timestampOfSignInCode) = (
+ SELECT userId, email, deviceName, MAX(timestampOfSignInCode)
+ FROM (
+ SELECT * FROM user_device
+ ) AS temp
+ WHERE userId = '$user_id' AND email = '$emailaddress' AND deviceName = '$deviceName'
+ )
+ ";
+ Base_Model::runQuery($conn,$sql);
-}else{
- //New email address
- $user_id = include "randomId.php";
- $sql = "INSERT INTO user (userId,email) VALUE ('$user_id','$emailaddress')";
- Base_Model::runQuery($conn,$sql);
- //Define device name
- $randomNumber = rand(0,(count($deviceNames) - 1));
- $deviceName = $deviceNames[$randomNumber];
-}
-$sql = "INSERT INTO user_device (userId,email,signInCode,timestampOfSignInCode, deviceName)
- VALUE ('$user_id','$emailaddress','$signInCode',NOW(),'$deviceName')";
-Base_Model::runQuery($conn,$sql);
+ }else{
+ //Select a device name
+ $deviceNames = include "deviceNames.php";
+ //In order to maintain unique deviceNames
+ //remove device names which are already in use
+ $sql = "
+ SELECT deviceName
+ FROM user_device
+ WHERE userId='$user_id'
+ AND signedIn=1
+ ";
+
+ $devicesArray = Base_Model::queryFetchAssoc($conn, $sql);
+ $used_deviceNames = $devicesArray['deviceName'] != null ? $devicesArray['deviceName'] : [];
+
+ $deviceNames = array_values(array_diff($deviceNames,$used_deviceNames));
+ if (count($deviceNames) < 1){
+ //Ran out of device names
+ $deviceName = include 'randomId.php';
+ }else{
+ //Pick from what is left
+ $randomNumber = rand(0,(count($deviceNames) - 1));
+ $deviceName = $deviceNames[$randomNumber];
+ }
+
+ //Insert the device with the code so a user with the right code can sign in
+ $sql = "INSERT INTO user_device (userId,email,signInCode,timestampOfSignInCode, deviceName)
+ VALUE ('$user_id','$emailaddress','$signInCode',NOW(),'$deviceName')";
+ Base_Model::runQuery($conn,$sql);
+ }
+
-// Generate and modify email content
-$htmlContent = file_get_contents("signInEmail.html");
-$htmlContent = str_replace(array("signInCode"), array($signInCode), $htmlContent);
+ // Generate and modify email content
+ $htmlContent = file_get_contents("signInEmail.html");
+ $htmlContent = str_replace(array("signInCode"), array($signInCode), $htmlContent);
-$from = 'noreply@doenet.org';
-$fromName = 'Doenet Accounts';
-$subject = 'Sign-In Request';
+ $from = 'noreply@doenet.org';
+ $fromName = 'Doenet Accounts';
+ $subject = 'Sign-In Request';
-// Set content-type header for sending HTML email
-$headers = "MIME-Version: 1.0" . "\r\n";
-$headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
-$headers .= 'From: '.$fromName.'<'.$from.'>' . "\r\n";
+ // Set content-type header for sending HTML email
+ $headers = "MIME-Version: 1.0" . "\r\n";
+ $headers .= "Content-type:text/html;charset=UTF-8" . "\r\n";
+ $headers .= 'From: '.$fromName.'<'.$from.'>' . "\r\n";
-//SEND EMAIL WITH CODE HERE
-$mailSuccess = mail($emailaddress,$subject,$htmlContent, $headers);
+ //SEND EMAIL WITH CODE HERE
+ $mailSuccess = mail($emailaddress,$subject,$htmlContent, $headers);
-if (!$mailSuccess && $mode != 'development'){
- throw new Exception("Sending Email Failed.");
-}
+ if (!$mailSuccess && $mode != 'development'){
+ throw new Exception("Sending Email Failed.");
+ }
$response_arr = [
'success' => true,
diff --git a/src/Tools/_framework/Paths/SignInCode.jsx b/src/Tools/_framework/Paths/SignInCode.jsx
index 39218e2ec4..a0c5828e54 100644
--- a/src/Tools/_framework/Paths/SignInCode.jsx
+++ b/src/Tools/_framework/Paths/SignInCode.jsx
@@ -19,7 +19,7 @@ import {
} from "@chakra-ui/react";
import axios from "axios";
import React, { useState } from "react";
-import { redirect, useLoaderData } from "react-router";
+import { redirect } from "react-router";
import { useFetcher } from "react-router-dom";
export async function action({ request }) {
@@ -33,8 +33,9 @@ export async function action({ request }) {
try {
if (formObj._action == "send new code") {
let { data } = await axios.get("/api/sendSignInEmail.php", {
- params: { emailaddress: emailAddress },
+ params: { emailaddress: emailAddress, deviceName },
});
+
return {
success: true,
_action: formObj._action,
@@ -98,7 +99,7 @@ export function SignInCode() {
if (codeError !== fetcher.data.message) {
setCodeError(fetcher.data.message);
setIsDisabled(false);
- if (fetcher.data.message == "Code expired") {
+ if (fetcher.data.message == "Code expired.") {
setIsExpired(true);
}
}
From 8ee72847cfdf6277615a6a0401422d1596409648 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Mon, 23 Oct 2023 21:51:05 -0500
Subject: [PATCH 64/83] Base_Model conversion
---
public/api/checkCredentials.php | 15 ++++++---------
public/api/jwt.php | 8 +++-----
public/api/saveUsersName.php | 3 +--
3 files changed, 10 insertions(+), 16 deletions(-)
diff --git a/public/api/checkCredentials.php b/public/api/checkCredentials.php
index 7c5b3665a9..15213e03ad 100644
--- a/public/api/checkCredentials.php
+++ b/public/api/checkCredentials.php
@@ -24,8 +24,7 @@
LIMIT 1
";
- $result = $conn->query($sql);
- $row = $result->fetch_assoc();
+ $row = Base_Model::runQuery($conn,$sql)->fetch_assoc();
//Assume it already exists
$existed = true;
@@ -47,8 +46,7 @@
ORDER BY timestampOfSignInCode DESC
LIMIT 1
";
- $result = $conn->query($sql);
- $row = $result->fetch_assoc();
+ $row = Base_Model::runQuery($conn,$sql)->fetch_assoc();
if ($row["nineCode"] != $nineCode){
throw new Exception("Invalid Code");
@@ -59,15 +57,14 @@
$sql = "UPDATE user_device
SET signedIn='1'
WHERE email='$emailaddress' AND deviceName='$deviceName'";
- $result = $conn->query($sql);
+ Base_Model::runQuery($conn,$sql);
//Test if it's a new account
$sql = "SELECT firstName,lastName, screenName
FROM user
WHERE email='$emailaddress'
";
- $result = $conn->query($sql);
- $row = $result->fetch_assoc();
+ $row = Base_Model::runQuery($conn,$sql)->fetch_assoc();
if ($row["firstName"] != "" && $row["lastName"] != ""){
$hasFullName = true;
@@ -90,7 +87,7 @@
$profile_pic = $profile_pics[$randomNumber];
// Store screen name and profile picture
$sql = "UPDATE user SET screenName='$screen_name',profilePicture='$profile_pic' WHERE email='$emailaddress' ";
- $result = $conn->query($sql);
+ Base_Model::runQuery($conn,$sql);
}
$sql = "SELECT c.courseId
@@ -98,7 +95,7 @@
LEFT JOIN user AS u
ON u.userId = c.portfolioCourseForUserId
WHERE u.email = '$emailaddress'";
- $result = $conn->query($sql);
+ $result = Base_Model::runQuery($conn,$sql);
$row = $result->fetch_assoc();
$portfolioCourseId = "not_created";
if ($result->num_rows > 0) {
diff --git a/public/api/jwt.php b/public/api/jwt.php
index 9e3eb4f169..9f7e63f1e0 100644
--- a/public/api/jwt.php
+++ b/public/api/jwt.php
@@ -24,8 +24,7 @@
LIMIT 1
";
- $result = $conn->query($sql);
- $row = $result->fetch_assoc();
+ $row = Base_Model::runQuery($conn,$sql)->fetch_assoc();
//Check if it took longer than 10 minutes to enter the code
if ($row['minutes'] > 10) {
@@ -36,8 +35,7 @@
WHERE email='$emailaddress' AND deviceName='$deviceName'
ORDER BY timestampOfSignInCode DESC
LIMIT 1";
- $result = $conn->query($sql);
- $row = $result->fetch_assoc();
+ $row = Base_Model::runQuery($conn,$sql)->fetch_assoc();
$userId = $row['userId'];
if ($row['nineCode'] != $nineCode) {
throw new Exception("Invalid Code.");
@@ -61,7 +59,7 @@
$sql = "UPDATE user_device
SET signedIn = '1'
WHERE userId='$userId' AND deviceName='$deviceName'";
- $result = $conn->query($sql);
+ Base_Model::runQuery($conn,$sql);
$value = $jwt;
diff --git a/public/api/saveUsersName.php b/public/api/saveUsersName.php
index 0708c6d203..0f1fbbabe8 100644
--- a/public/api/saveUsersName.php
+++ b/public/api/saveUsersName.php
@@ -21,11 +21,10 @@
lastName='$lastName'
WHERE email='$email'
";
- $conn->query($sql);
+ Base_Model::runQuery($conn,$sql);
$response_arr = array(
'success' => true,
- 'message' => $message,
);
http_response_code(200);
From a8047f68d3d539551512ffc88beaa3f650ec2615 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Tue, 24 Oct 2023 14:48:03 -0500
Subject: [PATCH 65/83] Fixed sign out
---
public/api/signOut.php | 80 ++++++++++--------
src/Tools/_framework/Paths/SignOut.jsx | 111 +++++++++++++++++++++++++
src/_utils/applicationUtils.js | 2 +-
src/index.jsx | 18 ++++
4 files changed, 175 insertions(+), 36 deletions(-)
create mode 100644 src/Tools/_framework/Paths/SignOut.jsx
diff --git a/public/api/signOut.php b/public/api/signOut.php
index 7b9221be39..ffe7f0b1bf 100644
--- a/public/api/signOut.php
+++ b/public/api/signOut.php
@@ -1,45 +1,55 @@
query($sql); //TODO: upgrade the script response
-
-// set response code - 200 OK
-http_response_code(200);
-
-$path = '/';
-// $domain = $ini_array['dbhost'];
-$domain = $_SERVER["SERVER_NAME"];
-if ($domain == 'apache'){$domain = 'localhost';}
+var_dump($cookies);
-$isSecure = true;
-if ($domain=="localhost"){
- $isSecure = false;
-}
-$isHttpOnly = true;
-$expirationTime = -3600;
-
-setcookie("JWT", "", $expirationTime, $path, $domain, $isSecure, $isHttpOnly);
-setcookie("JWT_JS", "", $expirationTime, $path, $domain, $isSecure, false);
-setcookie("EJWT", "", $expirationTime, $path, $domain, $isSecure, $isHttpOnly);
-setcookie("EJWT_JS", "", $expirationTime, $path, $domain, $isSecure, false);
-// setcookie("JWT", "", array("expires"=>$expirationTime, "path"=>$path, "domain"=>$domain, "secure"=>$isSecure, "httponly"=>$isHttpOnly, "samesite"=>"strict"));
-// setcookie("JWT_JS", "", array("expires"=>$expirationTime, "path"=>$path, "domain"=>$domain, "secure"=>$isSecure, "httponly"=>false, "samesite"=>"strict"));
-// setcookie("TrackingConsent", "", array("expires"=>$expirationTime, "path"=>$path, "domain"=>$domain, "secure"=>$isSecure, "httponly"=>false, "samesite"=>"strict"));
-// make it json format
-// echo json_encode($response_arr);
-
-$conn->close();
+?>
diff --git a/src/Tools/_framework/Paths/SignOut.jsx b/src/Tools/_framework/Paths/SignOut.jsx
new file mode 100644
index 0000000000..7d29927443
--- /dev/null
+++ b/src/Tools/_framework/Paths/SignOut.jsx
@@ -0,0 +1,111 @@
+import {
+ AbsoluteCenter,
+ Box,
+ Button,
+ Card,
+ CardBody,
+ CardFooter,
+ Flex,
+ Heading,
+ Image,
+ ListItem,
+ Stack,
+ Text,
+ UnorderedList,
+} from "@chakra-ui/react";
+import React from "react";
+import { useLoaderData, useNavigate } from "react-router";
+import {
+ checkIfUserClearedOut,
+ clearUsersInformationFromTheBrowser,
+} from "../../../_utils/applicationUtils";
+
+export async function loader() {
+ await clearUsersInformationFromTheBrowser();
+ const isSignedOutObj = await checkIfUserClearedOut();
+ return { isSignedOutObj };
+}
+
+//TODO: inform if not signed out
+export function SignOut() {
+ const { isSignedOutObj } = useLoaderData();
+ const navigate = useNavigate();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ {isSignedOutObj.cookieRemoved &&
+ isSignedOutObj.userInformationIsCompletelyRemoved ? (
+ <>
+
+ You are Signed Out!
+
+
+
+ navigate("/")}
+ >
+ Home
+
+
+
+ >
+ ) : (
+ <>
+
+ You are NOT Signed Out!
+
+
+
+ Hit refresh to try again.
+
+
+
+ Errors
+
+ {isSignedOutObj.messageArray.map((msg, i) => {
+ return (
+
+ {msg}
+
+ );
+ })}
+
+ >
+ )}
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/_utils/applicationUtils.js b/src/_utils/applicationUtils.js
index a4eebc6818..722a9a69dd 100644
--- a/src/_utils/applicationUtils.js
+++ b/src/_utils/applicationUtils.js
@@ -3,7 +3,7 @@ import { clear as idb_clear, keys as idb_keys } from "idb-keyval";
export async function clearUsersInformationFromTheBrowser() {
localStorage.clear(); //Clear out the profile of the last exam taker
- await axios.get("/api/signOut.php");
+ await axios.get("/api/signOut.php", { withCredentials: true }); //Clear all cookies
await idb_clear();
return true;
}
diff --git a/src/index.jsx b/src/index.jsx
index ba45b22000..5f1efe82fc 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -93,6 +93,10 @@ import {
SignInName,
action as signInNameAction,
} from "./Tools/_framework/Paths/SignInName";
+import {
+ SignOut,
+ loader as signOutLoader,
+} from "./Tools/_framework/Paths/SignOut";
{
/* TESTING 123 */
@@ -374,6 +378,20 @@ const router = createBrowserRouter([
),
},
+ {
+ path: "signout",
+ loader: signOutLoader,
+ errorElement: (
+
+
+
+ ),
+ element: (
+
+
+
+ ),
+ },
{
path: "public",
loader: editorSupportPanelLoader,
From d48b05927b7c97a60023ff8311484f9c967be254 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Tue, 24 Oct 2023 15:16:33 -0500
Subject: [PATCH 66/83] fix backticks
---
public/api/signInEmail.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/api/signInEmail.html b/public/api/signInEmail.html
index c6e7eed83c..27c70874a7 100644
--- a/public/api/signInEmail.html
+++ b/public/api/signInEmail.html
@@ -140,7 +140,7 @@
Sign-in code:
- ```signInCode```signInCode
From 28152a3143daf3431c02592e8212bbe42b890c22 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Tue, 24 Oct 2023 23:07:30 -0500
Subject: [PATCH 67/83] tests for signin
---
cypress/e2e/AsStudent/signIn.cy.js | 88 +++--
package-lock.json | 372 ++++++++++++++++------
package.json | 2 +-
src/Tools/_framework/Paths/SignIn.jsx | 2 +
src/Tools/_framework/Paths/SignInCode.jsx | 30 +-
src/Tools/_framework/Paths/SignInName.jsx | 11 +-
src/Tools/_framework/Paths/SignOut.jsx | 1 +
src/Tools/_framework/Paths/SiteHeader.jsx | 8 +-
8 files changed, 377 insertions(+), 137 deletions(-)
diff --git a/cypress/e2e/AsStudent/signIn.cy.js b/cypress/e2e/AsStudent/signIn.cy.js
index 65f3f19852..cbc6ff9a24 100644
--- a/cypress/e2e/AsStudent/signIn.cy.js
+++ b/cypress/e2e/AsStudent/signIn.cy.js
@@ -1,24 +1,7 @@
describe("Student Sign-In Test", function () {
const userId = "cyuserId";
- // const studentUserId = "cyStudentUserId";
const courseId = "courseid1";
- // const doenetId = "activity1id";
- // const pageDoenetId = "_page1id";
- before(() => {
- cy.signin({ userId });
- cy.clearAllOfAUsersCoursesAndItems({ userId });
- // cy.clearAllOfAUsersCoursesAndItems({ userId: studentUserId });
- cy.createCourse({ userId, courseId });
- });
- beforeEach(() => {
- cy.signin({ userId });
- cy.clearIndexedDB();
- cy.clearAllOfAUsersActivities({ userId });
- // cy.clearAllOfAUsersActivities({ userId: studentUserId });
- // cy.createActivity({ courseId, doenetId, parentDoenetId:courseId, pageDoenetId });
- cy.visit(`/course?tool=people&courseId=${courseId}`);
- });
Cypress.on("uncaught:exception", (err, runnable) => {
// Returning false here prevents Cypress from failing the test
@@ -26,6 +9,12 @@ describe("Student Sign-In Test", function () {
});
it("Student can sign in after being added to a course", () => {
+ cy.createCourse({ userId, courseId });
+ cy.signin({ userId });
+ cy.clearIndexedDB();
+ cy.clearAllOfAUsersActivities({ userId });
+ cy.visit(`/course?tool=people&courseId=${courseId}`);
+
const emailAddress = "scoobydoo@doenet.org";
cy.get('[data-test="First"]').type("Scooby");
cy.get('[data-test="Last"]').type("Doo");
@@ -42,8 +31,17 @@ describe("Student Sign-In Test", function () {
`SELECT signInCode FROM user_device ORDER BY id DESC LIMIT 1`,
).then((result) => {
const code = result[0].signInCode;
- cy.get('[data-test="signinCodeInput"]').type(code);
- cy.get('[data-test="signInButton"]').click();
+ // cy.get('[data-test="signinCodeInput"]').type(code);
+ cy.get('[data-test="code-input-0"]').type(code.charAt(0));
+ cy.get('[data-test="code-input-1"]').type(code.charAt(1));
+ cy.get('[data-test="code-input-2"]').type(code.charAt(2));
+ cy.get('[data-test="code-input-3"]').type(code.charAt(3));
+ cy.get('[data-test="code-input-4"]').type(code.charAt(4));
+ cy.get('[data-test="code-input-5"]').type(code.charAt(5));
+ cy.get('[data-test="code-input-6"]').type(code.charAt(6));
+ cy.get('[data-test="code-input-7"]').type(code.charAt(7));
+ cy.get('[data-test="code-input-8"]').type(code.charAt(8));
+ cy.get('[data-test="submitCodeButton"]').click();
cy.get('[data-test="My Courses"]').click();
cy.get('[data-test="Course Label"]').should(
"have.text",
@@ -53,4 +51,56 @@ describe("Student Sign-In Test", function () {
cy.document().should("contain.text", "Welcome");
});
});
+
+ it("Signed out to in to out with all entry errors", () => {
+ const emailAddress = "scrapydoo@doenet.org";
+ const firstName = "Scrapy";
+ const lastName = "Doo";
+ //Delete entry so we will need to enter the name
+ cy.task(
+ "queryDb",
+ `DELETE FROM user WHERE email='${emailAddress}'`,
+ ).then(() => {
+ cy.visit(`/`);
+ cy.get('[data-test="Nav to signin"]').click();
+ cy.get('[data-test="email input"]').type(emailAddress);
+ cy.get('[data-test="sendEmailButton"]').click();
+ cy.wait(500); //Wait for it to be stored in db
+ cy.task(
+ "queryDb",
+ `SELECT signInCode FROM user_device ORDER BY id DESC LIMIT 1`,
+ ).then((result) => {
+ const code = result[0].signInCode;
+ //Try no code
+ cy.get('[data-test="submitCodeButton"]').click();
+ cy.get('[data-test="code-err"]').should('contain', "Please enter the nine digits sent to your email.");
+ //Try only one number
+ cy.get('[data-test="code-input-0"]').type(code.charAt(0));
+ cy.get('[data-test="submitCodeButton"]').click();
+ cy.get('[data-test="code-err"]').should('contain', "Please enter all nine digits.");
+
+ cy.get('[data-test="code-input-0"]').type(code.charAt(0));
+ cy.get('[data-test="code-input-1"]').type(code.charAt(1));
+ cy.get('[data-test="code-input-2"]').type(code.charAt(2));
+ cy.get('[data-test="code-input-3"]').type(code.charAt(3));
+ cy.get('[data-test="code-input-4"]').type(code.charAt(4));
+ cy.get('[data-test="code-input-5"]').type(code.charAt(5));
+ cy.get('[data-test="code-input-6"]').type(code.charAt(6));
+ cy.get('[data-test="code-input-7"]').type(code.charAt(7));
+ cy.get('[data-test="code-input-8"]').type(code.charAt(8));
+ cy.get('[data-test="submitCodeButton"]').click();
+ //Try no names
+ cy.get('[data-test="submitName"]').click();
+ cy.get('[data-test="firstNameError"]').should('contain', 'Please enter your first name.')
+ cy.get('[data-test="lastNameError"]').should('contain', 'Please enter your last name.')
+
+ cy.get('[data-test="firstNameInput"]').type(firstName);
+ cy.get('[data-test="lastNameInput"]').type(lastName);
+ cy.get('[data-test="submitName"]').click();
+ cy.get('[data-test="AvatarMenuButton"]').click();
+ cy.get('[data-test="AvatarMenuSignOut"]').click();
+ cy.get('[data-test="homepage button"]').click();
+ });
+ });
+ });
});
diff --git a/package-lock.json b/package-lock.json
index 3c3e504443..3f4f3a4c52 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -39,6 +39,7 @@
"crypto-js": "^3.3.0",
"cssesc": "^3.0.0",
"csv-parse": "^5.3.6",
+ "cypress": "^13.3.3",
"esm-seedrandom": "^3.0.5",
"framer-motion": "^10.12.10",
"handsontable": "^12.1.2",
@@ -109,7 +110,7 @@
},
"optionalDependencies": {
"@esbuild/linux-arm64": "^0.17.19",
- "cypress": "^12.12.0",
+ "cypress": "^13.3.3",
"cypress-file-upload": "^5.0.8",
"cypress-parallel": "^0.13.0",
"cypress-plugin-tab": "^1.0.5",
@@ -1973,8 +1974,9 @@
}
},
"node_modules/@cypress/request": {
- "version": "2.88.10",
- "license": "Apache-2.0",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz",
+ "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==",
"optional": true,
"dependencies": {
"aws-sign2": "~0.7.0",
@@ -1990,9 +1992,9 @@
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"performance-now": "^2.1.0",
- "qs": "~6.5.2",
+ "qs": "6.10.4",
"safe-buffer": "^5.1.2",
- "tough-cookie": "~2.5.0",
+ "tough-cookie": "^4.1.3",
"tunnel-agent": "^0.6.0",
"uuid": "^8.3.2"
},
@@ -2002,7 +2004,8 @@
},
"node_modules/@cypress/request/node_modules/form-data": {
"version": "2.3.3",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"optional": true,
"dependencies": {
"asynckit": "^0.4.0",
@@ -2014,11 +2017,18 @@
}
},
"node_modules/@cypress/request/node_modules/qs": {
- "version": "6.5.3",
- "license": "BSD-3-Clause",
+ "version": "6.10.4",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz",
+ "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==",
"optional": true,
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
"engines": {
"node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/@cypress/xvfb": {
@@ -3443,8 +3453,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "18.11.4",
- "license": "MIT"
+ "version": "18.18.6",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz",
+ "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w=="
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.1",
@@ -4213,7 +4224,8 @@
},
"node_modules/asn1": {
"version": "0.2.6",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+ "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"optional": true,
"dependencies": {
"safer-buffer": "~2.1.0"
@@ -4221,7 +4233,8 @@
},
"node_modules/assert-plus": {
"version": "1.0.0",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"optional": true,
"engines": {
"node": ">=0.8"
@@ -4291,15 +4304,17 @@
},
"node_modules/aws-sign2": {
"version": "0.7.0",
- "license": "Apache-2.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
"optional": true,
"engines": {
"node": "*"
}
},
"node_modules/aws4": {
- "version": "1.11.0",
- "license": "MIT",
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
+ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==",
"optional": true
},
"node_modules/axe-core": {
@@ -4734,7 +4749,8 @@
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
- "license": "BSD-3-Clause",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"optional": true,
"dependencies": {
"tweetnacl": "^0.14.3"
@@ -4942,7 +4958,7 @@
},
"node_modules/call-bind": {
"version": "1.0.2",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.1",
@@ -4997,7 +5013,8 @@
},
"node_modules/caseless": {
"version": "0.12.0",
- "license": "Apache-2.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
"optional": true
},
"node_modules/chai": {
@@ -5508,6 +5525,12 @@
"url": "https://opencollective.com/core-js"
}
},
+ "node_modules/core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
+ "optional": true
+ },
"node_modules/cors": {
"version": "2.8.5",
"license": "MIT",
@@ -5603,15 +5626,15 @@
"license": "MIT"
},
"node_modules/cypress": {
- "version": "12.12.0",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.12.0.tgz",
- "integrity": "sha512-UU5wFQ7SMVCR/hyKok/KmzG6fpZgBHHfrXcHzDmPHWrT+UUetxFzQgt7cxCszlwfozckzwkd22dxMwl/vNkWRw==",
+ "version": "13.3.3",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.3.tgz",
+ "integrity": "sha512-mbdkojHhKB1xbrj7CrKWHi22uFx9P9vQFiR0sYDZZoK99OMp9/ZYN55TO5pjbXmV7xvCJ4JwBoADXjOJK8aCJw==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
- "@cypress/request": "^2.88.10",
+ "@cypress/request": "^3.0.0",
"@cypress/xvfb": "^1.2.4",
- "@types/node": "^14.14.31",
+ "@types/node": "^18.17.5",
"@types/sinonjs__fake-timers": "8.1.1",
"@types/sizzle": "^2.3.2",
"arch": "^2.2.0",
@@ -5644,9 +5667,10 @@
"minimist": "^1.2.8",
"ospath": "^1.2.2",
"pretty-bytes": "^5.6.0",
+ "process": "^0.11.10",
"proxy-from-env": "1.0.0",
"request-progress": "^3.0.0",
- "semver": "^7.3.2",
+ "semver": "^7.5.3",
"supports-color": "^8.1.1",
"tmp": "~0.2.1",
"untildify": "^4.0.0",
@@ -5656,7 +5680,7 @@
"cypress": "bin/cypress"
},
"engines": {
- "node": "^14.0.0 || ^16.0.0 || >=18.0.0"
+ "node": "^16.0.0 || ^18.0.0 || >=20.0.0"
}
},
"node_modules/cypress-file-upload": {
@@ -6112,11 +6136,6 @@
"license": "MIT",
"optional": true
},
- "node_modules/cypress/node_modules/@types/node": {
- "version": "14.18.32",
- "license": "MIT",
- "optional": true
- },
"node_modules/cypress/node_modules/ansi-styles": {
"version": "4.3.0",
"license": "MIT",
@@ -6205,8 +6224,9 @@
}
},
"node_modules/cypress/node_modules/semver": {
- "version": "7.3.8",
- "license": "ISC",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"optional": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -6239,7 +6259,8 @@
},
"node_modules/dashdash": {
"version": "1.14.1",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
"optional": true,
"dependencies": {
"assert-plus": "^1.0.0"
@@ -6479,7 +6500,8 @@
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
"optional": true,
"dependencies": {
"jsbn": "~0.1.0",
@@ -7772,7 +7794,8 @@
},
"node_modules/extend": {
"version": "3.0.2",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"optional": true
},
"node_modules/extend-shallow": {
@@ -7865,10 +7888,11 @@
},
"node_modules/extsprintf": {
"version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
"engines": [
"node >=0.6.0"
],
- "license": "MIT",
"optional": true
},
"node_modules/fast-deep-equal": {
@@ -8059,7 +8083,8 @@
},
"node_modules/forever-agent": {
"version": "0.6.1",
- "license": "Apache-2.0",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
"optional": true,
"engines": {
"node": "*"
@@ -8232,7 +8257,7 @@
},
"node_modules/get-intrinsic": {
"version": "1.1.3",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.1",
@@ -8298,7 +8323,8 @@
},
"node_modules/getpass": {
"version": "0.1.7",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
"optional": true,
"dependencies": {
"assert-plus": "^1.0.0"
@@ -8461,7 +8487,7 @@
},
"node_modules/has-symbols": {
"version": "1.0.3",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -8627,7 +8653,8 @@
},
"node_modules/http-signature": {
"version": "1.3.6",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz",
+ "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==",
"optional": true,
"dependencies": {
"assert-plus": "^1.0.0",
@@ -9178,7 +9205,8 @@
},
"node_modules/is-typedarray": {
"version": "1.0.0",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
"optional": true
},
"node_modules/is-unicode-supported": {
@@ -9249,7 +9277,8 @@
},
"node_modules/isstream": {
"version": "0.1.2",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
"optional": true
},
"node_modules/istanbul-lib-coverage": {
@@ -9366,7 +9395,8 @@
},
"node_modules/jsbn": {
"version": "0.1.1",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
"optional": true
},
"node_modules/jsesc": {
@@ -9385,7 +9415,8 @@
},
"node_modules/json-schema": {
"version": "0.4.0",
- "license": "(AFL-2.1 OR BSD-3-Clause)",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"optional": true
},
"node_modules/json-schema-traverse": {
@@ -9407,7 +9438,8 @@
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
- "license": "ISC",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"optional": true
},
"node_modules/json5": {
@@ -9433,10 +9465,11 @@
},
"node_modules/jsprim": {
"version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
+ "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==",
"engines": [
"node >=0.6.0"
],
- "license": "MIT",
"optional": true,
"dependencies": {
"assert-plus": "1.0.0",
@@ -10613,7 +10646,7 @@
},
"node_modules/object-inspect": {
"version": "1.12.2",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -10955,7 +10988,8 @@
},
"node_modules/performance-now": {
"version": "2.1.0",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"optional": true
},
"node_modules/picocolors": {
@@ -11210,6 +11244,15 @@
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true
},
+ "node_modules/process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/progress": {
"version": "2.0.3",
"dev": true,
@@ -11238,7 +11281,8 @@
},
"node_modules/psl": {
"version": "1.9.0",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
"optional": true
},
"node_modules/pump": {
@@ -11294,6 +11338,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "optional": true
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"devOptional": true,
@@ -11872,7 +11922,7 @@
},
"node_modules/requires-port": {
"version": "1.0.0",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/resize-observer-polyfill": {
@@ -12322,7 +12372,7 @@
},
"node_modules/side-channel": {
"version": "1.0.4",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.0",
@@ -12720,8 +12770,9 @@
}
},
"node_modules/sshpk": {
- "version": "1.17.0",
- "license": "MIT",
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
+ "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
"optional": true,
"dependencies": {
"asn1": "~0.2.3",
@@ -13226,15 +13277,27 @@
}
},
"node_modules/tough-cookie": {
- "version": "2.5.0",
- "license": "BSD-3-Clause",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
+ "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"optional": true,
"dependencies": {
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
},
"engines": {
- "node": ">=0.8"
+ "node": ">=6"
+ }
+ },
+ "node_modules/tough-cookie/node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "optional": true,
+ "engines": {
+ "node": ">= 4.0.0"
}
},
"node_modules/tr46": {
@@ -13285,7 +13348,8 @@
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
- "license": "Apache-2.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"optional": true,
"dependencies": {
"safe-buffer": "^5.0.1"
@@ -13296,7 +13360,8 @@
},
"node_modules/tweetnacl": {
"version": "0.14.5",
- "license": "Unlicense",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"optional": true
},
"node_modules/type-check": {
@@ -13532,6 +13597,16 @@
"version": "0.1.0",
"license": "MIT"
},
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "optional": true,
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"node_modules/use": {
"version": "3.1.1",
"license": "MIT",
@@ -13600,7 +13675,8 @@
},
"node_modules/uuid": {
"version": "8.3.2",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"optional": true,
"bin": {
"uuid": "dist/bin/uuid"
@@ -13636,10 +13712,11 @@
},
"node_modules/verror": {
"version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"engines": [
"node >=0.6.0"
],
- "license": "MIT",
"optional": true,
"dependencies": {
"assert-plus": "^1.0.0",
@@ -13647,11 +13724,6 @@
"extsprintf": "^1.2.0"
}
},
- "node_modules/verror/node_modules/core-util-is": {
- "version": "1.0.2",
- "license": "MIT",
- "optional": true
- },
"node_modules/vite": {
"version": "4.2.1",
"dev": true,
@@ -15568,7 +15640,9 @@
"optional": true
},
"@cypress/request": {
- "version": "2.88.10",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz",
+ "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==",
"optional": true,
"requires": {
"aws-sign2": "~0.7.0",
@@ -15584,15 +15658,17 @@
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"performance-now": "^2.1.0",
- "qs": "~6.5.2",
+ "qs": "6.10.4",
"safe-buffer": "^5.1.2",
- "tough-cookie": "~2.5.0",
+ "tough-cookie": "^4.1.3",
"tunnel-agent": "^0.6.0",
"uuid": "^8.3.2"
},
"dependencies": {
"form-data": {
"version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"optional": true,
"requires": {
"asynckit": "^0.4.0",
@@ -15601,8 +15677,13 @@
}
},
"qs": {
- "version": "6.5.3",
- "optional": true
+ "version": "6.10.4",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz",
+ "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==",
+ "optional": true,
+ "requires": {
+ "side-channel": "^1.0.4"
+ }
}
}
},
@@ -16532,7 +16613,9 @@
"dev": true
},
"@types/node": {
- "version": "18.11.4"
+ "version": "18.18.6",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.6.tgz",
+ "integrity": "sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w=="
},
"@types/normalize-package-data": {
"version": "2.4.1"
@@ -17058,6 +17141,8 @@
},
"asn1": {
"version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+ "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"optional": true,
"requires": {
"safer-buffer": "~2.1.0"
@@ -17065,6 +17150,8 @@
},
"assert-plus": {
"version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"optional": true
},
"assertion-error": {
@@ -17101,10 +17188,14 @@
},
"aws-sign2": {
"version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
"optional": true
},
"aws4": {
- "version": "1.11.0",
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
+ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==",
"optional": true
},
"axe-core": {
@@ -17395,6 +17486,8 @@
},
"bcrypt-pbkdf": {
"version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"optional": true,
"requires": {
"tweetnacl": "^0.14.3"
@@ -17524,7 +17617,7 @@
},
"call-bind": {
"version": "1.0.2",
- "dev": true,
+ "devOptional": true,
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
@@ -17548,6 +17641,8 @@
},
"caseless": {
"version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
"optional": true
},
"chai": {
@@ -17880,6 +17975,12 @@
"version": "3.26.0",
"dev": true
},
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
+ "optional": true
+ },
"cors": {
"version": "2.8.5",
"requires": {
@@ -17947,14 +18048,14 @@
"version": "5.3.6"
},
"cypress": {
- "version": "12.12.0",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.12.0.tgz",
- "integrity": "sha512-UU5wFQ7SMVCR/hyKok/KmzG6fpZgBHHfrXcHzDmPHWrT+UUetxFzQgt7cxCszlwfozckzwkd22dxMwl/vNkWRw==",
+ "version": "13.3.3",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.3.tgz",
+ "integrity": "sha512-mbdkojHhKB1xbrj7CrKWHi22uFx9P9vQFiR0sYDZZoK99OMp9/ZYN55TO5pjbXmV7xvCJ4JwBoADXjOJK8aCJw==",
"optional": true,
"requires": {
- "@cypress/request": "^2.88.10",
+ "@cypress/request": "^3.0.0",
"@cypress/xvfb": "^1.2.4",
- "@types/node": "^14.14.31",
+ "@types/node": "^18.17.5",
"@types/sinonjs__fake-timers": "8.1.1",
"@types/sizzle": "^2.3.2",
"arch": "^2.2.0",
@@ -17987,19 +18088,16 @@
"minimist": "^1.2.8",
"ospath": "^1.2.2",
"pretty-bytes": "^5.6.0",
+ "process": "^0.11.10",
"proxy-from-env": "1.0.0",
"request-progress": "^3.0.0",
- "semver": "^7.3.2",
+ "semver": "^7.5.3",
"supports-color": "^8.1.1",
"tmp": "~0.2.1",
"untildify": "^4.0.0",
"yauzl": "^2.10.0"
},
"dependencies": {
- "@types/node": {
- "version": "14.18.32",
- "optional": true
- },
"ansi-styles": {
"version": "4.3.0",
"optional": true,
@@ -18048,7 +18146,9 @@
"optional": true
},
"semver": {
- "version": "7.3.8",
+ "version": "7.5.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+ "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
@@ -18405,6 +18505,8 @@
},
"dashdash": {
"version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
"optional": true,
"requires": {
"assert-plus": "^1.0.0"
@@ -18550,6 +18652,8 @@
},
"ecc-jsbn": {
"version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
"optional": true,
"requires": {
"jsbn": "~0.1.0",
@@ -19337,6 +19441,8 @@
},
"extend": {
"version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"optional": true
},
"extend-shallow": {
@@ -19397,6 +19503,8 @@
},
"extsprintf": {
"version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
"optional": true
},
"fast-deep-equal": {
@@ -19519,6 +19627,8 @@
},
"forever-agent": {
"version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
"optional": true
},
"form-data": {
@@ -19625,7 +19735,7 @@
},
"get-intrinsic": {
"version": "1.1.3",
- "dev": true,
+ "devOptional": true,
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
@@ -19664,6 +19774,8 @@
},
"getpass": {
"version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
"optional": true,
"requires": {
"assert-plus": "^1.0.0"
@@ -19768,7 +19880,7 @@
},
"has-symbols": {
"version": "1.0.3",
- "dev": true
+ "devOptional": true
},
"has-tostringtag": {
"version": "1.0.0",
@@ -19880,6 +19992,8 @@
},
"http-signature": {
"version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz",
+ "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==",
"optional": true,
"requires": {
"assert-plus": "^1.0.0",
@@ -20191,6 +20305,8 @@
},
"is-typedarray": {
"version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
"optional": true
},
"is-unicode-supported": {
@@ -20229,6 +20345,8 @@
},
"isstream": {
"version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
"optional": true
},
"istanbul-lib-coverage": {
@@ -20309,6 +20427,8 @@
},
"jsbn": {
"version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
"optional": true
},
"jsesc": {
@@ -20319,6 +20439,8 @@
},
"json-schema": {
"version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"optional": true
},
"json-schema-traverse": {
@@ -20334,6 +20456,8 @@
},
"json-stringify-safe": {
"version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"optional": true
},
"json5": {
@@ -20349,6 +20473,8 @@
},
"jsprim": {
"version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
+ "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==",
"optional": true,
"requires": {
"assert-plus": "1.0.0",
@@ -21169,7 +21295,7 @@
},
"object-inspect": {
"version": "1.12.2",
- "dev": true
+ "devOptional": true
},
"object-keys": {
"version": "1.1.1",
@@ -21372,6 +21498,8 @@
},
"performance-now": {
"version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"optional": true
},
"picocolors": {
@@ -21529,6 +21657,12 @@
}
}
},
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "optional": true
+ },
"progress": {
"version": "2.0.3",
"dev": true
@@ -21552,6 +21686,8 @@
},
"psl": {
"version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
"optional": true
},
"pump": {
@@ -21591,6 +21727,12 @@
"side-channel": "^1.0.4"
}
},
+ "querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "optional": true
+ },
"queue-microtask": {
"version": "1.2.3",
"devOptional": true
@@ -21942,7 +22084,7 @@
},
"requires-port": {
"version": "1.0.0",
- "dev": true
+ "devOptional": true
},
"resize-observer-polyfill": {
"version": "1.5.1"
@@ -22231,7 +22373,7 @@
},
"side-channel": {
"version": "1.0.4",
- "dev": true,
+ "devOptional": true,
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
@@ -22506,7 +22648,9 @@
"dev": true
},
"sshpk": {
- "version": "1.17.0",
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
+ "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
"optional": true,
"requires": {
"asn1": "~0.2.3",
@@ -22849,11 +22993,23 @@
"dev": true
},
"tough-cookie": {
- "version": "2.5.0",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
+ "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"optional": true,
"requires": {
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "dependencies": {
+ "universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "optional": true
+ }
}
},
"tr46": {
@@ -22893,6 +23049,8 @@
},
"tunnel-agent": {
"version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"optional": true,
"requires": {
"safe-buffer": "^5.0.1"
@@ -22900,6 +23058,8 @@
},
"tweetnacl": {
"version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"optional": true
},
"type-check": {
@@ -23037,6 +23197,16 @@
"urix": {
"version": "0.1.0"
},
+ "url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "optional": true,
+ "requires": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"use": {
"version": "3.1.1"
},
@@ -23069,6 +23239,8 @@
},
"uuid": {
"version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"optional": true
},
"v8-to-istanbul": {
@@ -23092,17 +23264,13 @@
},
"verror": {
"version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"optional": true,
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
- },
- "dependencies": {
- "core-util-is": {
- "version": "1.0.2",
- "optional": true
- }
}
},
"vite": {
diff --git a/package.json b/package.json
index 1bd4fd65d0..2ed0964a8b 100644
--- a/package.json
+++ b/package.json
@@ -127,7 +127,7 @@
},
"optionalDependencies": {
"@esbuild/linux-arm64": "^0.17.19",
- "cypress": "^12.12.0",
+ "cypress": "^13.3.3",
"cypress-file-upload": "^5.0.8",
"cypress-parallel": "^0.13.0",
"cypress-plugin-tab": "^1.0.5",
diff --git a/src/Tools/_framework/Paths/SignIn.jsx b/src/Tools/_framework/Paths/SignIn.jsx
index 5d897ef721..aa77e8d65d 100644
--- a/src/Tools/_framework/Paths/SignIn.jsx
+++ b/src/Tools/_framework/Paths/SignIn.jsx
@@ -97,6 +97,7 @@ export function SignIn() {
size="md"
type="email"
value={emailAddress}
+ data-test="email input"
onChange={(e) => {
let nextValue = e.target.value;
//Clear error if email is now good
@@ -124,6 +125,7 @@ export function SignIn() {
isDisabled={isDisabled}
rightIcon={isDisabled ? : undefined}
colorScheme="blue"
+ data-test="sendEmailButton"
onClick={() => {
if (emailAddress == "") {
setEmailError("Please enter your email address");
diff --git a/src/Tools/_framework/Paths/SignInCode.jsx b/src/Tools/_framework/Paths/SignInCode.jsx
index a0c5828e54..7c1d4602dd 100644
--- a/src/Tools/_framework/Paths/SignInCode.jsx
+++ b/src/Tools/_framework/Paths/SignInCode.jsx
@@ -140,19 +140,25 @@ export function SignInCode() {
Sign-in code (9 digit code):
- setCode(code)}>
-
-
-
-
-
-
-
-
-
+ setCode(code)}
+ >
+
+
+
+
+
+
+
+
+
- {codeError}
+
+ {codeError}
+
@@ -162,6 +168,7 @@ export function SignInCode() {
{
setIsExpired(false);
setCodeError(null);
@@ -180,6 +187,7 @@ export function SignInCode() {
: undefined}
onClick={() => {
diff --git a/src/Tools/_framework/Paths/SignInName.jsx b/src/Tools/_framework/Paths/SignInName.jsx
index f406e337b1..a302ab3c9b 100644
--- a/src/Tools/_framework/Paths/SignInName.jsx
+++ b/src/Tools/_framework/Paths/SignInName.jsx
@@ -99,6 +99,7 @@ export function SignInName() {
First Name:
{
if (e.target.value != "") {
setFirstNameError(null);
@@ -106,11 +107,14 @@ export function SignInName() {
setFirstName(e.target.value);
}}
/>
- {firstNameError}
+
+ {firstNameError}
+
Last Name:
{
if (e.target.value != "") {
setLastNameError(null);
@@ -118,7 +122,9 @@ export function SignInName() {
setLastName(e.target.value);
}}
/>
- {lastNameError}
+
+ {lastNameError}
+
@@ -129,6 +135,7 @@ export function SignInName() {
colorScheme="blue"
isDisabled={isDisabled}
rightIcon={isDisabled ? : undefined}
+ data-test="submitName"
onClick={() => {
if (firstName == "") {
setFirstNameError("Please enter your first name.");
diff --git a/src/Tools/_framework/Paths/SignOut.jsx b/src/Tools/_framework/Paths/SignOut.jsx
index 7d29927443..e2d6c5163a 100644
--- a/src/Tools/_framework/Paths/SignOut.jsx
+++ b/src/Tools/_framework/Paths/SignOut.jsx
@@ -72,6 +72,7 @@ export function SignOut() {
navigate("/")}
+ data-test="homepage button"
>
Home
diff --git a/src/Tools/_framework/Paths/SiteHeader.jsx b/src/Tools/_framework/Paths/SiteHeader.jsx
index e5764f2fc9..f2788fb318 100644
--- a/src/Tools/_framework/Paths/SiteHeader.jsx
+++ b/src/Tools/_framework/Paths/SiteHeader.jsx
@@ -189,7 +189,7 @@ export function SiteHeader(props) {
{signedIn ? (
*/}
-
+
Sign Out
From baa2f196b9c01b5480b3838ea2e76d4783ca498b Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Tue, 24 Oct 2023 23:11:55 -0500
Subject: [PATCH 68/83] cleaned tool root
---
src/Tools/_framework/NewToolRoot.jsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/Tools/_framework/NewToolRoot.jsx b/src/Tools/_framework/NewToolRoot.jsx
index 165832fc1e..47fb1ae45f 100644
--- a/src/Tools/_framework/NewToolRoot.jsx
+++ b/src/Tools/_framework/NewToolRoot.jsx
@@ -107,7 +107,6 @@ export default function ToolRoot() {
import("./ToolPanels/PublicActivityViewer"),
),
CourseCards: lazy(() => import("./ToolPanels/CourseCards")),
- SignOut: lazy(() => import("./ToolPanels/SignOut")),
NavigationPanel: lazy(() => import("./ToolPanels/NavigationPanel")),
Dashboard: lazy(() => import("./ToolPanels/Dashboard")),
Gradebook: lazy(() => import("./ToolPanels/Gradebook")),
From 7589a9b98a9a4eb94fb21247a797fad276ab82e6 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Wed, 25 Oct 2023 09:42:19 -0500
Subject: [PATCH 69/83] Removed commented code
---
src/index.jsx | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/src/index.jsx b/src/index.jsx
index 5f1efe82fc..e04e00c117 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -493,16 +493,6 @@ const router = createBrowserRouter([
),
},
- // {
- // path: "/api/",
- // element: Loading...
,
- // errorElement: (
- //
- //
- //
- // ),
- // },
-
{
path: "*",
element: (
From 834f893bc7eda4f95d07bde4fcd6c3b97bc3b60f Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Wed, 25 Oct 2023 14:47:30 -0500
Subject: [PATCH 70/83] Chakra darkmode signout bug fixed
---
src/_utils/applicationUtils.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/_utils/applicationUtils.js b/src/_utils/applicationUtils.js
index 722a9a69dd..865d4a41b9 100644
--- a/src/_utils/applicationUtils.js
+++ b/src/_utils/applicationUtils.js
@@ -20,6 +20,11 @@ export async function checkIfUserClearedOut() {
//Check for local storage
//TODO: find something is stored in localStorage and test if this clears it
let localStorageRemoved = localStorage.length == 0;
+ //Chakra UI will put darkmode back so check that
+ if (localStorage.length === 1 && localStorage.key(0) === 'chakra-ui-color-mode') {
+ localStorageRemoved = true;
+ }
+
if (!localStorageRemoved) {
messageArray.push("local storage not removed");
}
From 1b54f33557673986897d85eec734f5decb46fbd9 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Wed, 25 Oct 2023 15:23:23 -0500
Subject: [PATCH 71/83] Fixed measurement of signed out
---
public/api/getQuickCheckSignedIn.php | 14 +++++++++-----
public/api/signOut.php | 1 -
src/_utils/applicationUtils.js | 10 ++++------
3 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/public/api/getQuickCheckSignedIn.php b/public/api/getQuickCheckSignedIn.php
index 56afe122f0..3fac4436e1 100644
--- a/public/api/getQuickCheckSignedIn.php
+++ b/public/api/getQuickCheckSignedIn.php
@@ -5,14 +5,18 @@
header('Access-Control-Allow-Credentials: true');
header('Content-Type: application/json');
-//ONLY TESTING IF THE SECURE SIGNED IN (JWT) COOKIE EXISTS
-$signedIn = false;
-
+$secureCookieExists = false;
if ($_COOKIE["JWT"] != NULL){
- $signedIn = true;
+ $secureCookieExists = true;
+}
+$unsecureCookieExists = false;
+if ($_COOKIE["JWT_JS"] != NULL){
+ $unsecureCookieExists = true;
}
-$response_arr = ['signedIn' => $signedIn];
+$response_arr = ['secureCookieExists' => $secureCookieExists,
+'unsecureCookieExists' => $unsecureCookieExists,
+];
// set response code - 200 OK
http_response_code(200);
diff --git a/public/api/signOut.php b/public/api/signOut.php
index ffe7f0b1bf..687314ea69 100644
--- a/public/api/signOut.php
+++ b/public/api/signOut.php
@@ -17,7 +17,6 @@
$parts = explode('=', $cookie);
$name = trim($parts[0]);
// Set the cookie to expire one hour ago
- // $success = setcookie($name, '', time()-3600);
$success = setcookie($name, '', time()-3600, '/', $domain);
//Stop the script if deleting a cookie fails
if (!$success){
diff --git a/src/_utils/applicationUtils.js b/src/_utils/applicationUtils.js
index 865d4a41b9..46939c7565 100644
--- a/src/_utils/applicationUtils.js
+++ b/src/_utils/applicationUtils.js
@@ -32,15 +32,13 @@ export async function checkIfUserClearedOut() {
//Check for cookie
//Ask the server without hitting the database
const { data } = await axios.get("/api/getQuickCheckSignedIn.php");
- const secureCookieRemoved = !data?.signedIn;
- const vanillaCookies = document.cookie.split(";");
- const vanillaCookieRemoved =
- vanillaCookies.length === 1 && vanillaCookies[0] === "";
+ const secureCookieRemoved = !data?.secureCookieExists;
+ const unsecureCookieRemoved = !data?.unsecureCookieExists;
- let cookieRemoved = vanillaCookieRemoved && secureCookieRemoved;
+ let cookieRemoved = unsecureCookieRemoved && secureCookieRemoved;
- if (!vanillaCookieRemoved) {
+ if (!unsecureCookieRemoved) {
messageArray.push("cookie not removed");
}
From bc2f8e1a71571808c268b9f02efca128f9772803 Mon Sep 17 00:00:00 2001
From: Kevin Charles
Date: Wed, 25 Oct 2023 22:02:14 -0500
Subject: [PATCH 72/83] Make layer selector a controlled component
---
src/Tools/_framework/Paths/PortfolioActivity.jsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Tools/_framework/Paths/PortfolioActivity.jsx b/src/Tools/_framework/Paths/PortfolioActivity.jsx
index 8d98b57c4f..6671239b75 100644
--- a/src/Tools/_framework/Paths/PortfolioActivity.jsx
+++ b/src/Tools/_framework/Paths/PortfolioActivity.jsx
@@ -1744,6 +1744,7 @@ export function PortfolioActivity() {
) : (