Dev (#2577)
* ui: use enum state for spot ready checker * ui: force worker for hls * ui: fix spot list header behavior, change spot login flow? * ui: bump spot v * ui: spot signup fixes
This commit is contained in:
parent
cbe2d62def
commit
9ed207abb1
9 changed files with 114 additions and 88 deletions
|
|
@ -119,6 +119,7 @@ interface Props {
|
|||
function PrivateRoutes(props: Props) {
|
||||
const { onboarding, sites, siteId } = props;
|
||||
const hasRecordings = sites.some(s => s.recorded);
|
||||
const redirectToSetup = props.scope === 0;
|
||||
const redirectToOnboarding =
|
||||
!onboarding && (localStorage.getItem(GLOBAL_HAS_NO_RECORDINGS) === 'true' || !hasRecordings) && props.scope > 0;
|
||||
const siteIdList: any = sites.map(({ id }) => id).toJS();
|
||||
|
|
@ -126,6 +127,13 @@ function PrivateRoutes(props: Props) {
|
|||
return (
|
||||
<Suspense fallback={<Loader loading={true} className="flex-1" />}>
|
||||
<Switch key="content">
|
||||
<Route
|
||||
exact
|
||||
strict
|
||||
path={SCOPE_SETUP}
|
||||
component={enhancedComponents.ScopeSetup}
|
||||
/>
|
||||
{redirectToSetup ? <Redirect to={SCOPE_SETUP} /> : null}
|
||||
<Route path={CLIENT_PATH} component={enhancedComponents.Client} />
|
||||
<Route
|
||||
path={withSiteId(ONBOARDING_PATH, siteIdList)}
|
||||
|
|
@ -143,12 +151,6 @@ function PrivateRoutes(props: Props) {
|
|||
path={SPOT_PATH}
|
||||
component={enhancedComponents.Spot}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
strict
|
||||
path={SCOPE_SETUP}
|
||||
component={enhancedComponents.ScopeSetup}
|
||||
/>
|
||||
{props.scope === 1 ? <Redirect to={SPOTS_LIST_PATH} /> : null}
|
||||
<Route
|
||||
path="/integrations/"
|
||||
|
|
|
|||
|
|
@ -10,21 +10,19 @@ import {
|
|||
GLOBAL_DESTINATION_PATH,
|
||||
IFRAME,
|
||||
JWT_PARAM,
|
||||
SPOT_ONBOARDING
|
||||
} from "App/constants/storageKeys";
|
||||
SPOT_ONBOARDING,
|
||||
} from 'App/constants/storageKeys';
|
||||
import Layout from 'App/layout/Layout';
|
||||
import { withStore } from "App/mstore";
|
||||
import { checkParam, handleSpotJWT, isTokenExpired } from "App/utils";
|
||||
import { withStore } from 'App/mstore';
|
||||
import { checkParam, handleSpotJWT, isTokenExpired } from 'App/utils';
|
||||
import { ModalProvider } from 'Components/Modal';
|
||||
import { ModalProvider as NewModalProvider } from 'Components/ModalContext';
|
||||
import { fetchListActive as fetchMetadata } from 'Duck/customField';
|
||||
import { setSessionPath } from 'Duck/sessions';
|
||||
import { fetchList as fetchSiteList } from 'Duck/site';
|
||||
import { init as initSite } from 'Duck/site';
|
||||
import { fetchUserInfo, getScope, setJwt, logout } from "Duck/user";
|
||||
import { fetchTenants } from 'Duck/user';
|
||||
import { fetchUserInfo, getScope, logout, setJwt } from 'Duck/user';
|
||||
import { Loader } from 'UI';
|
||||
import { spotsList } from "./routes";
|
||||
import * as routes from './routes';
|
||||
|
||||
interface RouterProps
|
||||
|
|
@ -36,7 +34,6 @@ interface RouterProps
|
|||
changePassword: boolean;
|
||||
isEnterprise: boolean;
|
||||
fetchUserInfo: () => any;
|
||||
fetchTenants: () => any;
|
||||
setSessionPath: (path: any) => any;
|
||||
fetchSiteList: (siteId?: number) => any;
|
||||
match: {
|
||||
|
|
@ -45,7 +42,7 @@ interface RouterProps
|
|||
};
|
||||
};
|
||||
mstore: any;
|
||||
setJwt: (params: { jwt: string, spotJwt: string | null }) => any;
|
||||
setJwt: (params: { jwt: string; spotJwt: string | null }) => any;
|
||||
fetchMetadata: (siteId: string) => void;
|
||||
initSite: (site: any) => void;
|
||||
scopeSetup: boolean;
|
||||
|
|
@ -68,15 +65,16 @@ const Router: React.FC<RouterProps> = (props) => {
|
|||
logout,
|
||||
} = props;
|
||||
|
||||
const params = new URLSearchParams(location.search)
|
||||
const params = new URLSearchParams(location.search);
|
||||
const spotCb = params.get('spotCallback');
|
||||
const spotReqSent = React.useRef(false)
|
||||
const spotReqSent = React.useRef(false);
|
||||
const [isSpotCb, setIsSpotCb] = React.useState(false);
|
||||
const [isSignup, setIsSignup] = React.useState(false);
|
||||
const [isIframe, setIsIframe] = React.useState(false);
|
||||
const [isJwt, setIsJwt] = React.useState(false);
|
||||
|
||||
const handleJwtFromUrl = () => {
|
||||
const params = new URLSearchParams(location.search)
|
||||
const params = new URLSearchParams(location.search);
|
||||
const urlJWT = params.get('jwt');
|
||||
const spotJwt = params.get('spotJwt');
|
||||
if (spotJwt) {
|
||||
|
|
@ -92,6 +90,7 @@ const Router: React.FC<RouterProps> = (props) => {
|
|||
return;
|
||||
} else {
|
||||
spotReqSent.current = true;
|
||||
setIsSpotCb(false);
|
||||
}
|
||||
handleSpotJWT(jwt);
|
||||
};
|
||||
|
|
@ -107,13 +106,17 @@ const Router: React.FC<RouterProps> = (props) => {
|
|||
|
||||
const handleUserLogin = async () => {
|
||||
if (isSpotCb) {
|
||||
localStorage.setItem(SPOT_ONBOARDING, 'true')
|
||||
localStorage.setItem(SPOT_ONBOARDING, 'true');
|
||||
}
|
||||
await fetchUserInfo();
|
||||
const siteIdFromPath = parseInt(location.pathname.split('/')[1]);
|
||||
await fetchSiteList(siteIdFromPath);
|
||||
props.mstore.initClient();
|
||||
|
||||
if (localSpotJwt && !isTokenExpired(localSpotJwt)) {
|
||||
handleSpotLogin(localSpotJwt);
|
||||
}
|
||||
|
||||
const destinationPath = localStorage.getItem(GLOBAL_DESTINATION_PATH);
|
||||
if (
|
||||
destinationPath &&
|
||||
|
|
@ -144,7 +147,10 @@ const Router: React.FC<RouterProps> = (props) => {
|
|||
if (spotCb) {
|
||||
setIsSpotCb(true);
|
||||
}
|
||||
}, [spotCb])
|
||||
if (location.pathname.includes('signup')) {
|
||||
setIsSignup(true);
|
||||
}
|
||||
}, [spotCb]);
|
||||
|
||||
useEffect(() => {
|
||||
handleDestinationPath();
|
||||
|
|
@ -159,22 +165,14 @@ const Router: React.FC<RouterProps> = (props) => {
|
|||
}, [isLoggedIn]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scopeSetup) {
|
||||
history.push(routes.scopeSetup())
|
||||
}
|
||||
}, [scopeSetup])
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn && (location.pathname.includes('login') || isSpotCb)) {
|
||||
if (localSpotJwt) {
|
||||
if (!isTokenExpired(localSpotJwt)) {
|
||||
handleSpotLogin(localSpotJwt);
|
||||
} else {
|
||||
logout();
|
||||
}
|
||||
if (isLoggedIn && isSpotCb && !isSignup) {
|
||||
if (localSpotJwt && !isTokenExpired(localSpotJwt)) {
|
||||
handleSpotLogin(localSpotJwt);
|
||||
} else {
|
||||
logout();
|
||||
}
|
||||
}
|
||||
}, [isSpotCb, location, isLoggedIn, localSpotJwt])
|
||||
}, [isSpotCb, isLoggedIn, localSpotJwt, isSignup]);
|
||||
|
||||
useEffect(() => {
|
||||
if (siteId && siteId !== lastFetchedSiteIdRef.current) {
|
||||
|
|
@ -204,8 +202,7 @@ const Router: React.FC<RouterProps> = (props) => {
|
|||
location.pathname.includes('multiview') ||
|
||||
location.pathname.includes('/view-spot/') ||
|
||||
location.pathname.includes('/spots/') ||
|
||||
location.pathname.includes('/scope-setup')
|
||||
|
||||
location.pathname.includes('/scope-setup');
|
||||
|
||||
if (isIframe) {
|
||||
return (
|
||||
|
|
@ -238,8 +235,11 @@ const mapStateToProps = (state: Map<string, any>) => {
|
|||
'loading',
|
||||
]);
|
||||
const sitesLoading = state.getIn(['site', 'fetchListRequest', 'loading']);
|
||||
const scopeSetup = getScope(state) === 0
|
||||
const loading = Boolean(userInfoLoading) || Boolean(sitesLoading) || (!scopeSetup && !siteId);
|
||||
const scopeSetup = getScope(state) === 0;
|
||||
const loading =
|
||||
Boolean(userInfoLoading) ||
|
||||
Boolean(sitesLoading) ||
|
||||
(!scopeSetup && !siteId);
|
||||
return {
|
||||
siteId,
|
||||
changePassword,
|
||||
|
|
@ -262,7 +262,6 @@ const mapStateToProps = (state: Map<string, any>) => {
|
|||
|
||||
const mapDispatchToProps = {
|
||||
fetchUserInfo,
|
||||
fetchTenants,
|
||||
setSessionPath,
|
||||
fetchSiteList,
|
||||
setJwt,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ArrowRightOutlined } from '@ant-design/icons';
|
|||
import { Button, Card, Radio } from 'antd';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { upgradeScope, downgradeScope } from "App/duck/user";
|
||||
import { upgradeScope, downgradeScope, getScope } from 'App/duck/user';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import * as routes from 'App/routes'
|
||||
import { SPOT_ONBOARDING } from "../../constants/storageKeys";
|
||||
|
|
@ -15,8 +15,18 @@ const Scope = {
|
|||
function ScopeForm({
|
||||
upgradeScope,
|
||||
downgradeScope,
|
||||
scopeState,
|
||||
}: any) {
|
||||
const [scope, setScope] = React.useState(Scope.FULL);
|
||||
React.useEffect(() => {
|
||||
if (scopeState !== 0) {
|
||||
if (scopeState === 2) {
|
||||
history.replace(routes.onboarding())
|
||||
} else {
|
||||
history.replace(routes.spotsList())
|
||||
}
|
||||
}
|
||||
}, [scopeState])
|
||||
React.useEffect(() => {
|
||||
const isSpotSetup = localStorage.getItem(SPOT_ONBOARDING)
|
||||
if (isSpotSetup) {
|
||||
|
|
@ -36,50 +46,52 @@ function ScopeForm({
|
|||
};
|
||||
return (
|
||||
<div className={'flex items-center justify-center w-screen h-screen'}>
|
||||
<Card
|
||||
style={{ width: 540 }}
|
||||
title={'👋 Welcome to OpenReplay'}
|
||||
classNames={{
|
||||
header: 'text-2xl font-semibold text-center',
|
||||
body: 'flex flex-col gap-2',
|
||||
}}
|
||||
>
|
||||
<div className={'font-semibold'}>
|
||||
How will you primarily use OpenReplay?{' '}
|
||||
</div>
|
||||
<div className={'text-disabled-text'}>
|
||||
<div>
|
||||
You will have access to all OpenReplay features regardless of your
|
||||
choice.
|
||||
</div>
|
||||
<div>
|
||||
Your preference will simply help us tailor your onboarding experience.
|
||||
</div>
|
||||
</div>
|
||||
<Radio.Group
|
||||
value={scope}
|
||||
onChange={(e) => setScope(e.target.value)}
|
||||
className={'flex flex-col gap-2 mt-4 '}
|
||||
<Card
|
||||
style={{ width: 540 }}
|
||||
title={'👋 Welcome to OpenReplay'}
|
||||
classNames={{
|
||||
header: 'text-2xl font-semibold text-center',
|
||||
body: 'flex flex-col gap-2',
|
||||
}}
|
||||
>
|
||||
<Radio value={'full'}>
|
||||
Session Replay & Debugging, Customer Support and more
|
||||
</Radio>
|
||||
<Radio value={'spot'}>Report bugs via Spot</Radio>
|
||||
</Radio.Group>
|
||||
|
||||
<div className={'self-end'}>
|
||||
<Button
|
||||
type={'primary'}
|
||||
onClick={() => onContinue()}
|
||||
icon={<ArrowRightOutlined />}
|
||||
iconPosition={'end'}
|
||||
<div className={'font-semibold'}>
|
||||
How will you primarily use OpenReplay?{' '}
|
||||
</div>
|
||||
<div className={'text-disabled-text'}>
|
||||
<div>
|
||||
You will have access to all OpenReplay features regardless of your
|
||||
choice.
|
||||
</div>
|
||||
<div>
|
||||
Your preference will simply help us tailor your onboarding experience.
|
||||
</div>
|
||||
</div>
|
||||
<Radio.Group
|
||||
value={scope}
|
||||
onChange={(e) => setScope(e.target.value)}
|
||||
className={'flex flex-col gap-2 mt-4 '}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<Radio value={'full'}>
|
||||
Session Replay & Debugging, Customer Support and more
|
||||
</Radio>
|
||||
<Radio value={'spot'}>Report bugs via Spot</Radio>
|
||||
</Radio.Group>
|
||||
|
||||
<div className={'self-end'}>
|
||||
<Button
|
||||
type={'primary'}
|
||||
onClick={() => onContinue()}
|
||||
icon={<ArrowRightOutlined />}
|
||||
iconPosition={'end'}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(null, { upgradeScope, downgradeScope })(ScopeForm);
|
||||
export default connect((state) => ({
|
||||
scopeState: getScope(state),
|
||||
}), { upgradeScope, downgradeScope })(ScopeForm);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const SpotsListHeader = observer(
|
|||
onDelete,
|
||||
selectedCount,
|
||||
onClearSelection,
|
||||
isEmpty,
|
||||
tenantHasSpots,
|
||||
onRefresh,
|
||||
}: {
|
||||
onDelete: () => void;
|
||||
|
|
@ -18,6 +18,7 @@ const SpotsListHeader = observer(
|
|||
onClearSelection: () => void;
|
||||
onRefresh: () => void;
|
||||
isEmpty?: boolean;
|
||||
tenantHasSpots: boolean;
|
||||
}) => {
|
||||
const { spotStore } = useStore();
|
||||
|
||||
|
|
@ -52,7 +53,7 @@ const SpotsListHeader = observer(
|
|||
<ReloadButton buttonSize={'small'} onClick={onRefresh} iconSize={16} />
|
||||
</div>
|
||||
|
||||
{isEmpty ? null : (
|
||||
{tenantHasSpots ? null : (
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className={'ml-auto'}>
|
||||
{selectedCount > 0 && (
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ function SpotsList() {
|
|||
selectedCount={selectedSpots.length}
|
||||
onClearSelection={clearSelection}
|
||||
isEmpty={isEmpty}
|
||||
tenantHasSpots={spotStore.tenantHasSpots}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import { makeAutoObservable } from 'mobx';
|
||||
|
||||
|
||||
|
||||
import { spotService } from 'App/services';
|
||||
import { UpdateSpotRequest } from 'App/services/spotService';
|
||||
|
||||
|
||||
|
||||
import { Spot } from './types/spot';
|
||||
|
||||
|
||||
export default class SpotStore {
|
||||
isLoading: boolean = false;
|
||||
spots: Spot[] = [];
|
||||
|
|
@ -18,6 +23,7 @@ export default class SpotStore {
|
|||
pubKey: { value: string; expiration: number } | null = null;
|
||||
readonly order = 'desc';
|
||||
accessError = false;
|
||||
tenantHasSpots = false;
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
|
|
@ -81,13 +87,18 @@ export default class SpotStore {
|
|||
limit: this.limit,
|
||||
} as const;
|
||||
|
||||
const response = await this.withLoader(() =>
|
||||
const { spots, tenantHasSpots, total } = await this.withLoader(() =>
|
||||
spotService.fetchSpots(filters)
|
||||
);
|
||||
this.setSpots(response.spots.map((spot: any) => new Spot(spot)));
|
||||
this.setTotal(response.total);
|
||||
this.setSpots(spots.map((spot: any) => new Spot(spot)));
|
||||
this.setTotal(total);
|
||||
this.setTenantHasSpots(tenantHasSpots);
|
||||
};
|
||||
|
||||
setTenantHasSpots(hasSpots: boolean) {
|
||||
this.tenantHasSpots = hasSpots;
|
||||
}
|
||||
|
||||
async fetchSpotById(id: string) {
|
||||
try {
|
||||
const response = await this.withLoader(() =>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ interface AddCommentRequest {
|
|||
interface GetSpotsResponse {
|
||||
spots: SpotInfo[];
|
||||
total: number;
|
||||
tenantHasSpots: boolean;
|
||||
}
|
||||
|
||||
interface GetSpotsRequest {
|
||||
|
|
|
|||
|
|
@ -504,7 +504,6 @@ export function truncateStringToFit(string: string, screenWidth: number, charWid
|
|||
|
||||
let sendingRequest = false;
|
||||
export const handleSpotJWT = (jwt: string) => {
|
||||
console.log(jwt, sendingRequest)
|
||||
let tries = 0;
|
||||
if (!jwt || sendingRequest) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "wxt-starter",
|
||||
"description": "manifest.json description",
|
||||
"private": true,
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue