Merge pull request #2636 from mattermost/private-templates
Added support for access control on templates
This commit is contained in:
commit
36bf5704d0
14 changed files with 75 additions and 35 deletions
|
@ -2188,7 +2188,7 @@ func (a *API) handleGetTemplates(w http.ResponseWriter, r *http.Request) {
|
||||||
auditRec.AddMeta("teamID", teamID)
|
auditRec.AddMeta("teamID", teamID)
|
||||||
|
|
||||||
// retrieve boards list
|
// retrieve boards list
|
||||||
boards, err := a.app.GetTemplateBoards(teamID)
|
boards, err := a.app.GetTemplateBoards(teamID, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -150,8 +150,8 @@ func (a *App) GetBoardsForUserAndTeam(userID, teamID string) ([]*model.Board, er
|
||||||
return a.store.GetBoardsForUserAndTeam(userID, teamID)
|
return a.store.GetBoardsForUserAndTeam(userID, teamID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetTemplateBoards(teamID string) ([]*model.Board, error) {
|
func (a *App) GetTemplateBoards(teamID, userID string) ([]*model.Board, error) {
|
||||||
return a.store.GetTemplateBoards(teamID)
|
return a.store.GetTemplateBoards(teamID, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) CreateBoard(board *model.Board, userID string, addMember bool) (*model.Board, error) {
|
func (a *App) CreateBoard(board *model.Board, userID string, addMember bool) (*model.Board, error) {
|
||||||
|
|
|
@ -46,14 +46,14 @@ func (a *App) PrepareOnboardingTour(userID string, teamID string) (string, strin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) getOnboardingBoardID() (string, error) {
|
func (a *App) getOnboardingBoardID() (string, error) {
|
||||||
boards, err := a.store.GetTemplateBoards(globalTeamID)
|
boards, err := a.store.GetTemplateBoards(globalTeamID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var onboardingBoardID string
|
var onboardingBoardID string
|
||||||
for _, block := range boards {
|
for _, block := range boards {
|
||||||
if block.Title == WelcomeBoardTitle {
|
if block.Title == WelcomeBoardTitle && block.TeamID == globalTeamID {
|
||||||
onboardingBoardID = block.ID
|
onboardingBoardID = block.ID
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ func TestPrepareOnboardingTour(t *testing.T) {
|
||||||
IsTemplate: true,
|
IsTemplate: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{&welcomeBoard}, nil)
|
th.Store.EXPECT().GetTemplateBoards("0", "").Return([]*model.Board{&welcomeBoard}, nil)
|
||||||
th.Store.EXPECT().DuplicateBoard(welcomeBoard.ID, userID, teamID, false).Return(&model.BoardsAndBlocks{Boards: []*model.Board{&welcomeBoard}},
|
th.Store.EXPECT().DuplicateBoard(welcomeBoard.ID, userID, teamID, false).Return(&model.BoardsAndBlocks{Boards: []*model.Board{&welcomeBoard}},
|
||||||
nil, nil)
|
nil, nil)
|
||||||
th.Store.EXPECT().GetMembersForBoard(welcomeBoard.ID).Return([]*model.BoardMember{}, nil)
|
th.Store.EXPECT().GetMembersForBoard(welcomeBoard.ID).Return([]*model.BoardMember{}, nil)
|
||||||
|
@ -60,7 +60,7 @@ func TestCreateWelcomeBoard(t *testing.T) {
|
||||||
TeamID: "0",
|
TeamID: "0",
|
||||||
IsTemplate: true,
|
IsTemplate: true,
|
||||||
}
|
}
|
||||||
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{&welcomeBoard}, nil)
|
th.Store.EXPECT().GetTemplateBoards("0", "").Return([]*model.Board{&welcomeBoard}, nil)
|
||||||
th.Store.EXPECT().DuplicateBoard(welcomeBoard.ID, userID, teamID, false).
|
th.Store.EXPECT().DuplicateBoard(welcomeBoard.ID, userID, teamID, false).
|
||||||
Return(&model.BoardsAndBlocks{Boards: []*model.Board{&welcomeBoard}}, nil, nil)
|
Return(&model.BoardsAndBlocks{Boards: []*model.Board{&welcomeBoard}}, nil, nil)
|
||||||
th.Store.EXPECT().GetMembersForBoard(welcomeBoard.ID).Return([]*model.BoardMember{}, nil)
|
th.Store.EXPECT().GetMembersForBoard(welcomeBoard.ID).Return([]*model.BoardMember{}, nil)
|
||||||
|
@ -72,7 +72,7 @@ func TestCreateWelcomeBoard(t *testing.T) {
|
||||||
|
|
||||||
t.Run("template doesn't contain a board", func(t *testing.T) {
|
t.Run("template doesn't contain a board", func(t *testing.T) {
|
||||||
teamID := testTeamID
|
teamID := testTeamID
|
||||||
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{}, nil)
|
th.Store.EXPECT().GetTemplateBoards("0", "").Return([]*model.Board{}, nil)
|
||||||
boardID, err := th.App.createWelcomeBoard("user_id_1", teamID)
|
boardID, err := th.App.createWelcomeBoard("user_id_1", teamID)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Empty(t, boardID)
|
assert.Empty(t, boardID)
|
||||||
|
@ -86,7 +86,7 @@ func TestCreateWelcomeBoard(t *testing.T) {
|
||||||
TeamID: teamID,
|
TeamID: teamID,
|
||||||
IsTemplate: true,
|
IsTemplate: true,
|
||||||
}
|
}
|
||||||
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{&welcomeBoard}, nil)
|
th.Store.EXPECT().GetTemplateBoards("0", "").Return([]*model.Board{&welcomeBoard}, nil)
|
||||||
boardID, err := th.App.createWelcomeBoard("user_id_1", "workspace_id_1")
|
boardID, err := th.App.createWelcomeBoard("user_id_1", "workspace_id_1")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Empty(t, boardID)
|
assert.Empty(t, boardID)
|
||||||
|
@ -104,7 +104,7 @@ func TestGetOnboardingBoardID(t *testing.T) {
|
||||||
TeamID: "0",
|
TeamID: "0",
|
||||||
IsTemplate: true,
|
IsTemplate: true,
|
||||||
}
|
}
|
||||||
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{&welcomeBoard}, nil)
|
th.Store.EXPECT().GetTemplateBoards("0", "").Return([]*model.Board{&welcomeBoard}, nil)
|
||||||
|
|
||||||
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -112,7 +112,7 @@ func TestGetOnboardingBoardID(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no blocks found", func(t *testing.T) {
|
t.Run("no blocks found", func(t *testing.T) {
|
||||||
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{}, nil)
|
th.Store.EXPECT().GetTemplateBoards("0", "").Return([]*model.Board{}, nil)
|
||||||
|
|
||||||
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
@ -126,7 +126,7 @@ func TestGetOnboardingBoardID(t *testing.T) {
|
||||||
TeamID: "0",
|
TeamID: "0",
|
||||||
IsTemplate: true,
|
IsTemplate: true,
|
||||||
}
|
}
|
||||||
th.Store.EXPECT().GetTemplateBoards("0").Return([]*model.Board{&welcomeBoard}, nil)
|
th.Store.EXPECT().GetTemplateBoards("0", "").Return([]*model.Board{&welcomeBoard}, nil)
|
||||||
|
|
||||||
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
onboardingBoardID, err := th.App.getOnboardingBoardID()
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|
|
@ -23,7 +23,7 @@ func (a *App) InitTemplates() error {
|
||||||
|
|
||||||
// initializeTemplates imports default templates if the boards table is empty.
|
// initializeTemplates imports default templates if the boards table is empty.
|
||||||
func (a *App) initializeTemplates() (bool, error) {
|
func (a *App) initializeTemplates() (bool, error) {
|
||||||
boards, err := a.store.GetTemplateBoards(globalTeamID)
|
boards, err := a.store.GetTemplateBoards(globalTeamID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("cannot initialize templates: %w", err)
|
return false, fmt.Errorf("cannot initialize templates: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func TestApp_initializeTemplates(t *testing.T) {
|
||||||
th, tearDown := SetupTestHelper(t)
|
th, tearDown := SetupTestHelper(t)
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
th.Store.EXPECT().GetTemplateBoards(globalTeamID).Return([]*model.Board{}, nil)
|
th.Store.EXPECT().GetTemplateBoards(globalTeamID, "").Return([]*model.Board{}, nil)
|
||||||
th.Store.EXPECT().RemoveDefaultTemplates([]*model.Board{}).Return(nil)
|
th.Store.EXPECT().RemoveDefaultTemplates([]*model.Board{}).Return(nil)
|
||||||
th.Store.EXPECT().CreateBoardsAndBlocks(gomock.Any(), gomock.Any()).AnyTimes().Return(boardsAndBlocks, nil)
|
th.Store.EXPECT().CreateBoardsAndBlocks(gomock.Any(), gomock.Any()).AnyTimes().Return(boardsAndBlocks, nil)
|
||||||
th.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{}, nil)
|
th.Store.EXPECT().GetMembersForBoard(board.ID).AnyTimes().Return([]*model.BoardMember{}, nil)
|
||||||
|
@ -54,7 +54,7 @@ func TestApp_initializeTemplates(t *testing.T) {
|
||||||
th, tearDown := SetupTestHelper(t)
|
th, tearDown := SetupTestHelper(t)
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
|
|
||||||
th.Store.EXPECT().GetTemplateBoards(globalTeamID).Return([]*model.Board{board}, nil)
|
th.Store.EXPECT().GetTemplateBoards(globalTeamID, "").Return([]*model.Board{board}, nil)
|
||||||
|
|
||||||
done, err := th.App.initializeTemplates()
|
done, err := th.App.initializeTemplates()
|
||||||
require.NoError(t, err, "initializeTemplates should not error")
|
require.NoError(t, err, "initializeTemplates should not error")
|
||||||
|
|
|
@ -42,7 +42,7 @@ func setupTestHelper(t *testing.T) *TestHelper {
|
||||||
newAuth := New(&cfg, mockStore, localpermissions.New(mockPermissions, logger))
|
newAuth := New(&cfg, mockStore, localpermissions.New(mockPermissions, logger))
|
||||||
|
|
||||||
// called during default template setup for every test
|
// called during default template setup for every test
|
||||||
mockStore.EXPECT().GetTemplateBoards(gomock.Any()).AnyTimes()
|
mockStore.EXPECT().GetTemplateBoards("0", "").AnyTimes()
|
||||||
mockStore.EXPECT().RemoveDefaultTemplates(gomock.Any()).AnyTimes()
|
mockStore.EXPECT().RemoveDefaultTemplates(gomock.Any()).AnyTimes()
|
||||||
mockStore.EXPECT().InsertBlock(gomock.Any(), gomock.Any()).AnyTimes()
|
mockStore.EXPECT().InsertBlock(gomock.Any(), gomock.Any()).AnyTimes()
|
||||||
|
|
||||||
|
|
|
@ -881,18 +881,18 @@ func (mr *MockStoreMockRecorder) GetTeamsForUser(arg0 interface{}) *gomock.Call
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTemplateBoards mocks base method.
|
// GetTemplateBoards mocks base method.
|
||||||
func (m *MockStore) GetTemplateBoards(arg0 string) ([]*model.Board, error) {
|
func (m *MockStore) GetTemplateBoards(arg0, arg1 string) ([]*model.Board, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetTemplateBoards", arg0)
|
ret := m.ctrl.Call(m, "GetTemplateBoards", arg0, arg1)
|
||||||
ret0, _ := ret[0].([]*model.Board)
|
ret0, _ := ret[0].([]*model.Board)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTemplateBoards indicates an expected call of GetTemplateBoards.
|
// GetTemplateBoards indicates an expected call of GetTemplateBoards.
|
||||||
func (mr *MockStoreMockRecorder) GetTemplateBoards(arg0 interface{}) *gomock.Call {
|
func (mr *MockStoreMockRecorder) GetTemplateBoards(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateBoards", reflect.TypeOf((*MockStore)(nil).GetTemplateBoards), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateBoards", reflect.TypeOf((*MockStore)(nil).GetTemplateBoards), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail mocks base method.
|
// GetUserByEmail mocks base method.
|
||||||
|
|
|
@ -435,8 +435,8 @@ func (s *SQLStore) GetTeamsForUser(userID string) ([]*model.Team, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLStore) GetTemplateBoards(teamID string) ([]*model.Board, error) {
|
func (s *SQLStore) GetTemplateBoards(teamID string, userID string) ([]*model.Board, error) {
|
||||||
return s.getTemplateBoards(s.db, teamID)
|
return s.getTemplateBoards(s.db, teamID, userID)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,13 +54,24 @@ func (s *SQLStore) removeDefaultTemplates(db sq.BaseRunner, boards []*model.Boar
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDefaultTemplateBoards fetches all template blocks .
|
// getTemplateBoards fetches all template boards .
|
||||||
func (s *SQLStore) getTemplateBoards(db sq.BaseRunner, teamID string) ([]*model.Board, error) {
|
func (s *SQLStore) getTemplateBoards(db sq.BaseRunner, teamID, userID string) ([]*model.Board, error) {
|
||||||
query := s.getQueryBuilder(db).
|
query := s.getQueryBuilder(db).
|
||||||
Select(boardFields("")...).
|
Select(boardFields("")...).
|
||||||
From(s.tablePrefix + "boards").
|
From(s.tablePrefix+"boards as b").
|
||||||
Where(sq.Eq{"coalesce(team_id, '0')": teamID}).
|
LeftJoin(s.tablePrefix+"board_members as bm on b.id = bm.board_id and bm.user_id = ?", userID).
|
||||||
Where(sq.Eq{"is_template": true})
|
Where(sq.Eq{"is_template": true}).
|
||||||
|
Where(sq.Eq{"b.team_id": teamID}).
|
||||||
|
Where(sq.Or{
|
||||||
|
// this is to include public templates even if there is not board_member entry
|
||||||
|
sq.And{
|
||||||
|
sq.Eq{"bm.board_id": nil},
|
||||||
|
sq.Eq{"b.type": model.BoardTypeOpen},
|
||||||
|
},
|
||||||
|
sq.And{
|
||||||
|
sq.NotEq{"bm.board_id": nil},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
rows, err := query.Query()
|
rows, err := query.Query()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -69,5 +80,10 @@ func (s *SQLStore) getTemplateBoards(db sq.BaseRunner, teamID string) ([]*model.
|
||||||
}
|
}
|
||||||
defer s.CloseRows(rows)
|
defer s.CloseRows(rows)
|
||||||
|
|
||||||
return s.boardsFromRows(rows)
|
userTemplates, err := s.boardsFromRows(rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return userTemplates, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ type Store interface {
|
||||||
GetNextNotificationHint(remove bool) (*model.NotificationHint, error)
|
GetNextNotificationHint(remove bool) (*model.NotificationHint, error)
|
||||||
|
|
||||||
RemoveDefaultTemplates(boards []*model.Board) error
|
RemoveDefaultTemplates(boards []*model.Board) error
|
||||||
GetTemplateBoards(teamID string) ([]*model.Board, error)
|
GetTemplateBoards(teamID, userID string) ([]*model.Board, error)
|
||||||
|
|
||||||
DBType() string
|
DBType() string
|
||||||
|
|
||||||
|
|
|
@ -181,6 +181,7 @@
|
||||||
"ShareBoard.tokenRegenrated": "Token regenerated",
|
"ShareBoard.tokenRegenrated": "Token regenerated",
|
||||||
"ShareBoard.userPermissionsRemoveMemberText": "Remove member",
|
"ShareBoard.userPermissionsRemoveMemberText": "Remove member",
|
||||||
"ShareBoard.userPermissionsYouText": "(You)",
|
"ShareBoard.userPermissionsYouText": "(You)",
|
||||||
|
"ShareTemplate.Title": "Share Template",
|
||||||
"Sidebar.about": "About Focalboard",
|
"Sidebar.about": "About Focalboard",
|
||||||
"Sidebar.add-board": "+ Add board",
|
"Sidebar.add-board": "+ Add board",
|
||||||
"Sidebar.changePassword": "Change password",
|
"Sidebar.changePassword": "Change password",
|
||||||
|
|
|
@ -212,5 +212,16 @@
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Menu {
|
||||||
|
position: fixed;
|
||||||
|
left: 55%;
|
||||||
|
right: calc(45% - 240px);
|
||||||
|
|
||||||
|
.menu-contents {
|
||||||
|
min-width: 240px;
|
||||||
|
max-width: 240px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Select from 'react-select/async'
|
||||||
import {CSSObject} from '@emotion/serialize'
|
import {CSSObject} from '@emotion/serialize'
|
||||||
|
|
||||||
import {useAppSelector} from '../../store/hooks'
|
import {useAppSelector} from '../../store/hooks'
|
||||||
import {getCurrentBoardId, getCurrentBoardMembers} from '../../store/boards'
|
import {getCurrentBoard, getCurrentBoardMembers} from '../../store/boards'
|
||||||
import {getMe, getBoardUsersList} from '../../store/users'
|
import {getMe, getBoardUsersList} from '../../store/users'
|
||||||
|
|
||||||
import {Utils, IDType} from '../../utils'
|
import {Utils, IDType} from '../../utils'
|
||||||
|
@ -95,7 +95,8 @@ export default function ShareBoardDialog(props: Props): JSX.Element {
|
||||||
|
|
||||||
// members of the current board
|
// members of the current board
|
||||||
const members = useAppSelector<{[key: string]: BoardMember}>(getCurrentBoardMembers)
|
const members = useAppSelector<{[key: string]: BoardMember}>(getCurrentBoardMembers)
|
||||||
const boardId = useAppSelector(getCurrentBoardId)
|
const board = useAppSelector(getCurrentBoard)
|
||||||
|
const boardId = board.id
|
||||||
const boardUsers = useAppSelector<IUser[]>(getBoardUsersList)
|
const boardUsers = useAppSelector<IUser[]>(getBoardUsersList)
|
||||||
const me = useAppSelector<IUser|null>(getMe)
|
const me = useAppSelector<IUser|null>(getMe)
|
||||||
|
|
||||||
|
@ -239,7 +240,7 @@ export default function ShareBoardDialog(props: Props): JSX.Element {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolbar = (
|
const shareBoardTitle = (
|
||||||
<span className='text-heading5'>
|
<span className='text-heading5'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={'ShareBoard.Title'}
|
id={'ShareBoard.Title'}
|
||||||
|
@ -248,6 +249,17 @@ export default function ShareBoardDialog(props: Props): JSX.Element {
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const shareTemplateTitle = (
|
||||||
|
<span className='text-heading5'>
|
||||||
|
<FormattedMessage
|
||||||
|
id={'ShareTemplate.Title'}
|
||||||
|
defaultMessage={'Share Template'}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
|
||||||
|
const toolbar = board.isTemplate ? shareTemplateTitle : shareBoardTitle
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
onClose={props.onClose}
|
onClose={props.onClose}
|
||||||
|
@ -299,7 +311,7 @@ export default function ShareBoardDialog(props: Props): JSX.Element {
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{props.enableSharedBoards && (
|
{props.enableSharedBoards && !board.isTemplate && (
|
||||||
<div className='tabs-container'>
|
<div className='tabs-container'>
|
||||||
<button
|
<button
|
||||||
onClick={() => setPublish(false)}
|
onClick={() => setPublish(false)}
|
||||||
|
@ -323,7 +335,7 @@ export default function ShareBoardDialog(props: Props): JSX.Element {
|
||||||
</BoardPermissionGate>
|
</BoardPermissionGate>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(props.enableSharedBoards && publish) &&
|
{(props.enableSharedBoards && publish && !board.isTemplate) &&
|
||||||
(<BoardPermissionGate permissions={[Permission.ShareBoard]}>
|
(<BoardPermissionGate permissions={[Permission.ShareBoard]}>
|
||||||
<div className='tabs-content'>
|
<div className='tabs-content'>
|
||||||
<div>
|
<div>
|
||||||
|
@ -401,7 +413,7 @@ export default function ShareBoardDialog(props: Props): JSX.Element {
|
||||||
</BoardPermissionGate>
|
</BoardPermissionGate>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!publish && (
|
{!publish && !board.isTemplate && (
|
||||||
<div className='tabs-content'>
|
<div className='tabs-content'>
|
||||||
<div>
|
<div>
|
||||||
<div className='d-flex justify-content-between'>
|
<div className='d-flex justify-content-between'>
|
||||||
|
|
Loading…
Reference in a new issue