refactor(twofactor_backupcodes): migrate frontend to Typescript

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2025-10-28 18:19:11 +01:00
parent 095e4709b7
commit 95f2d5dbac
No known key found for this signature in database
GPG key ID: 45FAE7268762B400
9 changed files with 142 additions and 119 deletions

View file

@ -1,16 +0,0 @@
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import Axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
/**
*
*/
export function generateCodes() {
const url = generateUrl('/apps/twofactor_backupcodes/settings/create')
return Axios.post(url, {}).then((resp) => resp.data)
}

View file

@ -0,0 +1,28 @@
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
export interface ITwoFactorBackupCodesState {
enabled: boolean
total: number
used: number
}
export interface IApiResponse {
codes: string[]
state: ITwoFactorBackupCodesState
}
/**
* Generate new backup codes
*/
export async function generateCodes(): Promise<IApiResponse> {
const url = generateUrl('/apps/twofactor_backupcodes/settings/create')
const { data } = await axios.post<IApiResponse>(url)
return data
}

View file

@ -1,16 +0,0 @@
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/**
* @param {any} data -
*/
export function print(data) {
const name = OC.theme.name || 'Nextcloud'
const newTab = window.open('', t('twofactor_backupcodes', '{name} backup codes', { name }))
newTab.document.write('<h1>' + t('twofactor_backupcodes', '{name} backup codes', { name }) + '</h1>')
newTab.document.write('<pre>' + data + '</pre>')
newTab.print()
newTab.close()
}

View file

@ -0,0 +1,39 @@
/*!
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getCapabilities } from '@nextcloud/capabilities'
import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
/**
* Open a new tab and print the given backup codes
*
* @param data - The backup codes to print
*/
export function print(data: string[]): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const name = (getCapabilities() as any).theming.name || 'Nextcloud'
const newTab = window.open('', t('twofactor_backupcodes', '{name} backup codes', { name }))
if (!newTab) {
showError(t('twofactor_backupcodes', 'Unable to open a new tab for printing'))
throw new Error('Unable to open a new tab for printing')
}
const heading = newTab.document.createElement('h1')
heading.textContent = t('twofactor_backupcodes', '{name} backup codes', { name })
const pre = newTab.document.createElement('pre')
for (const code of data) {
const codeLine = newTab.document.createTextNode(code)
pre.appendChild(codeLine)
pre.appendChild(newTab.document.createElement('br'))
}
newTab.document.body.innerHTML = ''
newTab.document.body.appendChild(heading)
newTab.document.body.appendChild(pre)
newTab.print()
newTab.close()
}

View file

@ -3,17 +3,17 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { loadState } from '@nextcloud/initial-state'
import { createPinia, PiniaVuePlugin } from 'pinia'
import Vue from 'vue'
import PersonalSettings from './views/PersonalSettings.vue'
import store from './store.js'
Vue.prototype.t = t
Vue.use(PiniaVuePlugin)
const initialState = loadState('twofactor_backupcodes', 'state')
store.replaceState(initialState)
const pinia = createPinia()
const View = Vue.extend(PersonalSettings)
new View({
store,
}).$mount('#twofactor-backupcodes-settings')
const app = new View({
pinia,
})
app.$mount('#twofactor-backupcodes-settings')

View file

@ -1,53 +0,0 @@
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import Vue from 'vue'
import Vuex, { Store } from 'vuex'
import { generateCodes } from './service/BackupCodesService.js'
Vue.use(Vuex)
const state = {
enabled: false,
total: 0,
used: 0,
codes: [],
}
const mutations = {
setEnabled(state, enabled) {
Vue.set(state, 'enabled', enabled)
},
setTotal(state, total) {
Vue.set(state, 'total', total)
},
setUsed(state, used) {
Vue.set(state, 'used', used)
},
setCodes(state, codes) {
Vue.set(state, 'codes', codes)
},
}
const actions = {
generate({ commit }) {
commit('setEnabled', false)
return generateCodes().then(({ codes, state }) => {
commit('setEnabled', state.enabled)
commit('setTotal', state.total)
commit('setUsed', state.used)
commit('setCodes', codes)
return true
})
},
}
export default new Store({
strict: !PRODUCTION,
state,
mutations,
actions,
})

View file

@ -0,0 +1,42 @@
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { ITwoFactorBackupCodesState } from '../service/BackupCodesService.ts'
import { loadState } from '@nextcloud/initial-state'
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { generateCodes } from '../service/BackupCodesService.ts'
const initialState = loadState<ITwoFactorBackupCodesState>('twofactor_backupcodes', 'state')
export const useStore = defineStore('twofactor_backupcodes', () => {
const enabled = ref(initialState.enabled)
const total = ref(initialState.total)
const used = ref(initialState.used)
const codes = ref<string[]>([])
/**
* Generate new backup codes and update the store state
*/
async function generate(): Promise<void> {
enabled.value = false
const { codes: newCodes, state } = await generateCodes()
enabled.value = state.enabled
total.value = state.total
used.value = state.used
codes.value = newCodes
}
return {
enabled,
total,
used,
codes,
generate,
}
})

View file

@ -59,11 +59,13 @@
</template>
<script>
import { showError } from '@nextcloud/dialogs'
import { confirmPassword } from '@nextcloud/password-confirmation'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import { logger } from '../logger.ts'
import { logger } from '../service/logger.ts'
import { print } from '../service/PrintService.js'
import { useStore } from '../store/index.ts'
export default {
name: 'PersonalSettings',
@ -72,6 +74,12 @@ export default {
NcLoadingIcon,
},
setup() {
return {
store: useStore(),
}
},
data() {
return {
generatingCodes: false,
@ -94,19 +102,19 @@ export default {
},
enabled() {
return this.$store.state.enabled
return this.store.enabled
},
total() {
return this.$store.state.total
return this.store.total
},
used() {
return this.$store.state.used
return this.store.used
},
codes() {
return this.$store.state.codes
return this.store.codes
},
name() {
@ -119,32 +127,23 @@ export default {
},
methods: {
generateBackupCodes() {
confirmPassword().then(() => {
// Hide old codes
this.generatingCodes = true
async generateBackupCodes() {
await confirmPassword()
// Hide old codes
this.generatingCodes = true
this.$store.dispatch('generate').then(() => {
this.generatingCodes = false
}).catch((err) => {
OC.Notification.showTemporary(t('twofactor_backupcodes', 'An error occurred while generating your backup codes'))
this.generatingCodes = false
throw err
})
}).catch(logger.error)
},
getPrintData(codes) {
if (!codes) {
return ''
try {
await this.store.generate()
} catch (error) {
logger.error('Error generating backup codes', { error })
showError(t('twofactor_backupcodes', 'An error occurred while generating your backup codes'))
} finally {
this.generatingCodes = false
}
return codes.reduce((prev, code) => {
return prev + code + '<br>'
}, '')
},
printCodes() {
print(this.getPrintData(this.codes))
print(!this.codes || this.codes.length === 0 ? [] : this.codes)
},
},
}