Website: Update signup modal styles (#35956)

Closes: https://github.com/fleetdm/fleet/issues/35955

Changes:
- Updated the signup modal styles to prevent page styles from overriding
the modal's stylesheet.
- Added punctuation to the link to the query generator on the query
library page.
- Added the signup modal to the query generator page.
This commit is contained in:
Eric 2025-11-18 18:03:20 -06:00 committed by GitHub
parent c5e789ab60
commit e8a473829e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 88 additions and 61 deletions

View file

@ -35,7 +35,7 @@ parasails.registerComponent('signupModal', {
firstName: {required: true},
lastName: {required: true},
emailAddress: {required: true, isEmail: true},
password: {required: true},
password: {required: true, minLength: 8},
},
// Login form
loginFormData: {},
@ -81,35 +81,35 @@ parasails.registerComponent('signupModal', {
</div>
<ajax-form action="signup" purpose="modal-form" class="self-service-register" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-errors.sync="signupFormErrors" :form-data="signupFormData" :form-rules="signupFormRules" @submitted="submittedSignupForm()" v-if="formToDisplay === 'signup'">
<div class="form-group">
<label for="email-address">Work email *</label>
<input tabindex="1" class="form-control" id="email-address" :class="[signupFormErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="signupFormData.emailAddress" @input="typeClearOneFormError('emailAddress')">
<label purpose="modal-form-label" for="email-address">Work email *</label>
<input tabindex="1" purpose="modal-form-input" class="form-control" id="email-address" :class="[signupFormErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="signupFormData.emailAddress" @input="typeClearOneFormError('emailAddress')">
<div class="invalid-feedback" v-if="signupFormErrors.emailAddress" focus-first>This doesnt appear to be a valid email address</div>
</div>
<div class="form-group">
<label for="password">Choose a password *</label>
<input tabindex="2" class="form-control" id="password" type="password" :class="[signupFormErrors.password ? 'is-invalid' : '']" v-model.trim="signupFormData.password" autocomplete="new-password" @input="typeClearOneFormError('password')">
<label purpose="modal-form-label" for="password">Choose a password *</label>
<input tabindex="2" purpose="modal-form-input" class="form-control" id="password" type="password" :class="[signupFormErrors.password ? 'is-invalid' : '']" v-model.trim="signupFormData.password" autocomplete="new-password" @input="typeClearOneFormError('password')">
<div class="invalid-feedback" v-if="signupFormErrors.password === 'minLength'">Password too short.</div>
<div class="invalid-feedback" v-if="signupFormErrors.password === 'required'">Please enter a password.</div>
<p class="mt-2 small"> Minimum length is 8 characters</p>
<p purpose="modal-form-note" class="mt-2"> Minimum length is 8 characters</p>
</div>
<div class="row">
<div class="col-12 col-sm-6 pr-sm-2">
<div class="form-group">
<label for="first-name">First name *</label>
<input tabindex="4" class="form-control" id="first-name" type="text" :class="[signupFormErrors.firstName ? 'is-invalid' : '']" v-model.trim="signupFormData.firstName" autocomplete="first-name" @input="typeClearOneFormError('firstName')">
<label purpose="modal-form-label" for="first-name">First name *</label>
<input tabindex="4" purpose="modal-form-input" class="form-control" id="first-name" type="text" :class="[signupFormErrors.firstName ? 'is-invalid' : '']" v-model.trim="signupFormData.firstName" autocomplete="first-name" @input="typeClearOneFormError('firstName')">
<div class="invalid-feedback" v-if="signupFormErrors.firstName">Please enter your first name.</div>
</div>
</div>
<div class="col-12 col-sm-6 pl-sm-2">
<div class="form-group">
<label for="last-name">Last name *</label>
<input tabindex="5" class="form-control" id="last-name" type="text" :class="[signupFormErrors.lastName ? 'is-invalid' : '']" v-model.trim="signupFormData.lastName" autocomplete="last-name" @input="typeClearOneFormError('lastName')">
<label purpose="modal-form-label" for="last-name">Last name *</label>
<input tabindex="5" purpose="modal-form-input" class="form-control" id="last-name" type="text" :class="[signupFormErrors.lastName ? 'is-invalid' : '']" v-model.trim="signupFormData.lastName" autocomplete="last-name" @input="typeClearOneFormError('lastName')">
<div class="invalid-feedback" v-if="signupFormErrors.lastName">Please enter your last name.</div>
</div>
</div>
</div>
<cloud-error v-if="cloudError==='emailAlreadyInUse'">
This email is already linked to a Fleet account.<br> Please <a @click="formToDisplay = 'login'">sign in</a> with your email and password.
This email is already linked to a Fleet account.<br> Please <a purpose="modal-form-link" @click="formToDisplay = 'login'">sign in</a> with your email and password.
</cloud-error>
<cloud-error v-if="cloudError === 'invalidEmailDomain'">
Please enter your work or school email address.
@ -117,32 +117,32 @@ parasails.registerComponent('signupModal', {
<blockquote purpose="tip" v-if="cloudError === 'invalidEmailDomain'">
<img src="/images/icon-info-16x16@2x.png" alt="An icon indicating that this section has important information">
<div class="d-block">
<p>Dont have a work email? Try a local demo of <a href="/try-fleet">Fleet Free</a> instead.</p>
<p>Dont have a work email? Try a local demo of <a purpose="modal-form-link" href="/try-fleet">Fleet Free</a> instead.</p>
</div>
</blockquote>
<cloud-error purpose="cloud-error" v-if="cloudError && !['emailAlreadyInUse', 'invalidEmailDomain'].includes(cloudError)"></cloud-error>
<p class="small">By signing up you agree to our <a href="/legal/privacy">privacy policy</a> and <a href="/terms">terms of service</a>.</p>
<ajax-button tabindex="6" purpose="submit-button" spinner="true" type="submit" :syncing="syncing" class="btn btn-block btn-lg btn-primary mt-4" v-if="!cloudError">Agree and continue</ajax-button>
<ajax-button tabindex="7" purpose="submit-button" type="button" :syncing="syncing" class="btn btn-block btn-lg btn-primary mt-4" v-if="cloudError" @click="clickResetForm()">Try again</ajax-button>
<p purpose="modal-form-note">By signing up you agree to our <a purpose="modal-form-link" href="/legal/privacy">privacy policy</a> and <a purpose="modal-form-link" href="/terms">terms of service</a>.</p>
<ajax-button tabindex="6" purpose="modal-form-submit-button" spinner="true" type="submit" :syncing="syncing" class="btn btn-block btn-lg btn-primary mt-4" v-if="!cloudError">Agree and continue</ajax-button>
<ajax-button tabindex="7" purpose="modal-form-submit-button" type="button" :syncing="syncing" class="btn btn-block btn-lg btn-primary mt-4" v-if="cloudError" @click="clickResetForm()">Try again</ajax-button>
</ajax-form>
<ajax-form class="w-100" action="login" purpose="modal-form" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-data="loginFormData" :form-rules="loginFormRules" :form-errors.sync="loginFormErrors" @submitted="submittedLoginForm()" v-else>
<div class="form-group">
<label for="email">Email</label>
<input tabindex="1" type="email" class="form-control" :class="[loginFormErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="loginFormData.emailAddress" autocomplete="email" focus-first>
<label purpose="modal-form-label" for="email">Email</label>
<input tabindex="1" type="email" purpose="modal-form-input" class="form-control" :class="[loginFormErrors.emailAddress ? 'is-invalid' : '']" v-model.trim="loginFormData.emailAddress" autocomplete="email" focus-first>
<div class="invalid-feedback" v-if="loginFormErrors.emailAddress">Please provide a valid email address.</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input tabindex="2" type="password" class="form-control" :class="[loginFormErrors.password ? 'is-invalid' : '']" v-model.trim="loginFormData.password" autocomplete="current-password">
<label purpose="modal-form-label" for="password">Password</label>
<input tabindex="2" type="password" purpose="modal-form-input" class="form-control" :class="[loginFormErrors.password ? 'is-invalid' : '']" v-model.trim="loginFormData.password" autocomplete="current-password">
<div class="invalid-feedback" v-if="loginFormErrors.password">Please enter your password.</div>
</div>
<cloud-error v-if="cloudError === 'noUser'">The email address provided doesn't match an existing account. Create an account <a @click="formToDisplay = 'signup'">here</a>.</cloud-error>
<cloud-error v-if="cloudError === 'noUser'">The email address provided doesn't match an existing account. Create an account <a purpose="modal-form-link" @click="formToDisplay = 'signup'">here</a>.</cloud-error>
<cloud-error v-else-if="cloudError === 'badCombo'">Somethings not quite right with your email or password.</cloud-error>
<cloud-error v-else-if="cloudError"></cloud-error>
<div class="pb-3">
<ajax-button tabindex="3" :syncing="syncing" spinner="true" purpose="submit-button" class="btn-primary mt-4 btn-lg btn-block">Sign in</ajax-button>
<ajax-button tabindex="3" :syncing="syncing" spinner="true" purpose="modal-form-submit-button" class="btn-primary mt-4 btn-lg btn-block">Sign in</ajax-button>
</div>
<span class="text-center small"><a href="/customers/forgot-password">Forgot your password?</a></span>
<span purpose="modal-form-note" class="text-center"><a purpose="modal-form-link" href="/customers/forgot-password">Forgot your password?</a></span>
</ajax-form>
</div>
</div><!-- /.modal-content -->

View file

@ -20,25 +20,24 @@
}
}
[purpose='form-switch'] {
[purpose='form-option'] {
user-select: none;
cursor: pointer;
width: fit-content;
padding: 8px 16px 8px 0px;
margin-bottom: 4px;
margin-bottom: 4px !important;//lesshint-disable-line importantRule
display: flex;
flex-direction: row;
align-items: center;
border-radius: 7px;
border: none;
color: #515774;
font-size: 14px;
font-weight: 400;
color: #515774 !important;//lesshint-disable-line importantRule
font-size: 14px !important;//lesshint-disable-line importantRule
font-weight: 400 !important;//lesshint-disable-line importantRule
line-height: 150%;
white-space: nowrap;
height: fit-content;
input {
input[type='radio'] {
cursor: pointer;
margin-right: 8px;
display: none;
@ -66,56 +65,82 @@
transform: scale(1);
}
}
.form-control {
height: 48px;
}
}
}
[purpose='modal-form'] {
.form-control {
[purpose='modal-form-input'] {
height: 40px;
border-radius: 6px;
}
.selectbox {
position: relative;
}
.selectbox::after {
content: url('/images/chevron-12x8@2x.png');
right: 14px;
transform: scale(0.5);
top: 14px;
position: absolute;
pointer-events: none;
}
.selectbox select {
border-radius: 6px;
height: 48px;
appearance: none;
-webkit-appearance: none;
}
label {
color: var(--text-text-brand, #192147);
/* Body SM (bold) */
[purpose='modal-form-label'] {
color: #192147;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 700;
line-height: 21px; /* 150% */
}
.small {
font-size: 12px;
[purpose='modal-form-note'] {
font-size: 12px !important;//lesshint-disable-line importantRule
}
[purpose='modal-form-link'] {
color: @core-fleet-black-75 !important;//lesshint-disable-line importantRule
cursor: pointer;
font-size: 12px !important;//lesshint-disable-line importantRule
text-decoration: underline !important;//lesshint-disable-line importantRule
text-underline-offset: 4px !important;//lesshint-disable-line importantRule
text-decoration-color: #C5C7D1 !important;//lesshint-disable-line importantRule
&:hover {
color: @core-fleet-black !important;//lesshint-disable-line importantRule
text-decoration-color: @core-fleet-black !important;//lesshint-disable-line importantRule
}
}
[parasails-component='cloud-error'] {
p {
color: @core-vibrant-red;
color: @core-vibrant-red !important;//lesshint-disable-line importantRule
font-size: 14px !important;//lesshint-disable-line importantRule
a {
text-decoration-color: @core-vibrant-red !important;//lesshint-disable-line importantRule
color: @core-vibrant-red !important;//lesshint-disable-line importantRule
font-size: unset !important;//lesshint-disable-line importantRule
&:hover {
color: darken(@core-vibrant-red, 20%) !important; //lesshint-disable-line importantRule
text-decoration-color: darken(@core-vibrant-red, 20%) !important;//lesshint-disable-line importantRule
}
}
}
}
[purpose='tip'] {
margin: 16px 0 32px;
background: #F4F4FF;
padding: 16px;
border-radius: 8px;
display: flex;
img {
display: flex;
margin: 4px 12px 0 0;
height: 16px;
width: 16px;
padding: 0px;
}
p {
display: block;
margin-bottom: 16px;
line-height: 24px;
font-size: 14px !important;//lesshint-disable-line importantRule
a {
font-size: unset !important;//lesshint-disable-line importantRule
color: unset !important;//lesshint-disable-line importantRule
}
}
p:last-child {
margin-bottom: 0px;
}
}
}
[purpose='submit-button'] {
[purpose='modal-form-submit-button'] {
margin-left: auto;
margin-right: auto;
@ -296,3 +321,4 @@
}
}

View file

@ -82,5 +82,6 @@
</div>
</div>
<signup-modal></signup-modal>
</div>
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>

View file

@ -7,7 +7,7 @@
<div purpose="page-headline" class="d-flex flex-column">
<h2>Queries</h2>
<p class="mb-1">A collection of optional queries you can run anytime. Contributions welcome <a target="_blank" href="https://github.com/fleetdm/fleet/edit/main/docs/queries.yml" no-icon>over on GitHub.</a></p>
<p>Want to create your own? Our <a href="/query-generator">query robot</a> can help</p>
<p>Want to create your own? Our <a href="/query-generator">query robot</a> can help.</p>
</div>
</div>
<div purpose="platform-filters" :class="[bowser.windows ? 'detected-windows' : '']" class="d-flex justify-content-center">