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

Allow users to create ereaders #3531

Merged
merged 14 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 13 additions & 2 deletions client/components/modals/AccountModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@
</div>
</div>

<div class="flex items-center my-2 max-w-md">
<div class="w-1/2">
<p id="ereader-permissions-toggle">{{ $strings.LabelPermissionsCreateEreader }}</p>
</div>
<div class="w-1/2">
<ui-toggle-switch labeledBy="ereader-permissions-toggle" v-model="newUser.permissions.createEreader" />
</div>
</div>

<div class="flex items-center my-2 max-w-md">
<div class="w-1/2">
<p id="explicit-content-permissions-toggle">{{ $strings.LabelPermissionsAccessExplicitContent }}</p>
Expand Down Expand Up @@ -354,7 +363,8 @@ export default {
accessExplicitContent: type === 'admin',
accessAllLibraries: true,
accessAllTags: true,
selectedTagsNotAccessible: false
selectedTagsNotAccessible: false,
createEreader: type === 'admin'
}
},
init() {
Expand Down Expand Up @@ -387,7 +397,8 @@ export default {
accessAllLibraries: true,
accessAllTags: true,
accessExplicitContent: false,
selectedTagsNotAccessible: false
selectedTagsNotAccessible: false,
createEreader: false
},
librariesAccessible: [],
itemTagsSelected: []
Expand Down
188 changes: 188 additions & 0 deletions client/components/modals/emails/UserEReaderDeviceModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<template>
<modals-modal ref="modal" v-model="show" name="ereader-device-edit" :width="800" :height="'unset'" :processing="processing">
<template #outer>
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
<p class="text-3xl text-white truncate">{{ title }}</p>
</div>
</template>
<form @submit.prevent="submitForm">
<div class="w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300">
<div class="w-full px-3 py-5 md:p-12">
<div class="flex items-center -mx-1 mb-4">
<div class="w-full md:w-1/2 px-1">
<ui-text-input-with-label ref="ereaderNameInput" v-model="newDevice.name" :disabled="processing" :label="$strings.LabelName" />
</div>
<div class="w-full md:w-1/2 px-1">
<ui-text-input-with-label ref="ereaderEmailInput" v-model="newDevice.email" :disabled="processing" :label="$strings.LabelEmail" />
</div>
</div>

<div class="flex items-center pt-4">
<div class="flex-grow" />
<ui-btn color="success" type="submit">{{ $strings.ButtonSubmit }}</ui-btn>
</div>
</div>
</div>
</form>
</modals-modal>
</template>

<script>
export default {
props: {
value: Boolean,
existingDevices: {
type: Array,
default: () => []
},
ereaderDevice: {
type: Object,
default: () => null
}
},
data() {
return {
processing: false,
newDevice: {
name: '',
email: '',
availabilityOption: 'adminAndUp',
users: []
}
}
},
watch: {
show: {
handler(newVal) {
if (newVal) {
this.init()
}
}
}
},
computed: {
show: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
},
user() {
return this.$store.state.user.user
},
title() {
return !this.ereaderDevice ? 'Create Device' : 'Update Device'
}
},
methods: {
submitForm() {
this.$refs.ereaderNameInput.blur()
this.$refs.ereaderEmailInput.blur()

if (!this.newDevice.name?.trim() || !this.newDevice.email?.trim()) {
this.$toast.error(this.$strings.ToastNameEmailRequired)
return
}

this.newDevice.name = this.newDevice.name.trim()
this.newDevice.email = this.newDevice.email.trim()

// Only catches duplicate names for the current user
// Duplicates with other users caught on server side
if (!this.ereaderDevice) {
if (this.existingDevices.some((d) => d.name === this.newDevice.name)) {
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
return
}

this.submitCreate()
} else {
if (this.ereaderDevice.name !== this.newDevice.name && this.existingDevices.some((d) => d.name === this.newDevice.name)) {
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
return
}

this.submitUpdate()
}
},
submitUpdate() {
this.processing = true

const existingDevicesWithoutThisOne = this.existingDevices.filter((d) => d.name !== this.ereaderDevice.name)

const payload = {
ereaderDevices: [
...existingDevicesWithoutThisOne,
{
...this.newDevice
}
]
}

this.$axios
.$post(`/api/me/ereader-devices`, payload)
.then((data) => {
this.$emit('update', data.ereaderDevices)
this.show = false
})
.catch((error) => {
console.error('Failed to update device', error)
if (error.response?.data?.toLowerCase().includes('duplicate')) {
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
} else {
this.$toast.error(this.$strings.ToastDeviceAddFailed)
}
})
.finally(() => {
this.processing = false
})
},
submitCreate() {
this.processing = true

const payload = {
ereaderDevices: [
...this.existingDevices,
{
...this.newDevice
}
]
}

this.$axios
.$post('/api/me/ereader-devices', payload)
.then((data) => {
this.$emit('update', data.ereaderDevices || [])
this.show = false
})
.catch((error) => {
console.error('Failed to add device', error)
if (error.response?.data?.toLowerCase().includes('duplicate')) {
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
} else {
this.$toast.error(this.$strings.ToastDeviceAddFailed)
}
})
.finally(() => {
this.processing = false
})
},
init() {
if (this.ereaderDevice) {
this.newDevice.name = this.ereaderDevice.name
this.newDevice.email = this.ereaderDevice.email
this.newDevice.availabilityOption = this.ereaderDevice.availabilityOption || 'specificUsers'
this.newDevice.users = this.ereaderDevice.users || [this.user.id]
} else {
this.newDevice.name = ''
this.newDevice.email = ''
this.newDevice.availabilityOption = 'specificUsers'
this.newDevice.users = [this.user.id]
}
}
},
mounted() {}
}
</script>
98 changes: 97 additions & 1 deletion client/pages/account.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,48 @@
</form>
</div>

<div v-if="showEreaderTable">
<div class="w-full h-px bg-white/10 my-4" />

<app-settings-content :header-text="$strings.HeaderEreaderDevices">
<template #header-items>
<div class="flex-grow" />

<ui-btn color="primary" small @click="addNewDeviceClick">{{ $strings.ButtonAddDevice }}</ui-btn>
</template>

<table v-if="ereaderDevices.length" class="tracksTable mt-4">
<tr>
<th class="text-left">{{ $strings.LabelName }}</th>
<th class="text-left">{{ $strings.LabelEmail }}</th>
<th class="w-40"></th>
</tr>
<tr v-for="device in ereaderDevices" :key="device.name">
<td>
<p class="text-sm md:text-base text-gray-100">{{ device.name }}</p>
</td>
<td class="text-left">
<p class="text-sm md:text-base text-gray-100">{{ device.email }}</p>
</td>
<td class="w-40">
<div class="flex justify-end items-center h-10">
<ui-icon-btn icon="edit" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name || device.users?.length !== 1" class="mx-1" @click="editDeviceClick(device)" />
<ui-icon-btn icon="delete" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name || device.users?.length !== 1" @click="deleteDeviceClick(device)" />
</div>
</td>
</tr>
</table>
<div v-else-if="!loading" class="text-center py-4">
<p class="text-lg text-gray-100">{{ $strings.MessageNoDevices }}</p>
</div>
</app-settings-content>
</div>

<div class="py-4 mt-8 flex">
<ui-btn color="primary flex items-center text-lg" @click="logout"><span class="material-symbols mr-4 icon-text">logout</span>{{ $strings.ButtonLogout }}</ui-btn>
</div>

<modals-emails-user-e-reader-device-modal v-model="showEReaderDeviceModal" :existing-devices="revisedEreaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" />
</div>
</div>
</template>
Expand All @@ -43,11 +82,20 @@
export default {
data() {
return {
loading: false,
password: null,
newPassword: null,
confirmPassword: null,
changingPassword: false,
selectedLanguage: ''
selectedLanguage: '',
newEReaderDevice: {
name: '',
email: ''
},
ereaderDevices: [],
deletingDeviceName: null,
selectedEReaderDevice: null,
showEReaderDeviceModal: false
}
},
computed: {
Expand Down Expand Up @@ -75,6 +123,12 @@ export default {
},
showChangePasswordForm() {
return !this.isGuest && this.isPasswordAuthEnabled
},
showEreaderTable() {
return this.usertype !== 'root' && this.usertype !== 'admin' && this.user.permissions?.createEreader
},
revisedEreaderDevices() {
return this.ereaderDevices.filter((device) => device.users?.length === 1)
}
},
methods: {
Expand Down Expand Up @@ -142,10 +196,52 @@ export default {
this.$toast.error(this.$strings.ToastUnknownError)
this.changingPassword = false
})
},
addNewDeviceClick() {
this.selectedEReaderDevice = null
this.showEReaderDeviceModal = true
},
editDeviceClick(device) {
this.selectedEReaderDevice = device
this.showEReaderDeviceModal = true
},
deleteDeviceClick(device) {
const payload = {
message: this.$getString('MessageConfirmDeleteDevice', [device.name]),
callback: (confirmed) => {
if (confirmed) {
this.deleteDevice(device)
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
deleteDevice(device) {
const payload = {
ereaderDevices: this.revisedEreaderDevices.filter((d) => d.name !== device.name)
}
this.deletingDeviceName = device.name
this.$axios
.$post(`/api/me/ereader-devices`, payload)
.then((data) => {
this.ereaderDevicesUpdated(data.ereaderDevices)
})
.catch((error) => {
console.error('Failed to delete device', error)
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.deletingDeviceName = null
})
},
ereaderDevicesUpdated(ereaderDevices) {
this.ereaderDevices = ereaderDevices
}
},
mounted() {
this.selectedLanguage = this.$languageCodes.current
this.ereaderDevices = this.$store.state.libraries.ereaderDevices || []
}
}
</script>
1 change: 1 addition & 0 deletions client/strings/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@
"LabelPermissionsAccessAllLibraries": "Can Access All Libraries",
"LabelPermissionsAccessAllTags": "Can Access All Tags",
"LabelPermissionsAccessExplicitContent": "Can Access Explicit Content",
laustindasauce marked this conversation as resolved.
Show resolved Hide resolved
"LabelPermissionsCreateEreader": "Can Create Ereader",
"LabelPermissionsDelete": "Can Delete",
"LabelPermissionsDownload": "Can Download",
"LabelPermissionsUpdate": "Can Update",
Expand Down
Loading
Loading