mirror of
https://github.com/mattermost/focalboard.git
synced 2024-11-27 08:31:20 +02:00
Start Welcome Page (#1231)
* start welcome page * setup template * setup forwarding logic * fix linting errors * Updating UI * Use intl strings * Address comments * go to baords welcome * Fix image problem * fix linting * fix getting shown wrong things * fix build issues * fix cypress test and non plugin * Fix bugs * remove console * Add welcome page * address comments Co-authored-by: Asaad Mahmood <asaadmahmood@users.noreply.github.com>
This commit is contained in:
parent
cbfb4cb36f
commit
7fae65ea02
@ -1,9 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import React, {useEffect} from 'react'
|
||||
import {Store, Action} from 'redux'
|
||||
import {Provider as ReduxProvider} from 'react-redux'
|
||||
import {useHistory} from 'mm-react-router-dom'
|
||||
|
||||
import {rudderAnalytics, RudderTelemetryHandler} from 'mattermost-redux/client/rudder'
|
||||
|
||||
@ -142,7 +141,7 @@ export default class Plugin {
|
||||
window.open(`${windowAny.frontendBaseURL}/workspace/${currentChannel}`, '_blank', 'noopener')
|
||||
}
|
||||
this.channelHeaderButtonId = registry.registerChannelHeaderButtonAction(<FocalboardIcon/>, goToFocalboardWorkspace, '', 'Boards')
|
||||
this.registry.registerProduct('/boards', 'product-boards', 'Boards', '/boards/dashboard', MainApp, HeaderComponent)
|
||||
this.registry.registerProduct('/boards', 'product-boards', 'Boards', '/boards/welcome', MainApp, HeaderComponent)
|
||||
} else {
|
||||
windowAny.frontendBaseURL = subpath + '/plug/focalboard'
|
||||
this.channelHeaderButtonId = registry.registerChannelHeaderButtonAction(<FocalboardIcon/>, () => {
|
||||
|
@ -14,6 +14,7 @@ describe('Create and delete board / card', () => {
|
||||
localStorage.setItem('focalboardSessionId', 'TESTTOKEN');
|
||||
localStorage.setItem('language', 'en');
|
||||
cy.expect(localStorage.getItem('focalboardSessionId')).to.eq('TESTTOKEN');
|
||||
localStorage.setItem('welcomePageViewed', 'true');
|
||||
});
|
||||
|
||||
it('Can create and delete a board and card', () => {
|
||||
|
@ -35,11 +35,10 @@
|
||||
"ContentBlock.moveDown": "Move down",
|
||||
"ContentBlock.moveUp": "Move up",
|
||||
"ContentBlock.text": "text",
|
||||
"DashboardPage.message": "Use Focalboard to create and track tasks for projects big and small using familiar Kanban boards, tables, and other views.",
|
||||
"DashboardPage.title": "Dashboard",
|
||||
"DashboardPage.CenterPanel.NoWorkspaces": "Sorry, we couldn't find any channels matching that term.",
|
||||
"DashboardPage.CenterPanel.NoWorkspacesDescription": "Please try searching for another term.",
|
||||
"DashboardPage.CenterPanel.ChangeChannels": "Use the switcher to easily change channels",
|
||||
"DashboardPage.CenterPanel.NoWorkspaces": "Sorry, we could not find any channels matching that term",
|
||||
"DashboardPage.CenterPanel.NoWorkspacesDescription": "Please try searching for another term",
|
||||
"DashboardPage.title": "Dashboard",
|
||||
"Dialog.closeDialog": "Close dialog",
|
||||
"EditableDayPicker.today": "Today",
|
||||
"EmptyCenterPanel.no-content": "Add or select a board from the sidebar to get started.",
|
||||
@ -168,6 +167,9 @@
|
||||
"ViewTitle.remove-icon": "Remove icon",
|
||||
"ViewTitle.show-description": "show description",
|
||||
"ViewTitle.untitled-board": "Untitled board",
|
||||
"WelcomePage.Description": "Boards is a project management tool that helps define, organize, track and manage work across teams, using a familiar kanban board view",
|
||||
"WelcomePage.Explore.Button": "Explore",
|
||||
"WelcomePage.Heading": "Welcome To Boards",
|
||||
"Workspace.editing-board-template": "You're editing a board template.",
|
||||
"default-properties.title": "Title",
|
||||
"error.no-workspace": "Your session may have expired or you may not have access to this workspace.",
|
||||
@ -177,4 +179,4 @@
|
||||
"login.register-button": "or create an account if you don't have one",
|
||||
"register.login-button": "or log in if you already have an account",
|
||||
"register.signup-title": "Sign up for your account"
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import {FlashMessages} from './components/flashMessages'
|
||||
import BoardPage from './pages/boardPage'
|
||||
import ChangePasswordPage from './pages/changePasswordPage'
|
||||
import DashboardPage from './pages/dashboard/dashboardPage'
|
||||
import WelcomePage from './pages/welcome/welcomePage'
|
||||
import ErrorPage from './pages/errorPage'
|
||||
import LoginPage from './pages/loginPage'
|
||||
import RegisterPage from './pages/registerPage'
|
||||
@ -33,9 +34,12 @@ import {useAppSelector, useAppDispatch} from './store/hooks'
|
||||
import {fetchClientConfig} from './store/clientConfig'
|
||||
|
||||
import {IUser} from './user'
|
||||
import {UserSettings} from './userSettings'
|
||||
|
||||
export const history = createBrowserHistory({basename: Utils.getFrontendBaseURL()})
|
||||
|
||||
const UUID_REGEX = new RegExp(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)
|
||||
|
||||
if (Utils.isDesktop() && Utils.isFocalboardPlugin()) {
|
||||
window.addEventListener('message', (event: MessageEvent) => {
|
||||
if (event.origin !== window.location.origin) {
|
||||
@ -102,6 +106,32 @@ const App = React.memo((): JSX.Element => {
|
||||
setTimeout(() => dispatch(setGlobalError('')), 0)
|
||||
}
|
||||
|
||||
const continueToWelcomeScreen = (boardIdIsValidUUIDV4 = true) => {
|
||||
return Utils.isFocalboardPlugin() && loggedIn === true && (!UserSettings.welcomePageViewed || !boardIdIsValidUUIDV4)
|
||||
}
|
||||
|
||||
const buildOriginalPath = (workspaceId = '', boardId = '', viewId = '', cardId = '') => {
|
||||
let originalPath = ''
|
||||
|
||||
if (workspaceId) {
|
||||
originalPath += `${workspaceId}/`
|
||||
}
|
||||
|
||||
if (boardId) {
|
||||
originalPath += `${boardId}/`
|
||||
}
|
||||
|
||||
if (viewId) {
|
||||
originalPath += `${viewId}/`
|
||||
}
|
||||
|
||||
if (cardId) {
|
||||
originalPath += `${cardId}/`
|
||||
}
|
||||
|
||||
return originalPath
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={language.split(/[_]/)[0]}
|
||||
@ -131,24 +161,44 @@ const App = React.memo((): JSX.Element => {
|
||||
<Route path='/shared/:boardId?/:viewId?'>
|
||||
<BoardPage readonly={true}/>
|
||||
</Route>
|
||||
<Route path='/board/:boardId?/:viewId?/:cardId?'>
|
||||
{loggedIn === false && <Redirect to='/login'/>}
|
||||
{loggedIn === true && <BoardPage/>}
|
||||
</Route>
|
||||
<Route
|
||||
path='/board/:boardId?/:viewId?/:cardId?'
|
||||
render={({match: {params: {boardId, viewId, cardId}}}) => {
|
||||
if (loggedIn === false) {
|
||||
return <Redirect to='/login'/>
|
||||
}
|
||||
|
||||
if (continueToWelcomeScreen()) {
|
||||
const originalPath = `/board/${buildOriginalPath('', boardId, viewId, cardId)}`
|
||||
return <Redirect to={`/welcome?r=${originalPath}`}/>
|
||||
}
|
||||
|
||||
if (loggedIn === true) {
|
||||
return <BoardPage/>
|
||||
}
|
||||
|
||||
return null
|
||||
}}
|
||||
/>
|
||||
<Route path='/workspace/:workspaceId/shared/:boardId?/:viewId?'>
|
||||
<BoardPage readonly={true}/>
|
||||
</Route>
|
||||
<Route
|
||||
path='/workspace/:workspaceId/:boardId?/:viewId?/:cardId?'
|
||||
render={({match}) => {
|
||||
render={({match: {params: {workspaceId, boardId, viewId, cardId}}}) => {
|
||||
const originalPath = `/workspace/${buildOriginalPath(workspaceId, boardId, viewId, cardId)}`
|
||||
if (loggedIn === false) {
|
||||
let redirectUrl = '/' + Utils.buildURL(`/workspace/${match.params.workspaceId}/`)
|
||||
let redirectUrl = '/' + Utils.buildURL(originalPath)
|
||||
if (redirectUrl.indexOf('//') === 0) {
|
||||
redirectUrl = redirectUrl.slice(1)
|
||||
}
|
||||
const loginUrl = `/login?r=${encodeURIComponent(redirectUrl)}`
|
||||
return <Redirect to={loginUrl}/>
|
||||
} else if (loggedIn === true) {
|
||||
if (continueToWelcomeScreen()) {
|
||||
return <Redirect to={`/welcome?r=${originalPath}`}/>
|
||||
}
|
||||
|
||||
return (
|
||||
<BoardPage/>
|
||||
)
|
||||
@ -162,10 +212,38 @@ const App = React.memo((): JSX.Element => {
|
||||
>
|
||||
<DashboardPage/>
|
||||
</Route>
|
||||
<Route path='/:boardId?/:viewId?/:cardId?'>
|
||||
{loggedIn === false && <Redirect to='/login'/>}
|
||||
{loggedIn === true && <BoardPage/>}
|
||||
<Route
|
||||
exact={true}
|
||||
path='/welcome'
|
||||
>
|
||||
<WelcomePage/>
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
path='/:boardId?/:viewId?/:cardId?'
|
||||
render={({match: {params: {boardId, viewId, cardId}}}) => {
|
||||
// Since these 3 path values are optional and they can be anything, we can pass /x/y/z and it will
|
||||
// match this route however these values may not be valid so we should at the very least check
|
||||
// board id for descisions made below
|
||||
const boardIdIsValidUUIDV4 = UUID_REGEX.test(boardId || '')
|
||||
|
||||
if (loggedIn === false) {
|
||||
return <Redirect to='/login'/>
|
||||
}
|
||||
|
||||
if (continueToWelcomeScreen(boardIdIsValidUUIDV4)) {
|
||||
const originalPath = `/${buildOriginalPath('', boardId, viewId, cardId)}`
|
||||
const queryString = boardIdIsValidUUIDV4 ? `r=${originalPath}` : ''
|
||||
return <Redirect to={`/welcome?${queryString}`}/>
|
||||
}
|
||||
|
||||
if (loggedIn === true) {
|
||||
return <BoardPage/>
|
||||
}
|
||||
|
||||
return null
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -45,7 +45,7 @@ const BoardPage = (props: Props) => {
|
||||
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, workspaceId?: string}>()
|
||||
const [websocketClosed, setWebsocketClosed] = useState(false)
|
||||
|
||||
let workspaceId = UserSettings.lastWorkspaceId || '0'
|
||||
let workspaceId = match.params.workspaceId || UserSettings.lastWorkspaceId || '0'
|
||||
|
||||
// TODO: Make this less brittle. This only works because this is the root render function
|
||||
useEffect(() => {
|
||||
|
22
webapp/src/pages/welcome/welcomePage.scss
Normal file
22
webapp/src/pages/welcome/welcomePage.scss
Normal file
@ -0,0 +1,22 @@
|
||||
.WelcomePage {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
padding: 0 40px;
|
||||
text-align: center;
|
||||
|
||||
> div {
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.Button {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
.WelcomePage__image {
|
||||
margin: 32px 0;
|
||||
text-align: center;
|
||||
}
|
76
webapp/src/pages/welcome/welcomePage.tsx
Normal file
76
webapp/src/pages/welcome/welcomePage.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
import {FormattedMessage} from 'react-intl'
|
||||
|
||||
import {useLocation, useHistory} from 'react-router-dom'
|
||||
|
||||
import BoardWelcomePNG from '../../../static/boards-welcome.png'
|
||||
|
||||
import CompassIcon from '../../widgets/icons/compassIcon'
|
||||
import {UserSettings} from '../../userSettings'
|
||||
|
||||
import './welcomePage.scss'
|
||||
|
||||
const WelcomePage = React.memo(() => {
|
||||
const history = useHistory()
|
||||
const queryString = new URLSearchParams(useLocation().search)
|
||||
|
||||
const goForward = () => {
|
||||
UserSettings.welcomePageViewed = 'true'
|
||||
|
||||
if (queryString.get('r')) {
|
||||
history.replace(queryString.get('r')!)
|
||||
return
|
||||
}
|
||||
|
||||
history.replace('/dashboard')
|
||||
}
|
||||
|
||||
if (UserSettings.welcomePageViewed) {
|
||||
goForward()
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='WelcomePage'>
|
||||
<div>
|
||||
<h1 className='text-heading6'>
|
||||
<FormattedMessage
|
||||
id='WelcomePage.Heading'
|
||||
defaultMessage='Welcome To Boards'
|
||||
/>
|
||||
</h1>
|
||||
<div className='text-base'>
|
||||
<FormattedMessage
|
||||
id='WelcomePage.Description'
|
||||
defaultMessage='Boards is a project management tool that helps define, organize, track and manage work across teams, using a familiar kanban board view'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='WelcomePage__image'>
|
||||
<img
|
||||
src={BoardWelcomePNG}
|
||||
alt='Boards Welcome Image'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={goForward}
|
||||
className='Button filled size--large'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='WelcomePage.Explore.Button'
|
||||
defaultMessage='Explore'
|
||||
/>
|
||||
<CompassIcon
|
||||
icon='chevron-right'
|
||||
className='Icon Icon--right'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default WelcomePage
|
7
webapp/src/types/images.d.ts
vendored
Normal file
7
webapp/src/types/images.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
declare module '*.jpg';
|
||||
declare module '*.png';
|
||||
declare module '*.gif';
|
||||
declare module '*.apng';
|
@ -14,7 +14,8 @@ enum UserSettingKey {
|
||||
EmojiMartSkin = 'emoji-mart.skin',
|
||||
EmojiMartLast = 'emoji-mart.last',
|
||||
EmojiMartFrequently = 'emoji-mart.frequently',
|
||||
RandomIcons = 'randomIcons'
|
||||
RandomIcons = 'randomIcons',
|
||||
WelcomePageViewed = 'welcomePageViewed'
|
||||
}
|
||||
|
||||
export class UserSettings {
|
||||
@ -42,6 +43,14 @@ export class UserSettings {
|
||||
UserSettings.set(UserSettingKey.Language, newValue)
|
||||
}
|
||||
|
||||
static get welcomePageViewed(): string | null {
|
||||
return UserSettings.get(UserSettingKey.WelcomePageViewed)
|
||||
}
|
||||
|
||||
static set welcomePageViewed(newValue: string | null) {
|
||||
UserSettings.set(UserSettingKey.WelcomePageViewed, newValue)
|
||||
}
|
||||
|
||||
static get theme(): string | null {
|
||||
return UserSettings.get(UserSettingKey.Theme)
|
||||
}
|
||||
|
@ -20,6 +20,21 @@
|
||||
background-color: rgba(var(--center-channel-color-rgb), 0.08);
|
||||
}
|
||||
|
||||
.Icon {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 14px;
|
||||
justify-content: center;
|
||||
margin-right: 0;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.Icon--right {
|
||||
height: 14px;
|
||||
margin-left: 4px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
&.filled {
|
||||
color: #fff;
|
||||
background-color: rgb(var(--button-bg-rgb));
|
||||
@ -56,9 +71,17 @@
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.Icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 0;
|
||||
&.size--large {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
height: 48px;
|
||||
padding: 0 24px;
|
||||
|
||||
.Icon--right {
|
||||
font-size: 24px;
|
||||
height: 20px;
|
||||
margin-left: 10px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
webapp/static/boards-welcome.png
Normal file
BIN
webapp/static/boards-welcome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
@ -42,7 +42,7 @@ function makeCommonConfig() {
|
||||
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
test: /\.(png|jpg|jpeg|gif|html)$/,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
{
|
||||
@ -67,7 +67,7 @@ function makeCommonConfig() {
|
||||
exclude: [/node_modules/],
|
||||
},
|
||||
{
|
||||
test: /\.(png|eot|tiff|svg|woff2|woff|ttf|jpg)$/,
|
||||
test: /\.(eot|tiff|svg|woff2|woff|ttf)$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
|
Loading…
Reference in New Issue
Block a user