Adding welcome first user modal and auto-silent-renew for token (#184)

* Adding welcome first user modal and auto-silent-renew for token

* Minor fix
This commit is contained in:
darth-coder00 2021-08-16 08:44:48 +05:30 committed by GitHub
parent b5ed3feac2
commit d170cfe1c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 160 additions and 90 deletions

View File

@ -0,0 +1,19 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Module declaration to allow importing JPEG files
declare module '*.jpeg';

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -19,7 +19,7 @@ import { AxiosResponse } from 'axios';
import { CookieStorage } from 'cookie-storage';
import { isEmpty, isNil } from 'lodash';
import { observer } from 'mobx-react';
import { User } from 'Models';
import { NewUser, User } from 'Models';
import { UserManager, WebStorageStateStore } from 'oidc-client';
import React, {
ComponentType,
@ -28,7 +28,13 @@ import React, {
useState,
} from 'react';
import { Callback, makeAuthenticator, makeUserManager } from 'react-oidc';
import { Redirect, Route, Switch, useHistory } from 'react-router-dom';
import {
Redirect,
Route,
Switch,
useHistory,
useLocation,
} from 'react-router-dom';
import appState from '../AppState';
import axiosClient from '../axiosAPIs';
import { fetchAuthorizerConfig } from '../axiosAPIs/miscAPI';
@ -38,6 +44,7 @@ import {
getUserByName,
getUsers,
} from '../axiosAPIs/userAPI';
import { FirstTimeUserModal } from '../components/Modals/FirstTimeUserModal/FirstTimeUserModal';
import {
API_RES_MAX_SIZE,
oidcTokenKey,
@ -72,9 +79,16 @@ const AuthProvider: FunctionComponent<AuthProviderProps> = ({
childComponentType,
children,
}: AuthProviderProps) => {
const location = useLocation();
const history = useHistory();
const showToast = useToastContext();
const { isSignedIn, isSigningIn, isSignedOut } = useAuth();
const {
isAuthenticatedRoute,
isFirstTimeUser,
isSignedIn,
isSigningIn,
isSignedOut,
} = useAuth(location.pathname);
const oidcUserToken = cookieStorage.getItem(oidcTokenKey);
const [loading, setLoading] = useState(true);
@ -82,11 +96,11 @@ const AuthProvider: FunctionComponent<AuthProviderProps> = ({
{} as UserManager
);
const [userManagerConfig, setUserManagerConfig] = useState<
Record<string, string | WebStorageStateStore>
Record<string, string | boolean | WebStorageStateStore>
>({});
const clearOidcUserData = (
userConfig: Record<string, string | WebStorageStateStore>
userConfig: Record<string, string | boolean | WebStorageStateStore>
): void => {
cookieStorage.removeItem(
`oidc.user:${userConfig.authority}:${userConfig.client_id}`
@ -198,6 +212,15 @@ const AuthProvider: FunctionComponent<AuthProviderProps> = ({
});
};
const handleFirstTourModal = (skip: boolean) => {
appState.newUser = {} as NewUser;
if (skip) {
history.push(ROUTES.HOME);
} else {
// TODO: Route to tour page
}
};
useEffect(() => {
fetchAuthConfig();
@ -241,6 +264,7 @@ const AuthProvider: FunctionComponent<AuthProviderProps> = ({
return (
<>
{!loading ? (
<>
<Switch>
<Route exact path={ROUTES.HOME}>
{!isSignedIn && !isSigningIn ? (
@ -274,6 +298,13 @@ const AuthProvider: FunctionComponent<AuthProviderProps> = ({
<AppWithAuth />
)}
</Switch>
{isAuthenticatedRoute && isFirstTimeUser ? (
<FirstTimeUserModal
onCancel={() => handleFirstTourModal(true)}
onSave={() => handleFirstTourModal(false)}
/>
) : null}
</>
) : null}
</>
);

View File

@ -1,5 +1,6 @@
import classNames from 'classnames';
import React, { FunctionComponent, useState } from 'react';
import BGConfetti from '../../../assets/img/confetti-bg.jpeg';
import { Button } from '../../buttons/Button/Button';
type Props = {
@ -15,48 +16,44 @@ const description = [
export const FirstTimeUserModal: FunctionComponent<Props> = ({
onCancel,
onSave,
}: Props) => {
const [active, setActive] = useState<number>(0);
const [lastSlide, setLastSlide] = useState<boolean>(false);
const previousClick = () => {
if (lastSlide) {
// to somthing
} else {
setActive((pre) => pre - 1);
}
setLastSlide(false);
};
const nextClick = () => {
if (lastSlide) {
onCancel();
} else {
setActive((pre) => {
const newVal = pre + 1;
description.length - 1 === newVal && setLastSlide(true);
setLastSlide(description.length - 1 === newVal);
return newVal;
});
}
};
return (
<dialog className="tw-modal">
<div className="tw-modal-backdrop tw-opacity-80" />
<div className="tw-modal-container tw-max-w-xl tw-max-h-90vh tw-bg-gradient-to-bl tw-to-primary-lite tw-from-secondary-lite">
<div className="tw-modal-header tw-border-0 tw-justify-center tw-pt-8">
<p className="tw-modal-title tw-text-h4 tw-font-semibold tw-text-primary-active">
<div
className="tw-modal-container tw-modal-confetti tw-max-w-xl tw-max-h-90vh"
style={{ backgroundImage: `url(${BGConfetti})` }}>
<div className="tw-modal-header tw-border-0 tw-justify-center tw-pt-8 tw-pb-0">
<p className="tw-modal-title tw-text-h4 tw-font-semibold tw-text-primary-active tw-mt-32">
Welcome to OpenMetadata
</p>
</div>
<div className="tw-modal-body tw-relative tw-h-64 tw-justify-center tw-items-center">
<div className="tw-modal-body tw-relative tw-h-40 tw-justify-start tw-items-center">
{description.map((d, i) => (
<p
className={classNames(
i === active
? 'tw-opacity-100 tw-relative tw-transition-opacity tw-delay-200'
: 'tw-opacity-0 tw-absolute',
'tw-text-xl tw-font-medium tw-text-center'
'tw-text-xl tw-font-medium tw-text-center tw-bg-white tw-mx-7'
)}
key={i}>
{d}
@ -66,38 +63,48 @@ export const FirstTimeUserModal: FunctionComponent<Props> = ({
<div className="tw-modal-footer tw-border-0 tw-justify-between">
<Button
className={classNames(
'tw-bg-primary-active tw-text-white',
'tw-text-primary-active',
active === 0 ? 'tw-invisible' : null
)}
size="regular"
theme="primary"
variant="contained"
variant="text"
onClick={previousClick}>
{lastSlide ? (
'Take a Tour'
) : (
<>
<i className="fas fa-arrow-left tw-text-sm tw-align-middle tw-pr-1.5" />{' '}
<span>Previous</span>
</>
)}
</Button>
{lastSlide ? (
<span>
<Button
className="tw-text-primary-active tw-hidden"
size="regular"
theme="default"
variant="text"
onClick={onCancel}>
<span>Skip</span>
<i className="fas fa-angle-double-right tw-text-sm tw-align-middle tw-pl-1.5" />
</Button>
<Button
className="tw-bg-primary-active tw-text-white"
id="take-tour"
size="regular"
theme="primary"
variant="contained"
onClick={nextClick}>
{lastSlide ? (
'Skip and go to landing page'
onClick={onSave}>
Explore OpenMetadata
</Button>
</span>
) : (
<>
<Button
className="tw-text-primary-active"
size="regular"
theme="primary"
variant="text"
onClick={nextClick}>
<span>Next</span>
<i className="fas fa-arrow-right tw-text-sm tw-align-middle tw-pl-1.5" />
</>
)}
</Button>
)}
</div>
</div>
</dialog>

View File

@ -38,5 +38,6 @@ export const useAuth = (pathname = '') => {
isAuthenticatedRoute: isAuthenticatedRoute,
isAuthDisabled: authDisabled,
isAdminUser: userDetails?.isAdmin,
isFirstTimeUser: !isEmpty(userDetails) && !isEmpty(newUser),
};
};

View File

@ -40,7 +40,7 @@ const SigninPage = () => {
<PageContainer>
<div className="tw-w-screen tw-h-screen tw-flex tw-justify-center">
<div className="tw-flex tw-flex-col tw-items-center signin-box">
<div className="tw-flex tw-justify-center tw-items-center tw-my-7">
<div className="tw-flex tw-justify-center tw-items-center tw-mb-7 tw-mt-20">
<SVGIcons
alt="OpenMetadata Logo"
icon={Icons.LOGO_SMALL}
@ -56,7 +56,7 @@ const SigninPage = () => {
<h6 className="tw-mb-px">Centralized Metadata Store, Discover,</h6>
<h6 className="tw-mb-px">Collaborate and get your Data Right</h6>
</div>
<div className="tw-mt-16" onClick={handleSignIn}>
<div className="tw-mt-4" onClick={handleSignIn}>
{appState.authProvider.provider === AuthTypes.GOOGLE && (
<button className="tw-signin-button">
<SVGIcons

View File

@ -110,11 +110,17 @@
.tw-modal {
@apply tw-z-9999 tw-flex tw-fixed tw-inset-0 tw-bg-transparent tw-justify-center tw-h-screen tw-w-screen tw-items-center tw-antialiased;
}
.tw-modal-confetti {
background-size: cover;
background-position-y: -75px;
background-repeat: no-repeat;
background-position-x: center;
}
.tw-modal-backdrop {
@apply tw-opacity-60 tw-bg-body-hover tw-absolute tw-inset-0;
}
.tw-modal-container {
@apply tw-flex tw-flex-col tw-absolute tw-py-5 tw-bg-white tw-w-11/12 tw-max-w-screen-lg tw-max-h-screen tw-mx-auto tw-rounded-lg tw-border tw-border-main tw-shadow-xl;
@apply tw-flex tw-flex-col tw-absolute tw-py-5 tw-bg-white tw-w-11/12 tw-max-w-screen-lg tw-max-h-screen tw-mx-auto tw-rounded-lg tw-border tw-border-main tw-shadow-modal;
}
.tw-modal-header {
@apply tw-flex tw-flex-row tw-justify-between tw-px-6 tw-pb-5 tw-bg-transparent tw-border-b tw-border-separator;
@ -280,13 +286,11 @@
}
.signin-box {
@apply tw-m-auto tw-h-100 tw-w-120;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
@apply tw-m-auto tw-h-100 tw-w-120 tw-bg-white tw-shadow-modal;
}
.signup-box {
@apply tw-m-auto tw-w-120 tw-bg-white;
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
@apply tw-m-auto tw-w-120 tw-bg-white tw-shadow-modal;
}
.disable-cta * {

View File

@ -11,11 +11,12 @@ export const getOidcExpiry = () => {
export const getUserManagerConfig = (
authClient: Record<string, string> = {}
): Record<string, string | WebStorageStateStore> => {
): Record<string, string | boolean | WebStorageStateStore> => {
const { authority, clientId, callbackUrl } = authClient;
return {
authority,
automaticSilentRenew: true,
// eslint-disable-next-line @typescript-eslint/camelcase
client_id: clientId,
// eslint-disable-next-line @typescript-eslint/camelcase

View File

@ -22,6 +22,8 @@ const primary = '#7147E8';
const primaryHover = '#5523E0';
const primaryActive = '#450DE2';
const primaryHoverLite = '#DBD1F9';
const secondary = '#B02AAC';
const secondaryBG = '#B02AAC40';
// state colors
const success = '#51C41A';
@ -66,6 +68,9 @@ module.exports = {
focus: primary,
search: '#D5D6D9',
},
boxShadow: {
modal: '1px 1px 5px rgba(0, 0, 0, 0.2)',
},
colors: {
'grey-body': textBody,
'grey-muted': textMuted,
@ -76,6 +81,8 @@ module.exports = {
'primary-hover': primaryHover,
'primary-active': primaryActive,
'primary-hover-lite': primaryHoverLite,
secondary: secondary,
'secondary-lite': secondaryBG,
'body-main': bodyBG,
'body-hover': bodyHoverBG,
tag: tagBG,

View File

@ -80,7 +80,7 @@ module.exports = {
},
//
{
test: /\.(png|jpg|gif|svg|ico)$/i,
test: /\.(png|jpg|jpeg|gif|svg|ico)$/i,
use: [
{
loader: 'url-loader',

View File

@ -81,7 +81,7 @@ module.exports = {
},
//
{
test: /\.(png|jpg|gif|svg|ico)$/i,
test: /\.(png|jpg|jpeg|gif|svg|ico)$/i,
use: [
{
loader: 'url-loader',