Skip to content

Commit

Permalink
Disable Link Sharing (#1475)
Browse files Browse the repository at this point in the history
* Add backend api

* Run black

* Add Switch button to frontend

* Move switch down

* Add hook to disable signup link

* Run black with line width 120

* update conditional check

* send request on switch

* Update TeamInvitation to use api instead of fetch

* Convert signup_token to signup_state

* Remove unused import and stray comment

* Improve code and UX

* Reword notice

* Add invite link revoke popconfirm

* Update signup_token test

* Improve Popconfirm style

* Make TeamInvitationContent work with Modal

Co-authored-by: Michael Matloka <[email protected]>
  • Loading branch information
J0 and Twixes authored Aug 25, 2020
1 parent ba4a752 commit 03a42f0
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 22 deletions.
14 changes: 10 additions & 4 deletions frontend/src/lib/components/CopyToClipboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { toast } from 'react-toastify'
import { Tooltip, Input } from 'antd'
import { CopyOutlined } from '@ant-design/icons'

export function CopyToClipboard({ url, ...props }) {
export function CopyToClipboard({ url, placeholder, addonBefore, addonAfter, ...props }) {
const urlRef = useRef()

function copyToClipboard() {
Expand All @@ -19,11 +19,17 @@ export function CopyToClipboard({ url, ...props }) {
type="text"
ref={urlRef}
value={url}
placeholder={placeholder || 'nothing to show here'}
disabled={!url}
suffix={
<Tooltip title="Copy to Clipboard">
<CopyOutlined onClick={copyToClipboard} />
</Tooltip>
url ? (
<Tooltip title="Copy to Clipboard">
<CopyOutlined onClick={copyToClipboard} />
</Tooltip>
) : null
}
addonBefore={addonBefore}
addonAfter={addonAfter}
{...props}
/>
)
Expand Down
61 changes: 45 additions & 16 deletions frontend/src/lib/components/TeamInvitation.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,51 @@
import React from 'react'
import { Modal } from 'antd'
import { Modal, Switch, Popconfirm } from 'antd'
import { CopyToClipboard } from 'lib/components/CopyToClipboard'
import { useActions } from 'kea'
import { userLogic } from 'scenes/userLogic'
import { InfoCircleOutlined } from '@ant-design/icons'
import { red } from '@ant-design/colors'

export function TeamInvitationLink({ user }) {
return (
<CopyToClipboard
data-attr="copy-invite-to-clipboard-input"
url={window.location.origin + '/signup/' + user.team.signup_token}
/>
)
}
export function TeamInvitationContent({ user, confirmRevocation = true }) {
const { userUpdateRequest } = useActions(userLogic)
const isSignupEnabled = Boolean(user.team.signup_token)
const confirmChange = confirmRevocation && isSignupEnabled

export function TeamInvitationContent({ user }) {
return (
<div>
<p>
<TeamInvitationLink user={user} />
<CopyToClipboard
data-attr="copy-invite-to-clipboard-input"
url={isSignupEnabled ? window.location.origin + '/signup/' + user.team.signup_token : null}
placeholder="disabled and revoked – switch on to generate a new link"
addonBefore="Team Invite Link"
addonAfter={
<Popconfirm
title="Revoke current link globally?"
okText="Revoke"
okType="danger"
icon={<InfoCircleOutlined style={{ color: red.primary }} />}
onConfirm={() => {
userUpdateRequest({ team: { signup_state: false } }, 'team.signup_state')
}}
disabled={!confirmChange}
>
<Switch
size="small"
checked={isSignupEnabled}
onChange={() => {
if (!confirmChange)
userUpdateRequest(
{ team: { signup_state: !isSignupEnabled } },
'team.signup_state'
)
}}
/>
</Popconfirm>
}
/>
</p>
Invite teammates with the link above.
<br />
Build an even better product, <i>together</i>.
Build an even better product <i>together</i>.
</div>
)
}
Expand All @@ -28,8 +54,11 @@ export function TeamInvitationModal({ user, visible, onCancel }) {
return (
<Modal visible={visible} footer={null} onCancel={onCancel}>
<div data-attr="invite-team-modal">
<h2>Team Invitation</h2>
<TeamInvitationContent user={user} />
<h2>Invite Teammate</h2>
<TeamInvitationContent
user={user}
confirmRevocation={false /* Popconfirm doesn't show up properly in Modal */}
/>
</div>
</Modal>
)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/team/Team.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function Team({ user }) {
return (
<>
<h1 className="page-header">Team</h1>
<div style={{ maxWidth: 600 }}>
<div style={{ maxWidth: 672 }}>
<i>
<p>This is you and all your teammates. Manage them from here.</p>
<TeamInvitationContent user={user} />
Expand Down
23 changes: 22 additions & 1 deletion posthog/api/test/test_user.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from unittest.mock import patch
from posthog.models import Team, User

from .base import BaseTest


Expand Down Expand Up @@ -40,6 +40,27 @@ def test_user_team_update(self):
self.assertEqual(team.opt_out_capture, True)
self.assertEqual(team.anonymize_ips, False)

@patch("secrets.token_urlsafe")
def test_user_team_update_signup_token(self, patch_token):
patch_token.return_value = "abcde"
response = self.client.patch(
"/api/user/", data={"team": {"signup_state": False}}, content_type="application/json",
).json()

self.assertEqual(response["team"]["signup_token"], None)

team = Team.objects.get(id=self.team.id)
self.assertEqual(team.signup_token, None)

response = self.client.patch(
"/api/user/", data={"team": {"signup_state": True}}, content_type="application/json",
).json()

self.assertEqual(response["team"]["signup_token"], "abcde")

team = Team.objects.get(id=self.team.id)
self.assertEqual(team.signup_token, "abcde")


class TestUserChangePassword(BaseTest):
TESTS_API = True
Expand Down
6 changes: 6 additions & 0 deletions posthog/api/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ def user(request):
team.completed_snippet_onboarding = data["team"].get(
"completed_snippet_onboarding", team.completed_snippet_onboarding,
)
# regenerate or disable team signup link
signup_state = data["team"].get("signup_state")
if signup_state == True:
team.signup_token = secrets.token_urlsafe(22)
elif signup_state == False:
team.signup_token = None
team.save()

if "user" in data:
Expand Down

0 comments on commit 03a42f0

Please sign in to comment.