Logged-out home stays the product page (#120)
## Summary - Closes #118 and #119. - Logged-out `/` is the product page. Sign In is a link. After sign-in, `/` is still the chat dashboard. - A 401 from a public API call (for example a stale token on `/public/usage/`) no longer sends visitors from `/` to Sign In. Protected routes such as `/account/` still redirect. - Product, pricing, changelog, and privacy use the same floating-dot background as Sign In and the dashboard. Text follows the light and dark theme so it stays readable. ## Test plan - [x] `authRedirect` tests: public API calls and public paths do not redirect; `/account/` and `/document_storage` still do. - [x] Landing, pricing, privacy, changelog, HomeRoute, and Sign In tests pass. - [ ] Logged out, open `/` and confirm the product page, then Sign In, then `/` is the dashboard. - [ ] With an expired access token still in storage, open `/` and confirm you stay on the product page. - [ ] Check product, pricing, changelog, and privacy in light and dark mode. Dots visible, text readable. Reviewed-on: #120
This commit was merged in pull request #120.
This commit is contained in:
1 parent
fa91c4969f
commit
4a5b05f421
15 files changed
+149
-38
No files matched your search
+14
-5
@@ -11,6 +11,7 @@ import {
|
|||||||
isTokenNotValidError,
|
isTokenNotValidError,
|
||||||
} from "./llm-fe/auth/jwtHelpers";
|
} from "./llm-fe/auth/jwtHelpers";
|
||||||
import { redirectToAppPath } from "./llm-fe/platform/nativePlatform";
|
import { redirectToAppPath } from "./llm-fe/platform/nativePlatform";
|
||||||
|
import { shouldRedirectToSignIn } from "./llm-fe/auth/authRedirect";
|
||||||
|
|
||||||
const baseURL = process.env.REACT_APP_BACKEND_REST_API_BASE_URL;
|
const baseURL = process.env.REACT_APP_BACKEND_REST_API_BASE_URL;
|
||||||
|
|
||||||
@@ -97,8 +98,10 @@ axiosInstance.interceptors.response.use(
|
|||||||
if (error.response.status === 401 && isRefreshCall) {
|
if (error.response.status === 401 && isRefreshCall) {
|
||||||
await clearTokens();
|
await clearTokens();
|
||||||
applyAccessToken(null);
|
applyAccessToken(null);
|
||||||
// Hash-safe on Capacitor (#24); plain path on web BrowserRouter.
|
// Stay on the product page. Only bounce protected app routes.
|
||||||
redirectToAppPath("/signin/");
|
if (shouldRedirectToSignIn(requestUrl, window.location.pathname)) {
|
||||||
|
redirectToAppPath("/signin/");
|
||||||
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +112,9 @@ axiosInstance.interceptors.response.use(
|
|||||||
if (!refresh_token) {
|
if (!refresh_token) {
|
||||||
await clearTokens();
|
await clearTokens();
|
||||||
applyAccessToken(null);
|
applyAccessToken(null);
|
||||||
redirectToAppPath("/signin/");
|
if (shouldRedirectToSignIn(requestUrl, window.location.pathname)) {
|
||||||
|
redirectToAppPath("/signin/");
|
||||||
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,14 +132,18 @@ axiosInstance.interceptors.response.use(
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
await clearTokens();
|
await clearTokens();
|
||||||
applyAccessToken(null);
|
applyAccessToken(null);
|
||||||
redirectToAppPath("/signin/");
|
if (shouldRedirectToSignIn(requestUrl, window.location.pathname)) {
|
||||||
|
redirectToAppPath("/signin/");
|
||||||
|
}
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await clearTokens();
|
await clearTokens();
|
||||||
applyAccessToken(null);
|
applyAccessToken(null);
|
||||||
redirectToAppPath("/signin/");
|
if (shouldRedirectToSignIn(requestUrl, window.location.pathname)) {
|
||||||
|
redirectToAppPath("/signin/");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { shouldRedirectToSignIn } from './authRedirect';
|
||||||
|
|
||||||
|
describe('shouldRedirectToSignIn', () => {
|
||||||
|
it('keeps a logged-out visitor on the product page', () => {
|
||||||
|
expect(shouldRedirectToSignIn('/public/usage/', '/')).toBe(false);
|
||||||
|
expect(shouldRedirectToSignIn('/public/settings/', '/pricing/')).toBe(false);
|
||||||
|
expect(shouldRedirectToSignIn('/user/get/', '/')).toBe(false);
|
||||||
|
expect(shouldRedirectToSignIn('/user/get/', '/changelog/')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('still sends an expired session on the app to sign in', () => {
|
||||||
|
expect(shouldRedirectToSignIn('/user/get/', '/account/')).toBe(true);
|
||||||
|
expect(shouldRedirectToSignIn('/token/refresh/', '/document_storage')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/** Public marketing routes. An auth failure here must not replace the page with Sign In. */
|
||||||
|
|
||||||
|
const PUBLIC_PATHS = new Set([
|
||||||
|
"/",
|
||||||
|
"/pricing",
|
||||||
|
"/privacy",
|
||||||
|
"/changelog",
|
||||||
|
"/terms_of_service",
|
||||||
|
"/legal/terms-of-service",
|
||||||
|
"/account-deletion",
|
||||||
|
"/signin",
|
||||||
|
"/signup",
|
||||||
|
"/password_reset",
|
||||||
|
"/password_reset_confirmation",
|
||||||
|
"/set_password",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function normalizeAppPath(pathname: string | null | undefined): string {
|
||||||
|
if (!pathname || pathname === "/") return "/";
|
||||||
|
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPublicApiRequest(requestUrl: string | null | undefined): boolean {
|
||||||
|
return String(requestUrl || "").includes("/public/");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPublicAppPath(pathname: string | null | undefined): boolean {
|
||||||
|
return PUBLIC_PATHS.has(normalizeAppPath(pathname));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldRedirectToSignIn(
|
||||||
|
requestUrl: string | null | undefined,
|
||||||
|
pathname: string | null | undefined,
|
||||||
|
): boolean {
|
||||||
|
if (isPublicApiRequest(requestUrl)) return false;
|
||||||
|
if (isPublicAppPath(pathname)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { axiosInstance } from '../../../axiosApi';
|
import { axiosInstance } from '../../../axiosApi';
|
||||||
import { AnalyticsEvents, trackEvent } from '../../utils/analytics';
|
import { AnalyticsEvents, trackEvent } from '../../utils/analytics';
|
||||||
|
import { publicAccent, publicCard, publicLine, publicText } from './publicColors';
|
||||||
|
|
||||||
const Stack = styled.div`
|
const Stack = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -34,12 +35,13 @@ const Primary = styled(Link)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Secondary = styled(Link)`
|
const Secondary = styled(Link)`
|
||||||
color: #d6dcff;
|
color: ${publicAccent};
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Quiet = styled(Link)`
|
const Quiet = styled(Link)`
|
||||||
color: rgba(255, 255, 255, 0.72);
|
color: ${publicText};
|
||||||
|
opacity: 0.75;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -63,11 +65,11 @@ const Label = styled.label`
|
|||||||
|
|
||||||
const Input = styled.input`
|
const Input = styled.input`
|
||||||
flex: 1 1 14rem;
|
flex: 1 1 14rem;
|
||||||
background: rgba(255, 255, 255, 0.06);
|
background: ${publicCard};
|
||||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
border: 1px solid ${publicLine};
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
padding: 0.9rem 1rem;
|
padding: 0.9rem 1rem;
|
||||||
color: #fff;
|
color: ${publicText};
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -89,7 +91,7 @@ const Submit = styled.button`
|
|||||||
|
|
||||||
const Note = styled.p`
|
const Note = styled.p`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: rgba(255, 255, 255, 0.8);
|
color: ${publicText};
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import ParticleBackground from '../ParticleBackground/ParticleBackground';
|
||||||
import { PUBLIC_FOOTER_LINKS, PUBLIC_HEADER_LINKS } from './publicNav';
|
import { PUBLIC_FOOTER_LINKS, PUBLIC_HEADER_LINKS } from './publicNav';
|
||||||
|
|
||||||
|
const text = ({ theme }: { theme?: { colors?: { text?: string } } }) =>
|
||||||
|
theme?.colors?.text ?? '#344767';
|
||||||
|
|
||||||
|
const line = ({ theme }: { theme?: { colors?: { cardBorder?: string } } }) =>
|
||||||
|
theme?.colors?.cardBorder ?? 'rgba(0, 0, 0, 0.08)';
|
||||||
|
|
||||||
const Page = styled.div`
|
const Page = styled.div`
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #07080d;
|
background: transparent;
|
||||||
color: #fff;
|
color: ${text};
|
||||||
font-family: Inter, sans-serif;
|
font-family: Inter, sans-serif;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -19,11 +28,11 @@ const Header = styled.header`
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1.25rem 1.5rem;
|
padding: 1.25rem 1.5rem;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
border-bottom: 1px solid ${line};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Brand = styled(Link)`
|
const Brand = styled(Link)`
|
||||||
color: #fff;
|
color: ${text};
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -38,19 +47,20 @@ const Nav = styled.nav`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const NavLink = styled(Link)`
|
const NavLink = styled(Link)`
|
||||||
color: rgba(255, 255, 255, 0.82);
|
color: ${text};
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
|
opacity: 0.85;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #fff;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SignInLink = styled(Link)`
|
const SignInLink = styled(Link)`
|
||||||
color: #fff;
|
color: ${text};
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.28);
|
border: 1px solid ${line};
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
padding: 0.45rem 0.8rem;
|
padding: 0.45rem 0.8rem;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
@@ -64,10 +74,11 @@ const Main = styled.main`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const FooterBar = styled.footer`
|
const FooterBar = styled.footer`
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
border-top: 1px solid ${line};
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: ${text};
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.85;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FooterNav = styled.nav`
|
const FooterNav = styled.nav`
|
||||||
@@ -78,7 +89,7 @@ const FooterNav = styled.nav`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const FooterLink = styled(Link)`
|
const FooterLink = styled(Link)`
|
||||||
color: rgba(255, 255, 255, 0.82);
|
color: ${text};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type PublicSiteLayoutProps = {
|
type PublicSiteLayoutProps = {
|
||||||
@@ -88,6 +99,7 @@ type PublicSiteLayoutProps = {
|
|||||||
const PublicSiteLayout = ({ children }: PublicSiteLayoutProps): JSX.Element => {
|
const PublicSiteLayout = ({ children }: PublicSiteLayoutProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
<ParticleBackground />
|
||||||
<Header>
|
<Header>
|
||||||
<Brand to="/">Hesychia</Brand>
|
<Brand to="/">Hesychia</Brand>
|
||||||
<Nav aria-label="Primary">
|
<Nav aria-label="Primary">
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { axiosInstance } from '../../../axiosApi';
|
import { axiosInstance } from '../../../axiosApi';
|
||||||
|
import { publicText } from './publicColors';
|
||||||
|
|
||||||
const Section = styled.section`
|
const Section = styled.section`
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
color: rgba(255, 255, 255, 0.86);
|
color: ${publicText};
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -15,7 +16,8 @@ const Figures = styled.p`
|
|||||||
|
|
||||||
const AsOf = styled.p`
|
const AsOf = styled.p`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: rgba(255, 255, 255, 0.62);
|
color: ${publicText};
|
||||||
|
opacity: 0.7;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type PublicUsage = {
|
type PublicUsage = {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
type PublicTheme = {
|
||||||
|
colors?: {
|
||||||
|
text?: string;
|
||||||
|
cardBackground?: string;
|
||||||
|
cardBorder?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const publicText = ({ theme }: { theme?: PublicTheme }) => theme?.colors?.text ?? '#344767';
|
||||||
|
|
||||||
|
export const publicCard = ({ theme }: { theme?: PublicTheme }) =>
|
||||||
|
theme?.colors?.cardBackground ?? 'rgba(255, 255, 255, 0.72)';
|
||||||
|
|
||||||
|
export const publicLine = ({ theme }: { theme?: PublicTheme }) =>
|
||||||
|
theme?.colors?.cardBorder ?? 'rgba(0, 0, 0, 0.08)';
|
||||||
|
|
||||||
|
export const publicAccent = '#667eea';
|
||||||
@@ -4,6 +4,8 @@ import { MemoryRouter } from 'react-router-dom';
|
|||||||
import ChangelogPage from './ChangelogPage';
|
import ChangelogPage from './ChangelogPage';
|
||||||
import { CHANGELOG_ENTRIES } from './changelogEntries';
|
import { CHANGELOG_ENTRIES } from './changelogEntries';
|
||||||
|
|
||||||
|
jest.mock('../../components/ParticleBackground/ParticleBackground', () => () => null);
|
||||||
|
|
||||||
describe('ChangelogPage', () => {
|
describe('ChangelogPage', () => {
|
||||||
it('lists dated shipped notes and no founder', () => {
|
it('lists dated shipped notes and no founder', () => {
|
||||||
render(
|
render(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import PublicSiteLayout from '../../components/PublicSite/PublicSiteLayout';
|
import PublicSiteLayout from '../../components/PublicSite/PublicSiteLayout';
|
||||||
import { CHANGELOG_ENTRIES } from './changelogEntries';
|
import { CHANGELOG_ENTRIES } from './changelogEntries';
|
||||||
|
import { publicLine, publicText } from '../../components/PublicSite/publicColors';
|
||||||
|
|
||||||
const Title = styled.h1`
|
const Title = styled.h1`
|
||||||
font-size: clamp(2rem, 4vw, 2.75rem);
|
font-size: clamp(2rem, 4vw, 2.75rem);
|
||||||
@@ -17,13 +18,14 @@ const List = styled.ol`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Item = styled.li`
|
const Item = styled.li`
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
border-top: 1px solid ${publicLine};
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const When = styled.time`
|
const When = styled.time`
|
||||||
display: block;
|
display: block;
|
||||||
color: rgba(255, 255, 255, 0.6);
|
color: ${publicText};
|
||||||
|
opacity: 0.65;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
margin-bottom: 0.35rem;
|
margin-bottom: 0.35rem;
|
||||||
`;
|
`;
|
||||||
@@ -35,7 +37,7 @@ const Heading = styled.h2`
|
|||||||
|
|
||||||
const Note = styled.p`
|
const Note = styled.p`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: rgba(255, 255, 255, 0.82);
|
color: ${publicText};
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import userEvent from '@testing-library/user-event';
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import LandingPage from './LandingPage';
|
import LandingPage from './LandingPage';
|
||||||
|
|
||||||
|
jest.mock('../../components/ParticleBackground/ParticleBackground', () => () => null);
|
||||||
|
|
||||||
const mockGet = jest.fn();
|
const mockGet = jest.fn();
|
||||||
const mockPost = jest.fn();
|
const mockPost = jest.fn();
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import hesychiaMark from '../../assets/brand/hesychia-mark.png';
|
|||||||
import GetStarted from '../../components/PublicSite/GetStarted';
|
import GetStarted from '../../components/PublicSite/GetStarted';
|
||||||
import PublicSiteLayout from '../../components/PublicSite/PublicSiteLayout';
|
import PublicSiteLayout from '../../components/PublicSite/PublicSiteLayout';
|
||||||
import UsageCounts from '../../components/PublicSite/UsageCounts';
|
import UsageCounts from '../../components/PublicSite/UsageCounts';
|
||||||
|
import { publicAccent, publicCard, publicLine, publicText } from '../../components/PublicSite/publicColors';
|
||||||
|
|
||||||
const Hero = styled.header`
|
const Hero = styled.header`
|
||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
@@ -27,14 +28,15 @@ const Lede = styled.p`
|
|||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
margin: 0 0 0.75rem;
|
margin: 0 0 0.75rem;
|
||||||
color: rgba(255, 255, 255, 0.92);
|
color: ${publicText};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Memory = styled.p`
|
const Memory = styled.p`
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin: 0 0 1.5rem;
|
margin: 0 0 1.5rem;
|
||||||
color: rgba(255, 255, 255, 0.82);
|
color: ${publicText};
|
||||||
|
opacity: 0.85;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Privacy = styled.section`
|
const Privacy = styled.section`
|
||||||
@@ -45,12 +47,13 @@ const Privacy = styled.section`
|
|||||||
const PrivacyList = styled.ul`
|
const PrivacyList = styled.ul`
|
||||||
margin: 0.5rem 0 0.75rem;
|
margin: 0.5rem 0 0.75rem;
|
||||||
padding-left: 1.2rem;
|
padding-left: 1.2rem;
|
||||||
color: rgba(255, 255, 255, 0.82);
|
color: ${publicText};
|
||||||
|
opacity: 0.85;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TextLink = styled(Link)`
|
const TextLink = styled(Link)`
|
||||||
color: #c9d4ff;
|
color: ${publicAccent};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Features = styled.section`
|
const Features = styled.section`
|
||||||
@@ -60,10 +63,10 @@ const Features = styled.section`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Feature = styled.article`
|
const Feature = styled.article`
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid ${publicLine};
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
padding: 1.25rem 1.35rem;
|
padding: 1.25rem 1.35rem;
|
||||||
background: rgba(255, 255, 255, 0.03);
|
background: ${publicCard};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FeatureTitle = styled.h2`
|
const FeatureTitle = styled.h2`
|
||||||
@@ -73,7 +76,8 @@ const FeatureTitle = styled.h2`
|
|||||||
|
|
||||||
const FeatureBody = styled.p`
|
const FeatureBody = styled.p`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: rgba(255, 255, 255, 0.8);
|
color: ${publicText};
|
||||||
|
opacity: 0.85;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { render, screen } from '@testing-library/react';
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import PricingPage from './PricingPage';
|
import PricingPage from './PricingPage';
|
||||||
|
|
||||||
|
jest.mock('../../components/ParticleBackground/ParticleBackground', () => () => null);
|
||||||
|
|
||||||
jest.mock('../../../axiosApi', () => ({
|
jest.mock('../../../axiosApi', () => ({
|
||||||
axiosInstance: {
|
axiosInstance: {
|
||||||
get: () => Promise.resolve({ data: { enable_account_registration: false } }),
|
get: () => Promise.resolve({ data: { enable_account_registration: false } }),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import GetStarted from '../../components/PublicSite/GetStarted';
|
import GetStarted from '../../components/PublicSite/GetStarted';
|
||||||
import PublicSiteLayout from '../../components/PublicSite/PublicSiteLayout';
|
import PublicSiteLayout from '../../components/PublicSite/PublicSiteLayout';
|
||||||
|
import { publicAccent, publicText } from '../../components/PublicSite/publicColors';
|
||||||
|
|
||||||
const Title = styled.h1`
|
const Title = styled.h1`
|
||||||
font-size: clamp(2rem, 4vw, 2.75rem);
|
font-size: clamp(2rem, 4vw, 2.75rem);
|
||||||
@@ -15,13 +16,13 @@ const Price = styled.p`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Body = styled.p`
|
const Body = styled.p`
|
||||||
color: rgba(255, 255, 255, 0.82);
|
color: ${publicText};
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
max-width: 36rem;
|
max-width: 36rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Back = styled(Link)`
|
const Back = styled(Link)`
|
||||||
color: #c9d4ff;
|
color: ${publicAccent};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PricingPage = (): JSX.Element => {
|
const PricingPage = (): JSX.Element => {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { render, screen } from '@testing-library/react';
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import PrivacyPage from './PrivacyPage';
|
import PrivacyPage from './PrivacyPage';
|
||||||
|
|
||||||
|
jest.mock('../../components/ParticleBackground/ParticleBackground', () => () => null);
|
||||||
|
|
||||||
describe('PrivacyPage', () => {
|
describe('PrivacyPage', () => {
|
||||||
it('states storage, no ads, and that cancel does not delete documents', () => {
|
it('states storage, no ads, and that cancel does not delete documents', () => {
|
||||||
render(
|
render(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import PublicSiteLayout from '../../components/PublicSite/PublicSiteLayout';
|
import PublicSiteLayout from '../../components/PublicSite/PublicSiteLayout';
|
||||||
|
import { publicAccent, publicText } from '../../components/PublicSite/publicColors';
|
||||||
|
|
||||||
const Title = styled.h1`
|
const Title = styled.h1`
|
||||||
font-size: clamp(2rem, 4vw, 2.75rem);
|
font-size: clamp(2rem, 4vw, 2.75rem);
|
||||||
@@ -20,12 +21,12 @@ const Heading = styled.h2`
|
|||||||
|
|
||||||
const Body = styled.p`
|
const Body = styled.p`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: rgba(255, 255, 255, 0.82);
|
color: ${publicText};
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TextLink = styled(Link)`
|
const TextLink = styled(Link)`
|
||||||
color: #c9d4ff;
|
color: ${publicAccent};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PrivacyPage = (): JSX.Element => {
|
const PrivacyPage = (): JSX.Element => {
|
||||||
|
|||||||
Reference in new issue
Block a user