Skip to content

Commit

Permalink
Merge pull request #42 from krantheman/feat-settings
Browse files Browse the repository at this point in the history
feat: add Settings
  • Loading branch information
krantheman authored Dec 10, 2024
2 parents 56cbfbf + 51d30bc commit 00471b0
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 65 deletions.
38 changes: 38 additions & 0 deletions frontend/src/components/Controls/Copy.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<template>
<label class="block pt-2 text-sm">
<span class="mb-2 block leading-4 text-gray-700">{{ props.label }}</span>
<button
class="border-2 rounded-lg bg-gray-100 p-2 w-full flex items-center"
@click="copyToClipBoard(props.value)"
>
<span class="text-gray-800">{{ props.value }}</span>
<span class="border rounded bg-white p-1 text-gray-600 text-xs ml-auto">
{{ message }}
</span>
</button>
</label>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Copy')
const props = defineProps({
label: {
type: String,
required: true,
},
value: {
type: String,
required: true,
},
})
const copyToClipBoard = async (text) => {
await navigator.clipboard.writeText(text)
message.value = 'Copied!'
setTimeout(() => {
message.value = 'Copy'
}, 2000)
}
</script>
13 changes: 3 additions & 10 deletions frontend/src/components/Modals/SendMail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ const bcc = ref(false)
const emoji = ref()
const isSend = ref(false)
const isMailWatcherActive = ref(true)
const { setCurrentMail } = userStore()
const { defaultOutgoing, setCurrentMail } = userStore()
const SYNC_DEBOUNCE_TIME = 1500
Expand Down Expand Up @@ -228,6 +228,7 @@ const syncMail = useDebounceFn(() => {
}, SYNC_DEBOUNCE_TIME)
const emptyMail = {
from: defaultOutgoing.data,
to: '',
cc: '',
bcc: '',
Expand Down Expand Up @@ -267,18 +268,10 @@ watch(
watch(mail, syncMail)
const defaultOutgoing = createResource({
url: 'mail_client.api.mail.get_default_outgoing',
auto: true,
onSuccess(data) {
if (data) mail.from = data
},
})
const createDraftMail = createResource({
url: 'mail_client.api.outbound.send',
method: 'POST',
makeParams(values) {
makeParams() {
return {
// TODO: use mailbox display_name
from_: `${user.data?.full_name} <${mail.from}>`,
Expand Down
58 changes: 58 additions & 0 deletions frontend/src/components/Modals/Settings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<template>
<Dialog v-model="show" :options="{ title: __('Settings'), size: '5xl' }">
<template #body>
<div class="flex" :style="{ height: 'calc(100vh - 9rem)' }">
<div class="flex w-52 shrink-0 flex-col bg-gray-50 py-3 p-4 border-r">
<h1 class="text-xl font-semibold leading-6 text-gray-900 px-2">Settings</h1>
<div class="mt-3 space-y-1">
<button
v-for="tab in tabs"
:key="tab.label"
class="flex h-7 w-full items-center gap-2 rounded px-2 py-1"
:class="[
activeTab.label == tab.label ? 'bg-gray-200' : 'hover:bg-gray-100',
]"
@click="activeTab = tab"
>
<component :is="tab.icon" class="h-4 w-4 text-gray-700 stroke-[1.5]" />
<span class="text-base text-gray-800">
{{ tab.label }}
</span>
</button>
</div>
</div>
<div class="flex flex-1 flex-col p-12 overflow-y-auto">
<component :is="activeTab.component" v-if="activeTab" />
</div>
<Button
class="my-3 mr-4 absolute right-0"
variant="ghost"
icon="x"
@click="show = false"
/>
</div>
</template>
</Dialog>
</template>
<script setup>
import { markRaw, ref } from 'vue'
import UserSettings from '@/components/Settings/UserSettings.vue'
import MailboxSettings from '@/components/Settings/MailboxSettings.vue'
import { Dialog, Button } from 'frappe-ui'
import { User, Mailbox } from 'lucide-vue-next'
const show = defineModel()
const tabs = [
{
label: 'User',
icon: User,
component: markRaw(UserSettings),
},
{
label: 'Mailbox',
icon: Mailbox,
component: markRaw(MailboxSettings),
},
]
const activeTab = ref(tabs[0])
</script>
107 changes: 107 additions & 0 deletions frontend/src/components/Settings/MailboxSettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<template>
<h1 class="font-semibold mb-8">Mailbox</h1>
<div class="flex items-center mb-3">
<span class="font-medium leading-normal text-gray-800 text-base">Email Address</span>
<Link
v-model="email"
doctype="Mailbox"
:filters="{ user: userResource.data?.name }"
class="ml-auto"
/>
</div>
<div v-if="mailbox.doc" class="space-y-1.5">
<Switch
label="Enabled"
v-model="mailbox.doc.enabled"
@update:modelValue="mailbox.setValue.submit({ enabled: mailbox.doc.enabled })"
/>
<Switch
label="Incoming"
v-model="mailbox.doc.incoming"
@update:modelValue="mailbox.setValue.submit({ incoming: mailbox.doc.incoming })"
/>
<Switch
label="Outgoing"
v-model="mailbox.doc.outgoing"
@update:modelValue="mailbox.setValue.submit({ outgoing: mailbox.doc.outgoing })"
/>
<Switch
label="Default Outgoing"
v-model="mailbox.doc.is_default"
:disabled="!mailbox.doc.outgoing"
@update:modelValue="mailbox.setValue.submit({ is_default: mailbox.doc.is_default })"
/>
<Switch
label="Track Outgoing Mail"
v-model="mailbox.doc.track_outgoing_mail"
:disabled="!mailbox.doc.outgoing"
@update:modelValue="
mailbox.setValue.submit({ track_outgoing_mail: mailbox.doc.track_outgoing_mail })
"
/>
<Switch
label="Create Mail Contact"
v-model="mailbox.doc.create_mail_contact"
@update:modelValue="
mailbox.setValue.submit({ create_mail_contact: mailbox.doc.create_mail_contact })
"
/>
<div class="mx-2.5 space-y-2.5 pt-0.5">
<div class="flex items-center justify-between">
<span class="font-medium leading-normal text-gray-800 text-base">
Display Name
</span>
<TextInput
v-model="mailbox.doc.display_name"
@input="
mailbox.setValueDebounced.submit({
display_name: mailbox.doc.display_name,
})
"
/>
</div>
<div class="flex items-center justify-between">
<span class="font-medium leading-normal text-gray-800 text-base">Reply To</span>
<TextInput
v-model="mailbox.doc.reply_to"
@input="mailbox.setValueDebounced.submit({ reply_to: mailbox.doc.reply_to })"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from 'vue'
import { Switch, TextInput, createDocumentResource } from 'frappe-ui'
import Link from '@/components/Controls/Link.vue'
import { userStore } from '@/stores/user'
const { userResource, defaultOutgoing } = userStore()
const email = ref(defaultOutgoing.data)
const fetchMailbox = () => {
mailbox.name = email.value
mailbox.reload()
}
onMounted(fetchMailbox)
watch(email, fetchMailbox)
const mailbox = createDocumentResource({
doctype: 'Mailbox',
name: email.value,
auto: false,
transform(data) {
for (const d of [
'enabled',
'incoming',
'outgoing',
'is_default',
'track_outgoing_mail',
'create_mail_contact',
]) {
data[d] = !!data[d]
}
},
})
</script>
63 changes: 63 additions & 0 deletions frontend/src/components/Settings/UserSettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<template>
<h1 class="font-semibold mb-8">User</h1>
<div class="flex justify-start w-full items-center gap-x-4">
<Avatar
:image="user.data?.user_image"
:label="user.data?.full_name"
size="3xl"
class="w-20 h-20"
/>
<div class="flex flex-col">
<span class="text-xl font-semibold">{{ user.data?.full_name }}</span>
<span class="text-base text-gray-700">{{ user.data?.email }}</span>
</div>
</div>

<div class="mt-12 mb-4 flex items-center">
<h1 class="font-semibold">API Access</h1>
<Button
:label="user.data?.api_key ? 'Regenerate Secret' : 'Generate Keys'"
class="ml-auto"
@click="generateKeys.submit()"
/>
</div>

<Copy v-if="user.data?.api_key" label="API Key" :value="user.data?.api_key" />

<div v-else class="mt-2">
<p class="text-base">You don't have an API key yet. Generate one to access the API.</p>
</div>

<Dialog v-model="showSecret" :options="{ title: __('API Access') }">
<template #body-content>
<p class="text-base">
Please copy the API secret now. You won’t be able to see it again!
</p>
<Copy label="API Key" :value="user.data?.api_key" />
<Copy label="API Secret" :value="apiSecret" />
</template>
</Dialog>
</template>
<script setup>
import { ref, inject } from 'vue'
import Copy from '@/components/Controls/Copy.vue'
import { Avatar, Button, Dialog, createResource } from 'frappe-ui'
const user = inject('$user')
const showSecret = ref(false)
const apiSecret = ref('')
const generateKeys = createResource({
url: 'frappe.core.doctype.user.user.generate_keys',
makeParams() {
return {
user: user.data?.name,
}
},
onSuccess(data) {
if (!user.data?.api_key) user.reload()
apiSecret.value = data.api_secret
showSecret.value = true
},
})
</script>
Loading

0 comments on commit 00471b0

Please sign in to comment.