feat: translate sso

This commit is contained in:
Abhinav Raut
2025-03-31 19:09:25 +05:30
parent dd8053b2bb
commit 19f08ec76a
7 changed files with 83 additions and 61 deletions

View File

@@ -2,11 +2,11 @@
<form @submit="onSubmit" class="space-y-6">
<FormField v-slot="{ componentField }" name="provider">
<FormItem>
<FormLabel>Provider</FormLabel>
<FormLabel>{{ $t('form.field.provider') }}</FormLabel>
<FormControl>
<Select v-bind="componentField">
<SelectTrigger>
<SelectValue placeholder="Select a provider" />
<SelectValue :placeholder="t('form.field.selectProvider')" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -22,7 +22,7 @@
<FormField v-slot="{ componentField }" name="name">
<FormItem v-auto-animate>
<FormLabel>Name</FormLabel>
<FormLabel>{{ $t('form.field.name') }}</FormLabel>
<FormControl>
<Input type="text" placeholder="Google" v-bind="componentField" />
</FormControl>
@@ -32,9 +32,9 @@
<FormField v-slot="{ componentField }" name="provider_url">
<FormItem v-auto-animate>
<FormLabel>Provider URL</FormLabel>
<FormLabel>{{ $t('form.field.providerURL') }}</FormLabel>
<FormControl>
<Input type="text" placeholder="Provider URL" v-bind="componentField" />
<Input type="text" placeholder="https://accounts.google.com" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
@@ -42,9 +42,9 @@
<FormField v-slot="{ componentField }" name="client_id">
<FormItem v-auto-animate>
<FormLabel>Client ID</FormLabel>
<FormLabel>{{ $t('form.field.clientID') }}</FormLabel>
<FormControl>
<Input type="text" placeholder="Client ID" v-bind="componentField" />
<Input type="text" placeholder="" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
@@ -52,9 +52,9 @@
<FormField v-slot="{ componentField }" name="client_secret">
<FormItem v-auto-animate>
<FormLabel>Client Secret</FormLabel>
<FormLabel>{{ $t('form.field.clientSecret') }}</FormLabel>
<FormControl>
<Input type="password" placeholder="Client Secret" v-bind="componentField" />
<Input type="password" placeholder="" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
@@ -62,11 +62,11 @@
<FormField v-slot="{ componentField }" name="redirect_uri" v-if="!isNewForm">
<FormItem v-auto-animate>
<FormLabel>Callback URL</FormLabel>
<FormLabel>{{ $t('form.field.callbackURL') }}</FormLabel>
<FormControl>
<Input type="text" placeholder="Redirect URL" v-bind="componentField" readonly />
<Input type="text" placeholder="" v-bind="componentField" readonly />
</FormControl>
<FormDescription>Set this URI for callback.</FormDescription>
<FormDescription>{{ $t('admin.sso.setThisUrlForCallback') }}</FormDescription>
<FormMessage />
</FormItem>
</FormField>
@@ -76,10 +76,9 @@
<FormControl>
<div class="flex items-center space-x-2">
<Checkbox :checked="value" @update:checked="handleChange" />
<Label>Enabled</Label>
<Label>{{ $t('form.field.enabled') }}</Label>
</div>
</FormControl>
<FormDescription></FormDescription>
<FormMessage />
</FormItem>
</FormField>
@@ -93,10 +92,11 @@ import { watch } from 'vue'
import { Button } from '@/components/ui/button'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { oidcLoginFormSchema } from './formSchema.js'
import { createFormSchema } from './formSchema.js'
import { Checkbox } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'
import { vAutoAnimate } from '@formkit/auto-animate/vue'
import { useI18n } from 'vue-i18n'
import {
FormControl,
FormField,
@@ -127,7 +127,7 @@ const props = defineProps({
submitLabel: {
type: String,
required: false,
default: () => 'Save'
default: () => ''
},
isNewForm: {
type: Boolean
@@ -137,9 +137,12 @@ const props = defineProps({
required: false
}
})
const { t } = useI18n()
const submitLabel = props.submitLabel || t('globals.buttons.save')
const form = useForm({
validationSchema: toTypedSchema(oidcLoginFormSchema)
validationSchema: toTypedSchema(createFormSchema(t)),
})
const onSubmit = form.handleSubmit((values) => {

View File

@@ -2,11 +2,11 @@ import { h } from 'vue'
import dropdown from './dataTableDropdown.vue'
import { format } from 'date-fns'
export const columns = [
export const createColumns = (t) => [
{
accessorKey: 'name',
header: function () {
return h('div', { class: 'text-center' }, 'Name')
return h('div', { class: 'text-center' }, t('form.field.name'))
},
cell: function ({ row }) {
return h('div', { class: 'text-center font-medium' }, row.getValue('name'))
@@ -15,7 +15,7 @@ export const columns = [
{
accessorKey: 'provider',
header: function () {
return h('div', { class: 'text-center' }, 'Provider')
return h('div', { class: 'text-center' }, t('form.field.provider'))
},
cell: function ({ row }) {
return h('div', { class: 'text-center font-medium' }, row.getValue('provider'))
@@ -23,16 +23,16 @@ export const columns = [
},
{
accessorKey: 'enabled',
header: () => h('div', { class: 'text-center' }, 'Enabled'),
header: () => h('div', { class: 'text-center' }, t('form.field.enabled')),
cell: ({ row }) => {
const enabled = row.getValue('enabled')
return h('div', { class: 'text-center' }, enabled ? 'Yes' : 'No')
return h('div', { class: 'text-center' }, enabled ? t('globals.messages.yes') : t('globals.messages.no'))
}
},
{
accessorKey: 'updated_at',
header: function () {
return h('div', { class: 'text-center' }, 'Updated at')
return h('div', { class: 'text-center' }, t('form.field.updatedAt'))
},
cell: function ({ row }) {
return h('div', { class: 'text-center' }, format(row.getValue('updated_at'), 'PPpp'))

View File

@@ -2,30 +2,35 @@
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="ghost" class="w-8 h-8 p-0">
<span class="sr-only">Open menu</span>
<span class="sr-only"></span>
<MoreHorizontal class="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem :as-child="true">
<RouterLink :to="{ name: 'edit-sso', params: { id: props.role.id } }">Edit</RouterLink>
<RouterLink :to="{ name: 'edit-sso', params: { id: props.role.id } }">
{{ $t('globals.buttons.edit') }}
</RouterLink>
</DropdownMenuItem>
<DropdownMenuItem @click="() => (alertOpen = true)">Delete</DropdownMenuItem>
<DropdownMenuItem @click="() => (alertOpen = true)">{{
$t('globals.buttons.delete')
}}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<AlertDialog :open="alertOpen" @update:open="alertOpen = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete OIDC Provider</AlertDialogTitle>
<AlertDialogTitle>{{ $t('globals.messages.areYouAbsolutelySure') }}</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the OIDC provider
configuration.
{{ $t('admin.sso.deleteConfirmation') }}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction @click="handleDelete">Delete</AlertDialogAction>
<AlertDialogCancel>{{ $t('globals.buttons.cancel') }}</AlertDialogCancel>
<AlertDialogAction @click="handleDelete">
{{ $t('globals.buttons.delete') }}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>

View File

@@ -1,20 +1,24 @@
import * as z from 'zod'
export const oidcLoginFormSchema = z.object({
export const createFormSchema = (t) => z.object({
disabled: z.boolean().optional(),
name: z.string({
required_error: 'Name is required.'
required_error: t('globals.messages.required'),
}),
provider: z.string().optional(),
provider_url: z
.string({
required_error: 'Provider URL is required.'
required_error: t('globals.messages.required'),
})
.url({
message: 'Provider URL must be a valid URL.'
message: t('form.error.validUrl'),
}),
client_id: z.string(),
client_secret: z.string(),
client_id: z.string({
required_error: t('globals.messages.required'),
}),
client_secret: z.string({
required_error: t('globals.messages.required'),
}),
redirect_uri: z.string().readonly().optional(),
enabled: z.boolean().default(true).optional(),
})

View File

@@ -2,7 +2,7 @@
<div class="mb-5">
<CustomBreadcrumb :links="breadcrumbLinks" />
</div>
<Spinner v-if="isLoading"></Spinner>
<Spinner v-if="isLoading" />
<OIDCForm
:initial-values="oidc"
:submitForm="submitForm"
@@ -21,9 +21,11 @@ import { CustomBreadcrumb } from '@/components/ui/breadcrumb'
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
import { useEmitter } from '@/composables/useEmitter'
import { handleHTTPError } from '@/utils/http'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
const router = useRouter()
const { t } = useI18n()
const oidc = ref({
provider: 'Google'
})
@@ -47,7 +49,6 @@ const submitForm = async (values) => {
} catch (error) {
formLoading.value = false
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
})
@@ -61,37 +62,32 @@ const submitForm = async (values) => {
values.client_secret = ''
}
await api.updateOIDC(props.id, values)
toastDescription = 'Provider updated successfully'
toastDescription = t('globals.messages.updatedSuccessfully', {
name: t('globals.entities.provider')
})
} else {
await api.createOIDC(values)
router.push({ name: 'sso-list' })
toastDescription = 'Provider created successfully'
toastDescription = t('globals.messages.createdSuccessfully', {
name: t('globals.entities.provider')
})
}
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Success',
description: toastDescription
})
} catch (error) {
if (props.id) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error reloading OIDC providers',
variant: 'destructive',
description: handleHTTPError(error).message
})
} else {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
})
}
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
variant: 'destructive',
description: handleHTTPError(error).message
})
} finally {
formLoading.value = false
}
}
const breadCrumLabel = () => {
return props.id ? 'Edit' : 'New'
return props.id ? t('globals.buttons.edit') : t('globals.buttons.new')
}
const isNewForm = computed(() => {
@@ -99,7 +95,7 @@ const isNewForm = computed(() => {
})
const breadcrumbLinks = [
{ path: 'sso-list', label: 'SSO' },
{ path: 'sso-list', label: t('globals.entities.sso') },
{ path: '', label: breadCrumLabel() }
]
@@ -111,7 +107,6 @@ onMounted(async () => {
oidc.value = resp.data.data
} catch (error) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
})

View File

@@ -5,12 +5,16 @@
<div></div>
<div>
<RouterLink :to="{ name: 'new-sso' }">
<Button>New SSO</Button>
<Button>{{
$t('globals.messages.new', {
name: $t('globals.entities.sso')
})
}}</Button>
</RouterLink>
</div>
</div>
<div>
<DataTable :columns="columns" :data="oidc" />
<DataTable :columns="createColumns(t)" :data="oidc" />
</div>
</div>
</template>
@@ -18,15 +22,16 @@
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import DataTable from '@/components/datatable/DataTable.vue'
import { columns } from '@/features/admin/oidc/dataTableColumns.js'
import { createColumns } from '@/features/admin/oidc/dataTableColumns.js'
import { Button } from '@/components/ui/button'
import { useEmitter } from '@/composables/useEmitter'
import { useI18n } from 'vue-i18n'
import { Spinner } from '@/components/ui/spinner'
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
import api from '@/api'
const oidc = ref([])
const { t } = useI18n()
const isLoading = ref(false)
const emit = useEmitter()

View File

@@ -48,6 +48,7 @@
"globals.entities.view": "View | Views",
"globals.entities.email": "Email | Emails",
"globals.entities.condition": "Condition | Conditions",
"globals.entities.sso": "SSO | SSOs",
"globals.messages.adjustFilters": "Try adjusting your filters",
"globals.messages.errorUploadingFile": "Error uploading file",
"globals.messages.errorUpdating": "Error updating {name}",
@@ -218,6 +219,11 @@
"navigation.edit": "Edit",
"navigation.delete": "Delete",
"form.field.name": "Name",
"form.field.provider": "Provider",
"form.field.providerURL": "Provider URL",
"form.field.clientID": "Client ID",
"form.field.clientSecret": "Client Secret",
"form.field.callbackURL": "Callback URL",
"form.field.subject": "Subject",
"form.field.isDefault": "Is default",
"form.field.body": "Body",
@@ -254,6 +260,7 @@
"form.field.selectTeam": "Select team",
"form.field.selectTag": "Select tag",
"form.field.selectAction": "Select action",
"form.field.selectProvider": "Select provider",
"form.field.selectUser": "Select user",
"form.field.selectValue": "Select value",
"form.field.selectTeams": "Select teams",
@@ -268,6 +275,7 @@
"form.error.name.required": "Name is required",
"form.error.description.required": "Description is required",
"form.error.time.invalid": "Invalid time format (HH:mm)",
"form.error.validUrl": "Invalid URL",
"admin.general.updated": "Settings updated successfully",
"admin.general.site_name": "Site Name",
"admin.general.site_name.description": "Name for your support desk.",
@@ -488,6 +496,8 @@
"admin.template.deleteConfirmation": "This action cannot be undone. This will permanently delete this template.",
"admin.template.makeSureTemplateHasContent": "Make sure the template has {content} only once.",
"admin.template.onlyOneDefaultOutgoingTemplate": "You can have only one default outgoing email template.",
"admin.sso.deleteConfirmation": "This action cannot be undone. This will permanently delete this SSO configuration.",
"admin.sso.setThisUrlForCallback": "Set this URI for callback.",
"globals.buttons.save": "Save",
"globals.buttons.save_changes": "Save changes",
"globals.buttons.cancel": "Cancel",