Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added checks for confirm terms and email notifications #5664

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions app/assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,11 @@
"allow_users_to_preupload_presentation": "Allow Users to Preupload Presentations",
"allow_users_to_preupload_presentation_description": "Users can preupload a presentation to be used as the default presentation for that specific room",
"default_visibility": "Default Recording Visibility",
"default_visibility_description": "All newly created recordings will have this visibility by default"
"default_visibility_description": "All newly created recordings will have this visibility by default",
"confirm_terms": "Confirm terms and conditions",
"confirm_terms_description": "Require users to confirm terms and conditions",
"disable": "Disable",
"enable": "Enable"
},
"registration": {
"registration": "Registration",
Expand Down Expand Up @@ -401,7 +405,8 @@
"brand_image_deleted": "The brand image has been deleted.",
"privacy_policy_updated": "The privacy policy has been updated.",
"helpcenter_updated": "The help center link has been updated.",
"terms_of_service_updated": "The terms of service have been updated."
"terms_of_service_updated": "The terms of service have been updated.",
"confirm_terms_updated": "The confirmation for terms of service have been updated."
},
"recording": {
"recording_visibility_updated": "The recording visibility has been updated.",
Expand Down Expand Up @@ -460,6 +465,12 @@
"resend_activation_link": "If you haven't received an activation email or if you are having a problem using it, click on the button below to request a new activation email.",
"resend_btn_lbl": "Resend Verification"
},
"confirm_terms_page": {
"title": "Confirm terms and conditions",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should Accept Our Terms and Conditions

"account_unconfirmed": "Your account has not accepted the terms and conditions.",
"message": "Please accept the terms and conditions for Greenlight.",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to phrase this a bit more gentle. Also, we don't really refer to the app as Greenlight to the end user since they only know it as BBB.

To continue using BigBlueButton, please accept our terms and conditions

"confirm_btn_lbl": "Confirm"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no plain confirm button that we can reuse from somewhere else?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't find one so I reused the resend button from the VerifyAccount page for a plain confirm.

},
"forms": {
"validations": {
"full_name": {
Expand Down Expand Up @@ -487,6 +498,9 @@
"required": "Please enter a password confirmation",
"match": "The passwords do not match"
},
"confirm_terms": {
"required": "Please accept the terms and conditions"
},
"emails": {
"required": "Please enter at least one valid email",
"list": "Please provide a comma separated list of valid emails ([email protected],[email protected],[email protected])"
Expand Down Expand Up @@ -560,6 +574,12 @@
"password_confirmation": {
"label": "Confirm Password",
"placeholder": "Confirm password"
},
"confirm_terms": {
"label": "I Accept the terms and conditions"
},
"email_notifs": {
"label": "I would like to receive email updates from Greenlight"
}
}
},
Expand Down
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.bootstrap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
@import 'admin_panel';
@import 'pagination';
@import 'fonts';
@import 'signup.scss';

html,
body {
Expand Down
39 changes: 39 additions & 0 deletions app/assets/stylesheets/signup.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
//
// Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
//
// This program is free software; you can redistribute it and/or modify it under the
// terms of the GNU Lesser General Public License as published by the Free Software
// Foundation; either version 3.0 of the License, or (at your option) any later
// version.
//
// Greenlight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along
// with Greenlight; if not, see <http://www.gnu.org/licenses/>.

// making it look like a normal checkbox removing FormControl Css
input[type="checkbox"].form-control {
width: auto;
height: auto;
padding: 0;
margin: 0;
vertical-align: baseline;
position: static;
display: inline-block;
border: none;
background: none;
box-shadow: none;
outline: none;
opacity: 1;
-webkit-appearance: checkbox; /* for WebKit browsers */
-moz-appearance: checkbox; /* for Firefox */
appearance: checkbox;
}

// additional styling
input[type="checkbox"].form-control {
margin-left: 0.5em;
}
7 changes: 4 additions & 3 deletions app/controllers/api/v1/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ def create
# Updates the values of a user
def update
user = User.find(params[:id])

# User can't change their own role
if params[:user][:role_id].present? && current_user == user && params[:user][:role_id] != user.role_id
return render_error errors: Rails.configuration.custom_error_msgs[:unauthorized], status: :forbidden
Expand Down Expand Up @@ -156,11 +155,13 @@ def change_password
private

def create_user_params
@create_user_params ||= params.require(:user).permit(:name, :email, :password, :avatar, :language, :role_id, :invite_token)
@create_user_params ||= params.require(:user).permit(:name, :email, :password, :avatar, :language, :role_id,
:confirm_terms, :email_notifs, :invite_token)
end

def update_user_params
@update_user_params ||= params.require(:user).permit(:name, :password, :avatar, :language, :role_id, :invite_token)
@update_user_params ||= params.require(:user).permit(:name, :password, :avatar, :language, :role_id,
:confirm_terms, :email_notifs, :invite_token)
end

def change_password_params
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default function Settings() {
const { t } = useTranslation();
const { data: siteSettings, isLoading } = useSiteSettings(['ShareRooms', 'PreuploadPresentation', 'DefaultRecordingVisibility']);
const updateDefaultRecordingVisibility = useUpdateSiteSetting('DefaultRecordingVisibility');
const updateConfirmTerms = useUpdateSiteSetting('ConfirmTerms');

if (isLoading) return null;

Expand Down Expand Up @@ -77,6 +78,19 @@ export default function Settings() {
{t('recording.unpublished')}
</Dropdown.Item>
</SettingSelect>

<SettingSelect
defaultValue={siteSettings?.ConfirmTerms}
title={t('admin.site_settings.settings.confirm_terms')}
description={t('admin.site_settings.settings.confirm_terms_description')}
>
<Dropdown.Item key="false" value="false" onClick={() => updateConfirmTerms.mutate({ value: 'false' })}>
{t('admin.site_settings.settings.disable')}
</Dropdown.Item>
<Dropdown.Item key="true" value="true" onClick={() => updateConfirmTerms.mutate({ value: 'true' })}>
{t('admin.site_settings.settings.enable')}
</Dropdown.Item>
</SettingSelect>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
//
// Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
//
// This program is free software; you can redistribute it and/or modify it under the
// terms of the GNU Lesser General Public License as published by the Free Software
// Foundation; either version 3.0 of the License, or (at your option) any later
// version.
//
// Greenlight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along
// with Greenlight; if not, see <http://www.gnu.org/licenses/>.

import React, { useState } from 'react';
import {
Button, Card, Stack,
} from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import Spinner from '../../shared_components/utilities/Spinner';
import Logo from '../../shared_components/Logo';
import { useAuth } from '../../../contexts/auth/AuthProvider';
import useUpdateUser from '../../../hooks/mutations/users/useUpdateUser';

export default function ConfirmTerms() {
const { t } = useTranslation();

const currentUser = useAuth();
const updateUserAPI = useUpdateUser(currentUser?.id);
const [isCheckedTerms, setIsCheckedTerms] = useState(currentUser?.confirm_terms || false);
const [isCheckedEmails, setIsCheckedEmails] = useState(currentUser?.email_notifs || false);

// Update the user's confirm_terms value when the button is clicked
const handleConfirmTerms = () => {
updateUserAPI.mutate({
confirm_terms: isCheckedTerms,
email_notifs: isCheckedEmails,
});
};

return (
<div className="vertical-center">
<div className="text-center pb-4">
<Logo />
</div>
<Card className="col-md-4 mx-auto p-4 border-0 card-shadow">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can increase the width of the box here abit to stop the title from wrapping around

<Stack direction="vertical" className="py-3">
<h3><strong>{ t('confirm_terms_page.title') }</strong></h3>
<h5 className="mb-3">{ t('confirm_terms_page.account_unconfirmed') }</h5>
</Stack>
<span className="mb-3">{ t('confirm_terms_page.message') }</span>

<Stack direction="horizontal">
<Stack>
{t('forms.user.signup.fields.confirm_terms.label')}
</Stack>
<div className="form-switch">
<input
className="form-check-input fs-5"
type="checkbox"
checked={isCheckedTerms}
onChange={() => setIsCheckedTerms(!isCheckedTerms)}
/>
</div>
</Stack>

<Stack direction="horizontal">
<Stack>
{t('forms.user.signup.fields.email_notifs.label')}
</Stack>
<div className="form-switch">
<input
className="form-check-input fs-5"
type="checkbox"
checked={isCheckedEmails}
onChange={() => setIsCheckedEmails(!isCheckedEmails)}
/>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check boxes should be on the left side of the text

</div>
</Stack>

<Button
variant="brand"
type="submit"
disabled={!isCheckedTerms || updateUserAPI.isLoading}
onClick={handleConfirmTerms}
className="mt-2"
>
{ t('confirm_terms_page.confirm_btn_lbl') }
{updateUserAPI.isLoading && <Spinner className="me-2" />}
</Button>
</Card>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export default function SignupForm() {
<FormControl field={fields.email} type="email" />
<FormControl field={fields.password} type="password" />
<FormControl field={fields.password_confirmation} type="password" />

<FormControl field={fields.confirm_terms} type="checkbox" />
<FormControl field={fields.email_notifs} type="checkbox" />

<HCaptcha ref={captchaRef} />
<Stack className="mt-1" gap={1}>
<Button variant="brand" className="w-100 my-3 mt-1" type="submit" disabled={createUserAPI.isLoading}>
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/contexts/auth/AuthProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default function AuthProvider({ children }) {
external_account: currentUser?.external_account,
stateChanging: false,
isSuperAdmin: currentUser?.super_admin,
confirm_terms: currentUser?.confirm_terms,
email_notifs: currentUser?.email_notifs,
};

const memoizedCurrentUser = useMemo(() => user, [user]);
Expand Down
18 changes: 18 additions & 0 deletions app/javascript/hooks/forms/users/authentication/useSignUpForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export function useSignUpFormValidation() {
.test('oneSymbol', 'forms.validations.password.symbol', (pwd) => pwd.match(/[`@%~!#£$\\^&*()\][+={}/|:;"'<>\-,.?_ ]/)),
password_confirmation: yup.string().required('forms.validations.password_confirmation.required')
.oneOf([yup.ref('password')], 'forms.validations.password_confirmation.match'),

confirm_terms: yup.boolean().oneOf([true], 'forms.validations.confirm_terms.required'),
})), []);
}

Expand Down Expand Up @@ -87,6 +89,20 @@ export default function useSignUpForm({ defaultValues: _defaultValues, ..._confi
},
},
},
confirm_terms: {
label: t('forms.user.signup.fields.confirm_terms.label'),
controlId: 'signupFormConfirmTerms',
hookForm: {
id: 'confirm_terms',
},
},
email_notifs: {
label: t('forms.user.signup.fields.email_notifs.label'),
controlId: 'signupFormEmailNotifs',
hookForm: {
id: 'email_notifs',
},
},
}), [i18n.resolvedLanguage]);

const validationSchema = useSignUpFormValidation();
Expand All @@ -101,6 +117,8 @@ export default function useSignUpForm({ defaultValues: _defaultValues, ..._confi
email: '',
password: '',
password_confirmation: '',
confirm_terms: false,
email_notifs: false,
},
..._defaultValues,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export default function useUpdateSiteSetting(name) {
case 'TermsOfService':
toast.success(t('toast.success.site_settings.terms_of_service_updated'));
break;
case 'ConfirmTerms':
toast.success(t('toast.success.site_settings.confirm_terms_updated'));
break;
default:
toast.success(t('toast.success.site_settings.site_setting_updated'));
}
Expand Down
7 changes: 7 additions & 0 deletions app/javascript/routes/AuthenticatedOnly.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ import { useTranslation } from 'react-i18next';
import { toast } from 'react-toastify';
import { useAuth } from '../contexts/auth/AuthProvider';
import useDeleteSession from '../hooks/mutations/sessions/useDeleteSession';
import useSiteSetting from '../hooks/queries/site_settings/useSiteSetting';
import ConfirmTerms from '../components/users/account_activation/ConfirmTerms';

export default function AuthenticatedOnly() {
const { t } = useTranslation();
const currentUser = useAuth();
const roomsMatch = useMatch('/rooms/:friendlyId');
const superAdminMatch = useMatch('/admin/*');
const deleteSession = useDeleteSession({ showToast: false });
const { data: confirmTerms } = useSiteSetting('ConfirmTerms');

// User is either pending or banned
if (currentUser.signed_in && (currentUser.status !== 'active' || !currentUser.verified)) {
Expand All @@ -50,6 +53,10 @@ export default function AuthenticatedOnly() {
return <Navigate to="/admin/users" />;
}

if (currentUser.signed_in && confirmTerms && !currentUser.confirm_terms) {
return <ConfirmTerms />;
}

if (!currentUser.signed_in) {
toast.error(t('toast.error.signin_required'));
return <Navigate to="/" />;
Expand Down
6 changes: 5 additions & 1 deletion app/serializers/user_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
class UserSerializer < ApplicationSerializer
include Avatarable

attributes :id, :name, :email, :provider, :language, :avatar, :verified, :created_at
attributes :id, :name, :email, :provider, :language, :avatar, :verified, :confirm_terms, :email_notifs, :created_at
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need access to email_notifs on the front end?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the email_notifs are needed on the ConfirmTerms page as along with option to accept terms they also have a toggle to receive email updates and the initial position of that toggle needs the data from the user. (email_notifs is now marketing).


belongs_to :role

Expand All @@ -30,4 +30,8 @@ def language
def avatar
user_avatar(object)
end

delegate :confirm_terms, to: :object

delegate :email_notifs, to: :object
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these are necessary - the serializer should be able to do this automatically (as it does with the other values (ie name, email)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tested yeah your right its not needed and works fine without. removed!

end
20 changes: 20 additions & 0 deletions db/migrate/20240108202126_add_confirm_terms_to_site_settings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

class AddConfirmTermsToSiteSettings < ActiveRecord::Migration[7.1]
def change
Setting.create!(name: 'ConfirmTerms') unless Setting.exists?(name: 'ConfirmTerms')

return if SiteSetting.exists?(setting: Setting.find_by(name: 'ConfirmTerms'))

SiteSetting.create!(
setting: Setting.find_by(name: 'ConfirmTerms'),
value: false,
provider: 'greenlight'
)
Comment on lines +5 to +13
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're doing a data migration inside of a schema migration. They should be seperated into 2 separate migrations


change_table :users, bulk: true do |t|
t.boolean :confirm_terms, default: false
t.boolean :email_notifs, default: false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For simplicities sake, I would just call this terms and marketing

end
end
end
Loading
Loading