feat: translate /admin/users

This commit is contained in:
Abhinav Raut
2025-03-31 15:13:06 +05:30
parent 25f23735d5
commit 6bea14e7a9
16 changed files with 147 additions and 84 deletions

View File

@@ -136,7 +136,7 @@
{{
holidayDate && !isNaN(new Date(holidayDate).getTime())
? format(new Date(holidayDate), 'MMMM dd, yyyy')
: t('form.field.pick_a_date')
: t('form.field.pickDate')
}}
</Button>
</PopoverTrigger>

View File

@@ -20,7 +20,7 @@
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{{ t('admin.business_hours.delete_confirmation_title') }}
{{ t('globals.messages.areYouAbsolutelySure') }}
</AlertDialogTitle>
<AlertDialogDescription>
{{ t('admin.business_hours.delete_confirmation') }}

View File

@@ -25,7 +25,7 @@
<AlertDialog :open="alertOpen" @update:open="alertOpen = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{{ $t('admin.inbox.delete_confirmation_title') }}</AlertDialogTitle>
<AlertDialogTitle>{{ $t('globals.messages.areYouAbsolutelySure') }}</AlertDialogTitle>
<AlertDialogDescription>
{{ $t('admin.inbox.delete_confirmation') }}
</AlertDialogDescription>

View File

@@ -17,7 +17,7 @@
<AlertDialog :open="isDeleteOpen" @update:open="isDeleteOpen = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{{ $t('admin.macro.delete_confirmation_title') }}</AlertDialogTitle>
<AlertDialogTitle>{{ $t('globals.messages.areYouAbsolutelySure') }}</AlertDialogTitle>
<AlertDialogDescription>
{{ $t('admin.macro.delete_confirmation') }}
</AlertDialogDescription>

View File

@@ -15,7 +15,7 @@
<AlertDialog :open="alertOpen" @update:open="alertOpen = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{{ t('admin.sla.delete_confirmation_title') }}</AlertDialogTitle>
<AlertDialogTitle>{{ t('globals.messages.areYouAbsolutelySure') }}</AlertDialogTitle>
<AlertDialogDescription>
{{ t('admin.sla.delete_confirmation') }}
</AlertDialogDescription>

View File

@@ -42,7 +42,7 @@
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{{ $t('admin.conversation_status.delete_confirmation_title') }}</AlertDialogTitle
{{ $t('globals.messages.areYouAbsolutelySure') }}</AlertDialogTitle
>
<AlertDialogDescription>
{{ $t('admin.conversation_status.delete_confirmation') }}

View File

@@ -34,7 +34,7 @@
<AlertDialog :open="alertOpen" @update:open="alertOpen = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{{t('admin.conversation_tags.delete_confirmation_title')}}</AlertDialogTitle>
<AlertDialogTitle>{{t('globals.messages.areYouAbsolutelySure')}}</AlertDialogTitle>
<AlertDialogDescription>
{{t('admin.conversation_tags.delete_confirmation')}}
</AlertDialogDescription>

View File

@@ -2,9 +2,9 @@
<form @submit.prevent="onSubmit" class="space-y-6">
<FormField v-slot="{ field }" name="first_name">
<FormItem v-auto-animate>
<FormLabel>First name</FormLabel>
<FormLabel>{{ $t('form.field.firstName') }}</FormLabel>
<FormControl>
<Input type="text" placeholder="First name" v-bind="field" />
<Input type="text" placeholder="" v-bind="field" />
</FormControl>
<FormMessage />
</FormItem>
@@ -12,9 +12,9 @@
<FormField v-slot="{ field }" name="last_name">
<FormItem>
<FormLabel>Last name</FormLabel>
<FormLabel>{{ $t('form.field.lastName') }}</FormLabel>
<FormControl>
<Input type="text" placeholder="Last name" v-bind="field" />
<Input type="text" placeholder="" v-bind="field" />
</FormControl>
<FormMessage />
</FormItem>
@@ -22,19 +22,24 @@
<FormField v-slot="{ field }" name="email">
<FormItem v-auto-animate>
<FormLabel>Email</FormLabel>
<FormLabel>{{ $t('form.field.email') }}</FormLabel>
<FormControl>
<Input type="email" placeholder="Email" v-bind="field" />
<Input type="email" placeholder="" v-bind="field" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField , handleChange }" name="teams">
<FormField v-slot="{ componentField, handleChange }" name="teams">
<FormItem v-auto-animate>
<FormLabel>Teams</FormLabel>
<FormLabel>{{ $t('form.field.teams') }}</FormLabel>
<FormControl>
<SelectTag :items="teamOptions" placeholder="Select teams" v-model="componentField.modelValue" @update:modelValue="handleChange"/>
<SelectTag
:items="teamOptions"
:placeholder="t('form.field.selectTeams')"
v-model="componentField.modelValue"
@update:modelValue="handleChange"
/>
</FormControl>
<FormMessage />
</FormItem>
@@ -42,9 +47,14 @@
<FormField v-slot="{ componentField, handleChange }" name="roles">
<FormItem v-auto-animate>
<FormLabel>Roles</FormLabel>
<FormLabel>{{ $t('form.field.roles') }}</FormLabel>
<FormControl>
<SelectTag :items="roleOptions" placeholder="Select roles" v-model="componentField.modelValue" @update:modelValue="handleChange"/>
<SelectTag
:items="roleOptions"
:placeholder="t('form.field.selectRoles')"
v-model="componentField.modelValue"
@update:modelValue="handleChange"
/>
</FormControl>
<FormMessage />
</FormItem>
@@ -52,9 +62,9 @@
<FormField v-slot="{ field }" name="new_password" v-if="!isNewForm">
<FormItem v-auto-animate>
<FormLabel>Set password</FormLabel>
<FormLabel>{{ t('form.field.setPassword') }}</FormLabel>
<FormControl>
<Input type="password" placeholder="Password" v-bind="field" />
<Input type="password" placeholder="" v-bind="field" />
</FormControl>
<FormMessage />
</FormItem>
@@ -65,7 +75,7 @@
<FormControl>
<div class="flex items-center space-x-2">
<Checkbox :checked="value" @update:checked="handleChange" />
<Label>Send welcome email?</Label>
<Label>{{ $t('form.field.sendWelcomeEmail') }}</Label>
</div>
</FormControl>
<FormMessage />
@@ -78,7 +88,7 @@
<Checkbox :checked="value" @update:checked="handleChange" />
</FormControl>
<div class="space-y-1 leading-none">
<FormLabel> Enabled </FormLabel>
<FormLabel> {{ $t('form.field.enabled') }} </FormLabel>
<FormMessage />
</div>
</FormItem>
@@ -93,13 +103,14 @@ import { watch, onMounted, ref, computed } from 'vue'
import { Button } from '@/components/ui/button'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { userFormSchema } 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 { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { SelectTag } from '@/components/ui/select'
import { Input } from '@/components/ui/input'
import { useI18n } from 'vue-i18n'
import api from '@/api'
const props = defineProps({
@@ -126,7 +137,7 @@ const props = defineProps({
required: false
}
})
const { t } = useI18n()
const teams = ref([])
const roles = ref([])
@@ -140,11 +151,15 @@ onMounted(async () => {
}
})
const teamOptions = computed(() => teams.value.map((team) => ({ label: team.name, value: team.name })))
const roleOptions = computed(() => roles.value.map((role) => ({ label: role.name, value: role.name })))
const teamOptions = computed(() =>
teams.value.map((team) => ({ label: team.name, value: team.name }))
)
const roleOptions = computed(() =>
roles.value.map((role) => ({ label: role.name, value: role.name }))
)
const form = useForm({
validationSchema: toTypedSchema(userFormSchema)
validationSchema: toTypedSchema(createFormSchema(t))
})
const onSubmit = form.handleSubmit((values) => {

View File

@@ -2,11 +2,11 @@ import { h } from 'vue'
import UserDataTableDropDown from '@/features/admin/users/dataTableDropdown.vue'
import { format } from 'date-fns'
export const columns = [
export const createColumns = (t) => [
{
accessorKey: 'first_name',
header: function () {
return h('div', { class: 'text-center' }, 'First name')
return h('div', { class: 'text-center' }, t('form.field.firstName'))
},
cell: function ({ row }) {
return h('div', { class: 'text-center font-medium' }, row.getValue('first_name'))
@@ -15,7 +15,7 @@ export const columns = [
{
accessorKey: 'last_name',
header: function () {
return h('div', { class: 'text-center' }, 'Last name')
return h('div', { class: 'text-center' }, t('form.field.lastName'))
},
cell: function ({ row }) {
return h('div', { class: 'text-center font-medium' }, row.getValue('last_name'))
@@ -24,16 +24,16 @@ export const columns = [
{
accessorKey: 'enabled',
header: function () {
return h('div', { class: 'text-center' }, 'Enabled')
return h('div', { class: 'text-center' }, t('form.field.enabled'))
},
cell: function ({ row }) {
return h('div', { class: 'text-center font-medium' }, row.getValue('enabled') ? 'Yes' : 'No')
return h('div', { class: 'text-center font-medium' }, row.getValue('enabled') ? t('globals.messages.yes') : t('globals.messages.no'))
}
},
{
accessorKey: 'email',
header: function () {
return h('div', { class: 'text-center' }, 'Email')
return h('div', { class: 'text-center' }, t('form.field.email'))
},
cell: function ({ row }) {
return h('div', { class: 'text-center font-medium' }, row.getValue('email'))
@@ -42,7 +42,7 @@ export const columns = [
{
accessorKey: 'created_at',
header: function () {
return h('div', { class: 'text-center' }, 'Created at')
return h('div', { class: 'text-center' }, t('form.field.createdAt'))
},
cell: function ({ row }) {
return h(
@@ -55,7 +55,7 @@ export const columns = [
{
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(

View File

@@ -2,27 +2,31 @@
<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 @click="editUser(props.user.id)">Edit</DropdownMenuItem>
<DropdownMenuItem @click="() => (alertOpen = true)">Delete</DropdownMenuItem>
<DropdownMenuItem @click="editUser(props.user.id)">{{
$t('globals.buttons.edit')
}}</DropdownMenuItem>
<DropdownMenuItem @click="() => (alertOpen = true)">{{
$t('globals.buttons.delete')
}}</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<AlertDialog :open="alertOpen" @update:open="alertOpen = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete User</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the user.
</AlertDialogDescription>
<AlertDialogTitle>{{ $t('globals.messages.areYouAbsolutelySure') }}</AlertDialogTitle>
<AlertDialogDescription>{{ $t('admin.user.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>
@@ -79,7 +83,6 @@ async function handleDelete() {
emitRefreshUserList()
} catch (error) {
emit.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
})

View File

@@ -1,34 +1,48 @@
import * as z from 'zod'
export const userFormSchema = z.object({
export const createFormSchema = (t) => z.object({
first_name: z
.string({
required_error: 'First name is required.'
required_error: t('globals.messages.required'),
})
.min(2, {
message: 'First name must be at least 2 characters.'
message: t('form.error.minmax', {
min: 2,
max: 50,
})
})
.max(50, {
message: t('form.error.minmax', {
min: 2,
max: 50,
})
}),
last_name: z.string().optional(),
email: z
.string({
required_error: 'Email is required.'
required_error: t('globals.messages.required'),
})
.email({
message: 'Invalid email address.'
message: t('globals.messages.invalidEmailAddress'),
}),
send_welcome_email: z.boolean().optional(),
teams: z.array(z.string()).default([]),
roles: z.array(z.string()).min(1, 'Please select at least one role.'),
roles: z.array(z.string()).min(1, t('globals.messages.pleaseSelectAtLeastOne', {
name: t('globals.entities.role')
})),
new_password: z
.string()
.regex(/^$|^(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{8,50}$/, {
message: 'Password must be between 8 and 50 characters long, contain at least one uppercase letter and one number.'
.regex(/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[\W_]).{10,72}$/, {
message: t('globals.messages.strongPassword', {
min: 10,
max: 72,
})
})
.optional(),
enabled: z.boolean().optional().default(true)

View File

@@ -13,14 +13,21 @@ import { CustomBreadcrumb } from '@/components/ui/breadcrumb'
import { useRouter } from 'vue-router'
import { useEmitter } from '@/composables/useEmitter'
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
import { useI18n } from 'vue-i18n'
import api from '@/api'
const { t } = useI18n()
const emitter = useEmitter()
const router = useRouter()
const formLoading = ref(false)
const breadcrumbLinks = [
{ path: 'user-list', label: 'Users' },
{ path: '', label: 'Add user' }
{ path: 'user-list', label: t('globals.entities.user', 2) },
{
path: '',
label: t('globals.messages.new', {
name: t('globals.entities.user', 1)
})
}
]
const onSubmit = (values) => {
@@ -32,13 +39,13 @@ const createNewUser = async (values) => {
formLoading.value = true
await api.createUser(values)
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Success',
description: 'User created successfully'
description: t('globals.messages.createdSuccessfully', {
name: t('globals.entities.user', 1)
})
})
router.push({ name: 'user-list' })
} catch (error) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
})

View File

@@ -2,7 +2,7 @@
<div class="mb-5">
<CustomBreadcrumb :links="breadcrumbLinks" />
</div>
<Spinner v-if="isLoading"></Spinner>
<Spinner v-if="isLoading"/>
<UserForm :initialValues="user" :submitForm="submitForm" :isLoading="formLoading" v-else />
</template>
@@ -15,15 +15,22 @@ import { handleHTTPError } from '@/utils/http'
import UserForm from '@/features/admin/users/UserForm.vue'
import { CustomBreadcrumb } from '@/components/ui/breadcrumb'
import { Spinner } from '@/components/ui/spinner'
import { useI18n } from 'vue-i18n'
const user = ref({})
const { t } = useI18n()
const isLoading = ref(false)
const formLoading = ref(false)
const emitter = useEmitter()
const breadcrumbLinks = [
{ path: 'user-list', label: 'Users' },
{ path: '', label: 'Edit user' }
{ path: 'user-list', label: t('globals.entities.user', 2) },
{
path: '',
label: t('globals.messages.edit', {
name: t('globals.entities.user', 1)
})
}
]
const submitForm = (values) => {
@@ -35,12 +42,12 @@ const updateUser = async (payload) => {
formLoading.value = true
await api.updateUser(user.value.id, payload)
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: "Success",
description: "User updated successfully"
description: t('globals.messages.updatedSuccessfully', {
name: t('globals.entities.user', 1)
})
})
} catch (error) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
})
@@ -56,7 +63,6 @@ onMounted(async () => {
user.value = resp.data.data
} catch (error) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
})

View File

@@ -3,18 +3,22 @@
<div :class="{ 'transition-opacity duration-300 opacity-50': isLoading }">
<div class="flex justify-end mb-5">
<router-link :to="{ name: 'new-user' }">
<Button>New User</Button>
<Button>{{
$t('globals.messages.new', {
name: $t('globals.entities.user', 1)
})
}}</Button>
</router-link>
</div>
<div>
<DataTable :columns="columns" :data="data" />
<DataTable :columns="createColumns(t)" :data="data" />
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { columns } from '@/features/admin/users/dataTableColumns.js'
import { createColumns } from '@/features/admin/users/dataTableColumns.js'
import { Button } from '@/components/ui/button'
import DataTable from '@/components/datatable/DataTable.vue'
import { handleHTTPError } from '@/utils/http'
@@ -22,8 +26,10 @@ import { Spinner } from '@/components/ui/spinner'
import { useEmitter } from '@/composables/useEmitter'
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
import api from '@/api'
import { useI18n } from 'vue-i18n'
const isLoading = ref(false)
const { t } = useI18n()
const data = ref([])
const emitter = useEmitter()
@@ -41,7 +47,6 @@ const getData = async () => {
data.value = response.data.data
} catch (error) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
})

View File

@@ -67,13 +67,22 @@
"globals.messages.errorSending": "Error sending {name}",
"globals.messages.errorAlreadyExists": "{name} already exists",
"globals.messages.errorMarshallingMeta": "Error marshalling meta",
"globals.messages.areYouAbsolutelySure": "Are you absolutely sure?",
"globals.messages.errorMarshalling": "Error marshalling",
"globals.messages.errorSearching": "Error searching {name}",
"globals.messages.tooLong": "{name} is too long, should be at most {max} characters",
"globals.messages.deletedSuccessfully": "{name} deleted successfully",
"globals.messages.updatedSuccessfully": "{name} updated successfully",
"globals.messages.edit": "Edit {name}",
"globals.messages.delete": "Delete {name}",
"globals.messages.create": "Create {name}",
"globals.messages.new": "New {name}",
"globals.messages.yes": "Yes",
"globals.messages.no": "No",
"globals.messages.createdSuccessfully": "{name} created successfully",
"globals.messages.invalidEmailAddress": "Invalid email address",
"globals.messages.pleaseSelectAtLeastOne": "Please select at least one {name}",
"globals.messages.strongPassword": "Password must be between {min} and {max} characters long, contain at least one uppercase letter and one number.",
"globals.messages.noResultsFound": "No results found",
"globals.messages.couldNotReload": "Could not reload {name}. Please restart the app",
"globals.messages.invalid": "Invalid {name}",
@@ -210,14 +219,22 @@
"form.field.disabled": "Disabled",
"form.field.port": "Port",
"form.field.host": "Host",
"form.field.firstName": "First Name",
"form.field.lastName": "Last Name",
"form.field.teams": "Teams",
"form.field.roles": "Roles",
"form.field.setPassword": "Set Password",
"form.field.sendWelcomeEmail": "Send Welcome Email?",
"form.field.username": "Username",
"form.field.password": "Password",
"form.field.visibility": "Visibility",
"form.field.usage": "Usage",
"form.field.createdAt": "Created At",
"form.field.updatedAt": "Updated At",
"form.field.pick_a_date": "Pick a date",
"form.field.pickDate": "Pick a date",
"form.field.selectTLS": "Select TLS",
"form.field.selectRoles": "Select roles",
"form.field.selectTeams": "Select teams",
"form.error.min": "Should be at least {min} characters",
"form.error.max": "Should be at most {max} characters",
"form.error.minmax": "Should be between {min} and {max} characters",
@@ -257,7 +274,6 @@
"admin.general.allowed_file_upload_extensions.description": "Allowed file upload extensions. Use `*` to allow all file types.",
"admin.business_hours": "Business Hours",
"admin.business_hours.delete": "Delete Business Hour",
"admin.business_hours.delete_confirmation_title": "Are you sure?",
"admin.business_hours.delete_confirmation": "This action cannot be undone. This will permanently delete this business hour.",
"admin.business_hours.new": "New Business Hour",
"admin.business_hours.new_holiday": "New Holiday",
@@ -274,7 +290,6 @@
"admin.sla.updated": "SLA Policy updated successfully",
"admin.sla.created": "SLA Policy created successfully",
"admin.sla.delete": "Delete SLA Policy",
"admin.sla.delete_confirmation_title": "Are you sure?",
"admin.sla.delete_confirmation": "This action cannot be undone. This will permanently delete this SLA policy.",
"admin.sla.name.valid": "SLA Policy name should be between 1 and 255 characters",
"admin.sla.description.valid": "SLA Policy description should be between 1 and 255 characters",
@@ -306,7 +321,6 @@
"admin.conversation_tags.updated": "Tag updated successfully",
"admin.conversation_tags.created": "Tag created successfully",
"admin.conversation_tags.name.valid": "Tag name should at least 3 characters",
"admin.conversation_tags.delete_confirmation_title": "Are you absolutely sure?",
"admin.conversation_tags.delete_confirmation": "This action cannot be undone. This will permanently delete this tag, and remove it from all conversations.",
"admin.macro.message_content": "Response to be sent when macro is used (optional)",
"admin.macro.message_content.placeholder": "Shift + Enter to add a new line",
@@ -324,7 +338,6 @@
"admin.macro.actionTypeRequired": "Action type is required",
"admin.macro.actionValueRequired": "Action value is required",
"admin.macro.teamOrUserRequired": "team is required when visibility is `team` & a user is required when visibility is `user`",
"admin.macro.delete_confirmation_title": "Are you absolutely sure?",
"admin.macro.delete_confirmation": "This action cannot be undone. This will permanently delete this macro.",
"admin.macro.created": "Macro created successfully",
"admin.macro": "Macros",
@@ -336,7 +349,6 @@
"admin.conversation_status.name.description": "Set status name. Click save when you're done.",
"admin.conversation_status.updated": "Status updated successfully",
"admin.conversation_status.created": "Status created successfully",
"admin.conversation_status.delete_confirmation_title": "Are you absolutely sure?",
"admin.conversation_status.delete_confirmation": "This action cannot be undone. This will permanently delete this status.",
"admin.inbox.name.description": "Name for your inbox.",
"admin.inbox.from_email_address.placeholder": "My inbox <support{'@'}example.com>",
@@ -370,7 +382,6 @@
"admin.inbox.helo_hostname.description": "The hostname to use in the HELO/EHLO command. If not set, defaults to localhost.",
"admin.inbox.skipTLSVerification": "Skip TLS Verification",
"admin.inbox.skipTLSVerification.description": "Skip hostname check on the TLS certificate.",
"admin.inbox.delete_confirmation_title": "Are you absolutely sure?",
"admin.inbox.delete_confirmation": "This action cannot be undone. This will permanently delete this inbox.",
"admin.inbox.updated": "Inbox updated successfully",
"admin.inbox.created": "Inbox created successfully",
@@ -382,6 +393,8 @@
"admin.inbox.configureChannel": "Configure channel",
"admin.inbox.email": "Email",
"admin.inbox.createEmailInbox": "Create Email Inbox",
"admin.user.deleteConfirmation": "This action cannot be undone. This will permanently delete this user.",
"admin.user.new": "New user",
"globals.buttons.save": "Save",
"globals.buttons.save_changes": "Save changes",
"globals.buttons.cancel": "Cancel",

View File

@@ -16,23 +16,23 @@ var (
)
type User struct {
ID int `db:"id" json:"id,omitempty"`
ID int `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
FirstName string `db:"first_name" json:"first_name"`
LastName string `db:"last_name" json:"last_name"`
Email null.String `db:"email" json:"email,omitempty"`
Email null.String `db:"email" json:"email"`
Type string `db:"type" json:"type"`
AvailabilityStatus string `db:"availability_status" json:"availability_status"`
PhoneNumber null.String `db:"phone_number" json:"phone_number,omitempty"`
PhoneNumber null.String `db:"phone_number" json:"phone_number"`
AvatarURL null.String `db:"avatar_url" json:"avatar_url"`
Enabled bool `db:"enabled" json:"enabled"`
Password string `db:"password" json:"-"`
Roles pq.StringArray `db:"roles" json:"roles,omitempty"`
Permissions pq.StringArray `db:"permissions" json:"permissions,omitempty"`
Meta pq.StringArray `db:"meta" json:"meta,omitempty"`
CustomAttributes pq.StringArray `db:"custom_attributes" json:"custom_attributes,omitempty"`
Teams tmodels.Teams `db:"teams" json:"teams,omitempty"`
Roles pq.StringArray `db:"roles" json:"roles"`
Permissions pq.StringArray `db:"permissions" json:"permissions"`
Meta pq.StringArray `db:"meta" json:"meta"`
CustomAttributes pq.StringArray `db:"custom_attributes" json:"custom_attributes"`
Teams tmodels.Teams `db:"teams" json:"teams"`
ContactChannelID int `db:"contact_channel_id" json:"contact_channel_id,omitempty"`
NewPassword string `db:"-" json:"new_password,omitempty"`
SendWelcomeEmail bool `db:"-" json:"send_welcome_email,omitempty"`