feat: Allow users to disable marking offline automatically (#6079)
Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
This commit is contained in:
parent
82d3398932
commit
aaacf9d4d2
14 changed files with 188 additions and 14 deletions
|
@ -18,6 +18,10 @@ class Api::V1::ProfilesController < Api::BaseController
|
|||
head :ok
|
||||
end
|
||||
|
||||
def auto_offline
|
||||
@user.account_users.find_by!(account_id: auto_offline_params[:account_id]).update!(auto_offline: auto_offline_params[:auto_offline] || false)
|
||||
end
|
||||
|
||||
def availability
|
||||
@user.account_users.find_by!(account_id: availability_params[:account_id]).update!(availability: availability_params[:availability])
|
||||
end
|
||||
|
@ -37,6 +41,10 @@ class Api::V1::ProfilesController < Api::BaseController
|
|||
params.require(:profile).permit(:account_id, :availability)
|
||||
end
|
||||
|
||||
def auto_offline_params
|
||||
params.require(:profile).permit(:account_id, :auto_offline)
|
||||
end
|
||||
|
||||
def profile_params
|
||||
params.require(:profile).permit(
|
||||
:email,
|
||||
|
|
|
@ -144,6 +144,12 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
updateAutoOffline(accountId, autoOffline = false) {
|
||||
return axios.post(endPoints('autoOffline').url, {
|
||||
profile: { account_id: accountId, auto_offline: autoOffline },
|
||||
});
|
||||
},
|
||||
|
||||
deleteAvatar() {
|
||||
return axios.delete(endPoints('deleteAvatar').url);
|
||||
},
|
||||
|
|
|
@ -16,6 +16,9 @@ const endPoints = {
|
|||
availabilityUpdate: {
|
||||
url: '/api/v1/profile/availability',
|
||||
},
|
||||
autoOffline: {
|
||||
url: '/api/v1/profile/auto_offline',
|
||||
},
|
||||
logout: {
|
||||
url: 'auth/sign_out',
|
||||
},
|
||||
|
|
|
@ -18,12 +18,35 @@
|
|||
</woot-button>
|
||||
</woot-dropdown-item>
|
||||
<woot-dropdown-divider />
|
||||
<woot-dropdown-item class="auto-offline--toggle">
|
||||
<div class="info-wrap">
|
||||
<fluent-icon
|
||||
v-tooltip.right-start="$t('SIDEBAR.SET_AUTO_OFFLINE.INFO_TEXT')"
|
||||
icon="info"
|
||||
size="14"
|
||||
class="info-icon"
|
||||
/>
|
||||
|
||||
<span class="auto-offline--text">
|
||||
{{ $t('SIDEBAR.SET_AUTO_OFFLINE.TEXT') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<woot-switch
|
||||
size="small"
|
||||
class="auto-offline--switch"
|
||||
:value="currentUserAutoOffline"
|
||||
@input="updateAutoOffline"
|
||||
/>
|
||||
</woot-dropdown-item>
|
||||
<woot-dropdown-divider />
|
||||
</woot-dropdown-menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
|
||||
import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader';
|
||||
|
@ -41,7 +64,7 @@ export default {
|
|||
AvailabilityStatusBadge,
|
||||
},
|
||||
|
||||
mixins: [clickaway],
|
||||
mixins: [clickaway, alertMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
@ -54,6 +77,7 @@ export default {
|
|||
...mapGetters({
|
||||
getCurrentUserAvailability: 'getCurrentUserAvailability',
|
||||
currentAccountId: 'getCurrentAccountId',
|
||||
currentUserAutoOffline: 'getCurrentUserAutoOffline',
|
||||
}),
|
||||
availabilityDisplayLabel() {
|
||||
const availabilityIndex = AVAILABILITY_STATUS_KEYS.findIndex(
|
||||
|
@ -85,21 +109,30 @@ export default {
|
|||
closeStatusMenu() {
|
||||
this.isStatusMenuOpened = false;
|
||||
},
|
||||
updateAutoOffline(autoOffline) {
|
||||
this.$store.dispatch('updateAutoOffline', {
|
||||
accountId: this.currentAccountId,
|
||||
autoOffline,
|
||||
});
|
||||
},
|
||||
changeAvailabilityStatus(availability) {
|
||||
const accountId = this.currentAccountId;
|
||||
if (this.isUpdating) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isUpdating = true;
|
||||
this.$store
|
||||
.dispatch('updateAvailability', {
|
||||
availability: availability,
|
||||
account_id: accountId,
|
||||
})
|
||||
.finally(() => {
|
||||
this.isUpdating = false;
|
||||
try {
|
||||
this.$store.dispatch('updateAvailability', {
|
||||
availability,
|
||||
account_id: this.currentAccountId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.showAlert(
|
||||
this.$t('PROFILE_SETTINGS.FORM.AVAILABILITY.SET_AVAILABILITY_ERROR')
|
||||
);
|
||||
} finally {
|
||||
this.isUpdating = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -143,4 +176,32 @@ export default {
|
|||
align-items: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.auto-offline--toggle {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-smaller) 0 var(--space-smaller) var(--space-small);
|
||||
margin: 0;
|
||||
|
||||
.info-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.auto-offline--switch {
|
||||
margin: -1px var(--space-micro) 0;
|
||||
}
|
||||
|
||||
.auto-offline--text {
|
||||
margin: 0 var(--space-smaller);
|
||||
font-size: var(--font-size-mini);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--s-700);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -135,7 +135,7 @@ export default {
|
|||
.dropdown-pane {
|
||||
left: var(--space-slab);
|
||||
bottom: var(--space-larger);
|
||||
min-width: 16.8rem;
|
||||
z-index: var(--z-index-much-higher);
|
||||
min-width: 22rem;
|
||||
z-index: var(--z-index-low);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<button
|
||||
type="button"
|
||||
class="toggle-button"
|
||||
:class="{ active: value }"
|
||||
:class="{ active: value, small: size === 'small' }"
|
||||
role="switch"
|
||||
:aria-checked="value.toString()"
|
||||
@click="onClick"
|
||||
|
@ -15,6 +15,7 @@
|
|||
export default {
|
||||
props: {
|
||||
value: { type: Boolean, default: false },
|
||||
size: { type: String, default: '' },
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
|
@ -45,6 +46,20 @@ export default {
|
|||
background-color: var(--w-500);
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: 22px;
|
||||
height: 14px;
|
||||
|
||||
span {
|
||||
height: var(--space-one);
|
||||
width: var(--space-one);
|
||||
|
||||
&.active {
|
||||
transform: translate(var(--space-small), var(--space-zero));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
--space-one-point-five: 1.5rem;
|
||||
background-color: var(--white);
|
||||
|
|
|
@ -99,7 +99,9 @@
|
|||
},
|
||||
"AVAILABILITY": {
|
||||
"LABEL": "Availability",
|
||||
"STATUSES_LIST": ["Online", "Busy", "Offline"]
|
||||
"STATUSES_LIST": ["Online", "Busy", "Offline"],
|
||||
"SET_AVAILABILITY_SUCCESS": "Availability has been set successfully",
|
||||
"SET_AVAILABILITY_ERROR": "Couldn't set availability, please try again"
|
||||
},
|
||||
"EMAIL": {
|
||||
"LABEL": "Your email address",
|
||||
|
@ -222,6 +224,10 @@
|
|||
"CATEGORY": "Category",
|
||||
"CATEGORY_EMPTY_MESSAGE": "No categories found"
|
||||
},
|
||||
"SET_AUTO_OFFLINE": {
|
||||
"TEXT": "Mark offline automatically",
|
||||
"INFO_TEXT": "Let the system automatically mark you offline when you aren't using the app or dashboard."
|
||||
},
|
||||
"DOCS": "Read docs"
|
||||
},
|
||||
"BILLING_SETTINGS": {
|
||||
|
|
|
@ -48,6 +48,14 @@ export const getters = {
|
|||
return currentAccount.availability;
|
||||
},
|
||||
|
||||
getCurrentUserAutoOffline($state, $getters) {
|
||||
const { accounts = [] } = $state.currentUser;
|
||||
const [currentAccount = {}] = accounts.filter(
|
||||
account => account.id === $getters.getCurrentAccountId
|
||||
);
|
||||
return currentAccount.auto_offline;
|
||||
},
|
||||
|
||||
getCurrentAccountId(_, __, rootState) {
|
||||
if (rootState.route.params && rootState.route.params.accountId) {
|
||||
return Number(rootState.route.params.accountId);
|
||||
|
@ -174,6 +182,15 @@ export const actions = {
|
|||
}
|
||||
},
|
||||
|
||||
updateAutoOffline: async ({ commit }, { accountId, autoOffline }) => {
|
||||
try {
|
||||
const response = await authAPI.updateAutoOffline(accountId, autoOffline);
|
||||
commit(types.SET_CURRENT_USER, response.data);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
}
|
||||
},
|
||||
|
||||
setCurrentUserAvailability({ commit, state: $state }, data) {
|
||||
if (data[$state.currentUser.id]) {
|
||||
commit(types.SET_CURRENT_USER_AVAILABILITY, data[$state.currentUser.id]);
|
||||
|
|
|
@ -88,6 +88,38 @@ describe('#actions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#updateAutoOffline', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
axios.post.mockResolvedValue({
|
||||
data: {
|
||||
id: 1,
|
||||
name: 'John',
|
||||
accounts: [
|
||||
{
|
||||
account_id: 1,
|
||||
auto_offline: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
headers: { expiry: 581842904 },
|
||||
});
|
||||
await actions.updateAutoOffline(
|
||||
{ commit, dispatch },
|
||||
{ autoOffline: false, accountId: 1 }
|
||||
);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
[
|
||||
types.default.SET_CURRENT_USER,
|
||||
{
|
||||
id: 1,
|
||||
name: 'John',
|
||||
accounts: [{ account_id: 1, auto_offline: false }],
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateUISettings', () => {
|
||||
it('sends correct actions if API is success', async () => {
|
||||
axios.put.mockResolvedValue({
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<li class="dropdown-menu--header" :tabindex="null" :aria-disabled="true">
|
||||
<span class="title">{{ title }}</span>
|
||||
<slot />
|
||||
</li>
|
||||
</template>
|
||||
<script>
|
||||
|
|
1
app/views/api/v1/profiles/auto_offline.jbuilder
Normal file
1
app/views/api/v1/profiles/auto_offline.jbuilder
Normal file
|
@ -0,0 +1 @@
|
|||
json.partial! 'api/v1/models/user', formats: [:json], resource: @user
|
|
@ -77,4 +77,3 @@ fullcontact:
|
|||
settings_form_schema:
|
||||
[{ 'label': 'API Key', 'type': 'text', 'name': 'api_key',"validation": "required", }]
|
||||
visible_properties: ['api_key']
|
||||
|
||||
|
|
|
@ -182,6 +182,7 @@ Rails.application.routes.draw do
|
|||
delete :avatar, on: :collection
|
||||
member do
|
||||
post :availability
|
||||
post :auto_offline
|
||||
put :set_active_account
|
||||
end
|
||||
end
|
||||
|
|
|
@ -196,6 +196,30 @@ RSpec.describe 'Profile API', type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/profile/auto_offline' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
post '/api/v1/profile/auto_offline'
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:agent) { create(:user, password: 'Test123!', account: account, role: :agent) }
|
||||
|
||||
it 'updates the auto offline status' do
|
||||
post '/api/v1/profile/auto_offline',
|
||||
params: { profile: { auto_offline: false, account_id: account.id } },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response['accounts'].first['auto_offline']).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /api/v1/profile/set_active_account' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
|
|
Loading…
Reference in a new issue