Fix user authentication flow (#4)
* display site name * fix user auth flow
This commit is contained in:
parent
37a74aca5c
commit
3f48992c0c
File diff suppressed because it is too large
Load Diff
|
@ -44,10 +44,14 @@
|
|||
"@storybook/react-vite": "^7.1.0",
|
||||
"@storybook/testing-library": "^0.2.0",
|
||||
"@storybook/types": "^7.1.0",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/node": "^20.4.2",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"jsdom": "^22.1.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"storybook": "^7.1.0",
|
||||
"storybook-builder-vite": "^0.1.23"
|
||||
"storybook-builder-vite": "^0.1.23",
|
||||
"vitest": "^0.34.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { act, render } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { describe, test, vi } from "vitest";
|
||||
import { App } from "./App";
|
||||
import { config } from "./config";
|
||||
|
||||
vi.mock("./pages/MetricDetailsPage", () => ({
|
||||
MetricDetailsPage: () => <div>MetricDetailsPage</div>,
|
||||
}));
|
||||
|
||||
describe("App", () => {
|
||||
test("Display metrics after the auth process", async () => {
|
||||
const { queryByText } = render(<App />);
|
||||
expect(queryByText("MetricDetailsPage")).toBeNull();
|
||||
const mockEvent = new MessageEvent("message", {
|
||||
data: "token",
|
||||
origin: config.baseUrl,
|
||||
});
|
||||
await act(() => window.dispatchEvent(mockEvent));
|
||||
expect(queryByText("MetricDetailsPage")).toBeDefined();
|
||||
});
|
||||
test("Display metrics if user is already authenticated", async () => {
|
||||
localStorage.setItem(config.localstorageKeys.token, "token");
|
||||
const { queryByText } = render(<App />);
|
||||
expect(queryByText("MetricDetailsPage")).toBeDefined();
|
||||
});
|
||||
});
|
13
src/App.tsx
13
src/App.tsx
|
@ -1,14 +1,17 @@
|
|||
import { ApolloProvider } from "@apollo/client";
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { apolloClient } from "./apolloClient";
|
||||
import { AuthIFrame } from "./components/AuthIFrame";
|
||||
import { MetricDetailsPage } from "./pages";
|
||||
import { AuthIFrame } from "./components";
|
||||
import { MetricDetailsPage } from "./pages/MetricDetailsPage";
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>();
|
||||
return (
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<AuthIFrame />
|
||||
<MetricDetailsPage />
|
||||
<AuthIFrame
|
||||
onChange={(value) => setIsAuthenticated(value.isAuthenticated)}
|
||||
/>
|
||||
{isAuthenticated ? <MetricDetailsPage /> : null}
|
||||
</ApolloProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { act, render } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { AuthIFrame } from "..";
|
||||
import { config } from "../../config";
|
||||
|
||||
describe("AuthIFrame", () => {
|
||||
let mockLocalStorage;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLocalStorage = {
|
||||
getItem: vi.fn(),
|
||||
setItem: vi.fn(),
|
||||
};
|
||||
Object.defineProperty(window, "localStorage", { value: mockLocalStorage });
|
||||
});
|
||||
|
||||
test("should set isAuthenticated to true when message received", async () => {
|
||||
const onChange = vi.fn();
|
||||
|
||||
render(<AuthIFrame onChange={onChange} />);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({ isAuthenticated: false });
|
||||
|
||||
const mockEvent = new MessageEvent("message", {
|
||||
data: "token",
|
||||
origin: config.baseUrl,
|
||||
});
|
||||
|
||||
await act(() => window.dispatchEvent(mockEvent));
|
||||
expect(onChange).toHaveBeenCalledWith({ isAuthenticated: true });
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
||||
config.localstorageKeys.token,
|
||||
"token"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,5 +1,9 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { config } from "../config";
|
||||
import { config } from "../../config";
|
||||
|
||||
interface AuthIFrameProps {
|
||||
onChange(value: { isAuthenticated: boolean }): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is based on Rodney Urquhart's example https://github.com/RodneyU215
|
||||
|
@ -11,13 +15,14 @@ import { config } from "../config";
|
|||
*
|
||||
* This component listens to that event and save the token in local storage
|
||||
*/
|
||||
export const AuthIFrame: React.FC = () => {
|
||||
export const AuthIFrame: React.FC<AuthIFrameProps> = (props) => {
|
||||
const [url, setUrl] = useState<string>();
|
||||
const [done, setDone] = useState<boolean>(false);
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.origin.startsWith(config.baseUrl)) {
|
||||
localStorage.setItem(config.localstorageKeys.token, event.data);
|
||||
props.onChange({ isAuthenticated: true });
|
||||
console.info(
|
||||
`new token received from ${config.authIFrameUrl}`,
|
||||
event.data
|
||||
|
@ -32,6 +37,11 @@ export const AuthIFrame: React.FC = () => {
|
|||
|
||||
useEffect(() => {
|
||||
window.addEventListener("message", handleMessage);
|
||||
props.onChange({
|
||||
isAuthenticated: Boolean(
|
||||
localStorage.getItem(config.localstorageKeys.token)
|
||||
),
|
||||
});
|
||||
setUrl(`${config.authIFrameUrl}`);
|
||||
return () => {
|
||||
window.removeEventListener("message", handleMessage);
|
|
@ -0,0 +1 @@
|
|||
export * from "./AuthIFrame";
|
|
@ -2,3 +2,4 @@ export * from "./Button";
|
|||
export * from "./Chart";
|
||||
export * from "./Dropdown";
|
||||
export * from "./Icon";
|
||||
export * from "./AuthIFrame";
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Dropdown } from "../components";
|
||||
import { Chart } from "../containers/Chart";
|
||||
import { MetricType } from "../generated/graphql";
|
||||
import { MetricType, MetricTimeframe } from "../generated/graphql";
|
||||
|
||||
export const MetricDetailsPage: React.FC = () => {
|
||||
const [path, setPath] = useState<string>();
|
||||
const [name, setName] = useState<string>();
|
||||
const [timeframe, setTimeframe] = useState<string>("Last30days");
|
||||
const [timeframe, setTimeframe] = useState<MetricTimeframe>(
|
||||
MetricTimeframe.Last30days
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeCurrentPage = webflow.subscribe("currentpage", (page) => {
|
||||
|
@ -38,14 +40,14 @@ export const MetricDetailsPage: React.FC = () => {
|
|||
<Dropdown
|
||||
selected="Last30days"
|
||||
options={[
|
||||
{ label: "Last 7 days", value: "Last7days" },
|
||||
{ label: "Last 30 days", value: "Last30days" },
|
||||
{ label: "Last 3 Months", value: "Last3Months" },
|
||||
{ label: "Last 6 Months", value: "Last6Months" },
|
||||
{ label: "Last Year", value: "LastYear" },
|
||||
{ label: "Last 7 days", value: MetricTimeframe.Last7days },
|
||||
{ label: "Last 30 days", value: MetricTimeframe.Last30days },
|
||||
{ label: "Last 3 Months", value: MetricTimeframe.Last3Months },
|
||||
{ label: "Last 6 Months", value: MetricTimeframe.Last6Months },
|
||||
{ label: "Last Year", value: MetricTimeframe.LastYear },
|
||||
]}
|
||||
onChange={(value) => {
|
||||
setTimeframe(value);
|
||||
setTimeframe(value as MetricTimeframe);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import "@testing-library/jest-dom";
|
||||
import "cross-fetch/polyfill";
|
||||
|
||||
process.env.BASE_URL = window.location.origin;
|
|
@ -0,0 +1,13 @@
|
|||
/// <reference types="vitest" />
|
||||
/// <reference types="vite/client" />
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
setupFiles: "./src/setupTests.ts",
|
||||
environment: "jsdom",
|
||||
css: false,
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue