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/react-vite": "^7.1.0",
|
||||||
"@storybook/testing-library": "^0.2.0",
|
"@storybook/testing-library": "^0.2.0",
|
||||||
"@storybook/types": "^7.1.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/node": "^20.4.2",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
|
"jsdom": "^22.1.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"storybook": "^7.1.0",
|
"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 { ApolloProvider } from "@apollo/client";
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { apolloClient } from "./apolloClient";
|
import { apolloClient } from "./apolloClient";
|
||||||
import { AuthIFrame } from "./components/AuthIFrame";
|
import { AuthIFrame } from "./components";
|
||||||
import { MetricDetailsPage } from "./pages";
|
import { MetricDetailsPage } from "./pages/MetricDetailsPage";
|
||||||
|
|
||||||
export const App: React.FC = () => {
|
export const App: React.FC = () => {
|
||||||
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean>();
|
||||||
return (
|
return (
|
||||||
<ApolloProvider client={apolloClient}>
|
<ApolloProvider client={apolloClient}>
|
||||||
<AuthIFrame />
|
<AuthIFrame
|
||||||
<MetricDetailsPage />
|
onChange={(value) => setIsAuthenticated(value.isAuthenticated)}
|
||||||
|
/>
|
||||||
|
{isAuthenticated ? <MetricDetailsPage /> : null}
|
||||||
</ApolloProvider>
|
</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 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
|
* 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
|
* 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 [url, setUrl] = useState<string>();
|
||||||
const [done, setDone] = useState<boolean>(false);
|
const [done, setDone] = useState<boolean>(false);
|
||||||
|
|
||||||
const handleMessage = (event: MessageEvent) => {
|
const handleMessage = (event: MessageEvent) => {
|
||||||
if (event.origin.startsWith(config.baseUrl)) {
|
if (event.origin.startsWith(config.baseUrl)) {
|
||||||
localStorage.setItem(config.localstorageKeys.token, event.data);
|
localStorage.setItem(config.localstorageKeys.token, event.data);
|
||||||
|
props.onChange({ isAuthenticated: true });
|
||||||
console.info(
|
console.info(
|
||||||
`new token received from ${config.authIFrameUrl}`,
|
`new token received from ${config.authIFrameUrl}`,
|
||||||
event.data
|
event.data
|
||||||
|
@ -32,6 +37,11 @@ export const AuthIFrame: React.FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("message", handleMessage);
|
window.addEventListener("message", handleMessage);
|
||||||
|
props.onChange({
|
||||||
|
isAuthenticated: Boolean(
|
||||||
|
localStorage.getItem(config.localstorageKeys.token)
|
||||||
|
),
|
||||||
|
});
|
||||||
setUrl(`${config.authIFrameUrl}`);
|
setUrl(`${config.authIFrameUrl}`);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("message", handleMessage);
|
window.removeEventListener("message", handleMessage);
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./AuthIFrame";
|
|
@ -2,3 +2,4 @@ export * from "./Button";
|
||||||
export * from "./Chart";
|
export * from "./Chart";
|
||||||
export * from "./Dropdown";
|
export * from "./Dropdown";
|
||||||
export * from "./Icon";
|
export * from "./Icon";
|
||||||
|
export * from "./AuthIFrame";
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Dropdown } from "../components";
|
import { Dropdown } from "../components";
|
||||||
import { Chart } from "../containers/Chart";
|
import { Chart } from "../containers/Chart";
|
||||||
import { MetricType } from "../generated/graphql";
|
import { MetricType, MetricTimeframe } from "../generated/graphql";
|
||||||
|
|
||||||
export const MetricDetailsPage: React.FC = () => {
|
export const MetricDetailsPage: React.FC = () => {
|
||||||
const [path, setPath] = useState<string>();
|
const [path, setPath] = useState<string>();
|
||||||
const [name, setName] = useState<string>();
|
const [name, setName] = useState<string>();
|
||||||
const [timeframe, setTimeframe] = useState<string>("Last30days");
|
const [timeframe, setTimeframe] = useState<MetricTimeframe>(
|
||||||
|
MetricTimeframe.Last30days
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribeCurrentPage = webflow.subscribe("currentpage", (page) => {
|
const unsubscribeCurrentPage = webflow.subscribe("currentpage", (page) => {
|
||||||
|
@ -38,14 +40,14 @@ export const MetricDetailsPage: React.FC = () => {
|
||||||
<Dropdown
|
<Dropdown
|
||||||
selected="Last30days"
|
selected="Last30days"
|
||||||
options={[
|
options={[
|
||||||
{ label: "Last 7 days", value: "Last7days" },
|
{ label: "Last 7 days", value: MetricTimeframe.Last7days },
|
||||||
{ label: "Last 30 days", value: "Last30days" },
|
{ label: "Last 30 days", value: MetricTimeframe.Last30days },
|
||||||
{ label: "Last 3 Months", value: "Last3Months" },
|
{ label: "Last 3 Months", value: MetricTimeframe.Last3Months },
|
||||||
{ label: "Last 6 Months", value: "Last6Months" },
|
{ label: "Last 6 Months", value: MetricTimeframe.Last6Months },
|
||||||
{ label: "Last Year", value: "LastYear" },
|
{ label: "Last Year", value: MetricTimeframe.LastYear },
|
||||||
]}
|
]}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setTimeframe(value);
|
setTimeframe(value as MetricTimeframe);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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