diff --git a/cypress/e2e/signup.cy.ts b/cypress/e2e/signup.cy.ts index 4ef4036000378..8c660a69a93ff 100644 --- a/cypress/e2e/signup.cy.ts +++ b/cypress/e2e/signup.cy.ts @@ -12,7 +12,7 @@ describe('Signup', () => { cy.get('[data-attr=signup-email]').type('test@posthog.com').should('have.value', 'test@posthog.com') cy.get('[data-attr=password]').type('12345678').should('have.value', '12345678') cy.get('[data-attr=signup-start]').click() - cy.get('[data-attr=signup-first-name]').type('Jane').should('have.value', 'Jane') + cy.get('[data-attr=signup-name]').type('Jane Doe').should('have.value', 'Jane Doe') cy.get('[data-attr=signup-organization-name]').type('Hogflix Movies').should('have.value', 'Hogflix Movies') cy.get('[data-attr=signup-role-at-organization]').click() cy.get('.Popover li:first-child').click() @@ -39,18 +39,53 @@ describe('Signup', () => { cy.get('.text-danger').should('not.contain', 'Password must be at least 8 characters') // Validation error removed on keystroke }) - it('Can create user account', () => { + it('Can create user account with first name, last name and organization name', () => { + cy.intercept('POST', '/api/signup/').as('signupRequest') + const email = `new_user+${Math.floor(Math.random() * 10000)}@posthog.com` cy.get('[data-attr=signup-email]').type(email).should('have.value', email) cy.get('[data-attr=password]').type('12345678').should('have.value', '12345678') cy.get('[data-attr=signup-start]').click() - cy.get('[data-attr=signup-first-name]').type('Alice').should('have.value', 'Alice') + cy.get('[data-attr=signup-name]').type('Alice Bob').should('have.value', 'Alice Bob') cy.get('[data-attr=signup-organization-name]').type('Hogflix SpinOff').should('have.value', 'Hogflix SpinOff') cy.get('[data-attr=signup-role-at-organization]').click() cy.get('.Popover li:first-child').click() cy.get('[data-attr=signup-role-at-organization]').contains('Engineering') cy.get('[data-attr=signup-submit]').click() + cy.wait('@signupRequest').then((interception) => { + expect(interception.request.body).to.have.property('first_name') + expect(interception.request.body.first_name).to.equal('Alice') + expect(interception.request.body).to.have.property('last_name') + expect(interception.request.body.last_name).to.equal('Bob') + expect(interception.request.body).to.have.property('organization_name') + expect(interception.request.body.organization_name).to.equal('Hogflix SpinOff') + }) + + // lazy regex for a guid + cy.location('pathname').should('match', /\/verify_email\/[a-zA-Z0-9_.-]*/) + }) + + it('Can create user account with just a first name', () => { + cy.intercept('POST', '/api/signup/').as('signupRequest') + + const email = `new_user+${Math.floor(Math.random() * 10000)}@posthog.com` + cy.get('[data-attr=signup-email]').type(email).should('have.value', email) + cy.get('[data-attr=password]').type('12345678').should('have.value', '12345678') + cy.get('[data-attr=signup-start]').click() + cy.get('[data-attr=signup-name]').type('Alice').should('have.value', 'Alice') + cy.get('[data-attr=signup-role-at-organization]').click() + cy.get('.Popover li:first-child').click() + cy.get('[data-attr=signup-role-at-organization]').contains('Engineering') + cy.get('[data-attr=signup-submit]').click() + + cy.wait('@signupRequest').then((interception) => { + expect(interception.request.body).to.have.property('first_name') + expect(interception.request.body.first_name).to.equal('Alice') + expect(interception.request.body).to.not.have.property('last_name') + expect(interception.request.body).to.not.have.property('organization_name') + }) + // lazy regex for a guid cy.location('pathname').should('match', /\/verify_email\/[a-zA-Z0-9_.-]*/) }) @@ -74,7 +109,7 @@ describe('Signup', () => { cy.get('.Toastify [data-attr="error-toast"]').contains('Inactive social login session.') }) - it.only('Shows redirect notice if redirecting for maintenance', () => { + it('Shows redirect notice if redirecting for maintenance', () => { cy.intercept('**/decide/*', (req) => req.reply( decideResponse({ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-user--dark.png b/frontend/__snapshots__/scenes-other-settings--settings-user--dark.png index f68ff84645860..651d180143e39 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-user--dark.png and b/frontend/__snapshots__/scenes-other-settings--settings-user--dark.png differ diff --git a/frontend/__snapshots__/scenes-other-settings--settings-user--light.png b/frontend/__snapshots__/scenes-other-settings--settings-user--light.png index b4a80fca60fc9..807819111d701 100644 Binary files a/frontend/__snapshots__/scenes-other-settings--settings-user--light.png and b/frontend/__snapshots__/scenes-other-settings--settings-user--light.png differ diff --git a/frontend/src/scenes/authentication/signup/signupForm/panels/SignupPanel2.tsx b/frontend/src/scenes/authentication/signup/signupForm/panels/SignupPanel2.tsx index 1952c156a8fa2..a79bac3b9b0df 100644 --- a/frontend/src/scenes/authentication/signup/signupForm/panels/SignupPanel2.tsx +++ b/frontend/src/scenes/authentication/signup/signupForm/panels/SignupPanel2.tsx @@ -17,10 +17,10 @@ export function SignupPanel2(): JSX.Element | null { return (
- + diff --git a/frontend/src/scenes/authentication/signup/signupForm/signupLogic.ts b/frontend/src/scenes/authentication/signup/signupForm/signupLogic.ts index 803f47ecad608..df031fe4f4ec6 100644 --- a/frontend/src/scenes/authentication/signup/signupForm/signupLogic.ts +++ b/frontend/src/scenes/authentication/signup/signupForm/signupLogic.ts @@ -6,6 +6,7 @@ import { urlToAction } from 'kea-router' import api from 'lib/api' import { CLOUD_HOSTNAMES, FEATURE_FLAGS } from 'lib/constants' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' +import posthog from 'posthog-js' import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' import { urls } from 'scenes/urls' @@ -22,7 +23,7 @@ export interface AccountResponse { export interface SignupForm { email: string password: string - first_name: string + name: string organization_name: string role_at_organization: string referral_source: string @@ -78,19 +79,27 @@ export const signupLogic = kea([ alwaysShowErrors: true, showErrorsOnTouch: true, defaults: { - first_name: '', + name: '', organization_name: '', role_at_organization: '', referral_source: '', } as SignupForm, - errors: ({ first_name, organization_name }) => ({ - first_name: !first_name ? 'Please enter your name' : undefined, - organization_name: !organization_name ? 'Please enter your organization name' : undefined, + errors: ({ name }) => ({ + name: !name ? 'Please enter your name' : undefined, }), submit: async (payload, breakpoint) => { breakpoint() try { - const res = await api.create('api/signup/', { ...values.signupPanel1, ...payload }) + const res = await api.create('api/signup/', { + ...values.signupPanel1, + ...payload, + first_name: payload.name.split(' ')[0], + last_name: payload.name.split(' ')[1] || undefined, + organization_name: payload.organization_name || undefined, + }) + if (!payload.organization_name) { + posthog.capture('sign up organization name not provided') + } location.href = res.redirect_url || '/' } catch (e) { actions.setSignupPanel2ManualErrors({ @@ -142,7 +151,7 @@ export const signupLogic = kea([ email, }) actions.setSignupPanel2Values({ - first_name: 'X', + name: 'X', organization_name: 'Y', }) actions.submitSignupPanel2() diff --git a/frontend/src/scenes/settings/user/UserDetails.tsx b/frontend/src/scenes/settings/user/UserDetails.tsx index 587b54161b91d..b9ffad870537d 100644 --- a/frontend/src/scenes/settings/user/UserDetails.tsx +++ b/frontend/src/scenes/settings/user/UserDetails.tsx @@ -19,16 +19,25 @@ export function UserDetails(): JSX.Element { maxWidth: '28rem', }} > - + - + + + + + ([ : null, email: !email ? 'You need to have an email.' - : first_name.length > 254 + : email.length > 254 ? 'This email is too long. Please keep it under 255 characters.' : null, }), @@ -98,10 +98,12 @@ export const userLogic = kea([ { loadUserSuccess: (_, { user }) => ({ first_name: user?.first_name || '', + last_name: user?.last_name || '', email: user?.email || '', }), updateUserSuccess: (_, { user }) => ({ first_name: user?.first_name || '', + last_name: user?.last_name || '', email: user?.email || '', }), }, diff --git a/posthog/api/signup.py b/posthog/api/signup.py index 13f171b906485..b8c3db86c3341 100644 --- a/posthog/api/signup.py +++ b/posthog/api/signup.py @@ -51,6 +51,7 @@ def get_redirect_url(uuid: str, is_email_verified: bool) -> str: class SignupSerializer(serializers.Serializer): first_name: serializers.Field = serializers.CharField(max_length=128) + last_name: serializers.Field = serializers.CharField(max_length=128, required=False, allow_blank=True) email: serializers.Field = serializers.EmailField() password: serializers.Field = serializers.CharField(allow_null=True, required=True) organization_name: serializers.Field = serializers.CharField(max_length=128, required=False, allow_blank=True) @@ -92,7 +93,7 @@ def create(self, validated_data, **kwargs): is_instance_first_user: bool = not User.objects.exists() - organization_name = validated_data.pop("organization_name", validated_data["first_name"]) + organization_name = validated_data.pop("organization_name", f"{validated_data['first_name']}'s Organization") role_at_organization = validated_data.pop("role_at_organization", "") referral_source = validated_data.pop("referral_source", "") diff --git a/posthog/api/test/test_signup.py b/posthog/api/test/test_signup.py index e62be1ffd4893..1587c0b365e9e 100644 --- a/posthog/api/test/test_signup.py +++ b/posthog/api/test/test_signup.py @@ -43,6 +43,7 @@ def test_api_sign_up(self, mock_capture): "/api/signup/", { "first_name": "John", + "last_name": "Doe", "email": "hedgehog@posthog.com", "password": "notsecure", "organization_name": "Hedgehogs United, LLC", @@ -62,8 +63,8 @@ def test_api_sign_up(self, mock_capture): "id": user.pk, "uuid": str(user.uuid), "distinct_id": user.distinct_id, - "last_name": "", "first_name": "John", + "last_name": "Doe", "email": "hedgehog@posthog.com", "redirect_url": "/", "is_email_verified": False, @@ -72,6 +73,7 @@ def test_api_sign_up(self, mock_capture): # Assert that the user was properly created self.assertEqual(user.first_name, "John") + self.assertEqual(user.last_name, "Doe") self.assertEqual(user.email, "hedgehog@posthog.com") self.assertFalse(user.email_opt_in) self.assertTrue(user.is_staff) # True because this is the first user in the instance @@ -223,7 +225,7 @@ def test_signup_minimum_attrs(self, mock_capture): self.assertEqual(user.first_name, "Jane") self.assertEqual(user.email, "hedgehog2@posthog.com") self.assertTrue(user.email_opt_in) # Defaults to True - self.assertEqual(organization.name, "Jane") + self.assertEqual(organization.name, f"{user.first_name}'s Organization") self.assertTrue(user.is_staff) # True because this is the first user in the instance # Assert that the sign up event & identify calls were sent to PostHog analytics