settings modal

This commit is contained in:
2023-05-28 18:35:27 +02:00
parent 28f2f9a9ca
commit 37e677ec6a
12 changed files with 268 additions and 57 deletions

4
components.d.ts vendored
View File

@@ -9,6 +9,7 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AccountSettings: typeof import('./src/components/TopBar/Settings/AccountSettings.vue')['default']
Auth: typeof import('./src/components/Auth.vue')['default']
Autocomplete: typeof import('./src/components/Note/Autocomplete.vue')['default']
Hamburger: typeof import('./src/components/TopBar/Hamburger.vue')['default']
@@ -18,6 +19,7 @@ declare module '@vue/runtime-core' {
Note: typeof import('./src/components/ViewModes/Note.vue')['default']
NoteEditor: typeof import('./src/components/Note/NoteEditor.vue')['default']
NoteReferences: typeof import('./src/components/Note/NoteReferences.vue')['default']
NotesSourceSwitcher: typeof import('./src/components/TopBar/Settings/NotesSourceSwitcher.vue')['default']
NoteToolbar: typeof import('./src/components/Note/NoteToolbar.vue')['default']
SearchBar: typeof import('./src/components/Search/SearchBar.vue')['default']
SearchResult: typeof import('./src/components/Search/SearchResult.vue')['default']
@@ -25,6 +27,7 @@ declare module '@vue/runtime-core' {
SideBar: typeof import('./src/components/SideBar.vue')['default']
SideBarMenu: typeof import('./src/components/SideBar/SideBarMenu.vue')['default']
SideBarMenuItem: typeof import('./src/components/SideBar/SideBarMenuItem.vue')['default']
SignOut: typeof import('./src/components/TopBar/Settings/SignOut.vue')['default']
SkeletonNote: typeof import('./src/components/Skeleton/SkeletonNote.vue')['default']
SkeletonSidebarItem: typeof import('./src/components/Skeleton/SkeletonSidebarItem.vue')['default']
SkeletonTopBar: typeof import('./src/components/Skeleton/SkeletonTopBar.vue')['default']
@@ -34,6 +37,7 @@ declare module '@vue/runtime-core' {
UIBadge: typeof import('./src/components/ui/UIBadge.vue')['default']
UIButton: typeof import('./src/components/ui/UIButton.vue')['default']
UIButtonGroup: typeof import('./src/components/ui/UIButtonGroup.vue')['default']
UICard: typeof import('./src/components/ui/UICard.vue')['default']
UIDropdown: typeof import('./src/components/ui/UIDropdown.vue')['default']
UIDropdownItem: typeof import('./src/components/ui/UIDropdownItem.vue')['default']
UIInputCheckbox: typeof import('./src/components/ui/UIInputCheckbox.vue')['default']

65
package-lock.json generated
View File

@@ -41,6 +41,7 @@
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@tailwindcss/typography": "^0.5.9",
"@tsconfig/node18": "^2.0.1",
"@types/crypto-js": "^4.1.1",
"@types/dompurify": "^3.0.2",
@@ -1990,6 +1991,34 @@
"integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==",
"dev": true
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
"integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
"dev": true,
"dependencies": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -8019,6 +8048,12 @@
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
},
"node_modules/lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
@@ -13874,6 +13909,30 @@
"integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==",
"dev": true
},
"@tailwindcss/typography": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
"integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
"dev": true,
"requires": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"dependencies": {
"postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
}
}
}
},
"@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@@ -18551,6 +18610,12 @@
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
},
"lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true
},
"lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",

View File

@@ -48,6 +48,7 @@
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@tailwindcss/typography": "^0.5.9",
"@tsconfig/node18": "^2.0.1",
"@types/crypto-js": "^4.1.1",
"@types/dompurify": "^3.0.2",

View File

@@ -1,61 +1,23 @@
<script setup lang="ts">
import { activeNotesSource, availableNotesSources } from '@/composables/useNotes'
import { signOut as firebaseSignOut } from '@/composables/useFirebase'
import { OnClickOutside } from '@vueuse/components'
import { preferredNotesSource } from '@/composables/useSettings'
const sourceLabels: { [source: string]: string } = {
local: 'Switch to local notes',
firebase: 'Switch to cloud notes'
}
const signOut = async (close: () => Promise<boolean>) => {
await firebaseSignOut()
preferredNotesSource.value = null
close()
}
const blur = () => (document.activeElement as HTMLElement)?.blur()
const handleClick = (fn: (...args: any[]) => any) => {
blur()
fn()
}
</script>
<template>
<OnClickOutside>
<UIDropdown class="search-active-hide">
<template #activator>
<UIButton :dropdown="true" size="sm" variant="outline" class="py-1 text-white topbar-button">
<UIButton
:dropdown="true"
size="sm"
variant="outline"
class="topbar-button py-1 text-white"
>
<i class="fa-fw fa-solid fa-user-gear" />
</UIButton>
</template>
<template #items>
<UIDropdownItem
v-for="source in availableNotesSources.filter((source) => source !== activeNotesSource)"
:key="source"
@click="handleClick(() => (preferredNotesSource = source))"
>
<i class="fa-fw fa-solid fa-database" />
{{ sourceLabels[source] }}
</UIDropdownItem>
<UIModal>
<template #activator="{ open }">
<UIDropdownItem @click="open">
<i class="fa-fw fa-solid fa-right-from-bracket" />
Sign out
</UIDropdownItem>
</template>
<template #title>Sign out</template>
<template #default>
<p>Are you sure want to signout?</p>
<p>Your synchronized notes can't be accessed until you sign-in again.</p>
</template>
<template #actions="{ close }">
<UIButton size="sm" @click="close">Cancel</UIButton>
<UIButton size="sm" color="primary" @click="signOut(close)">Sign out</UIButton>
</template>
</UIModal>
<NotesSourceSwitcher />
<AccountSettings />
<SignOut />
</template>
</UIDropdown>
</OnClickOutside>

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import { user } from '@/composables/useFirebase'
import { format } from 'date-fns'
const verificationEmailSent = ref(false)
const sendVerificationMail = () => {
if (!user.value) throw Error("User doesn't exist, can't send verification email")
user.value.sendEmailVerification()
verificationEmailSent.value = true
}
</script>
<template>
<UIModal size="lg">
<template #activator="{ open }">
<UIDropdownItem @click="open">
<i class="fa-fw fa-solid fa-sliders" />
Settings
</UIDropdownItem>
</template>
<template #title>
<i class="fa-fw fa-solid fa-sliders mr-2" />
Settings
</template>
<template #default>
<div class="space-y-2">
<UICard>
<template #title>Account</template>
<template #default>
<div class="w-full flex-row sm:flex">
<div class="font-bold sm:w-4/12">E-mail address</div>
<div>{{ user?.email }}</div>
</div>
<div class="w-full flex-row sm:flex">
<div class="font-bold sm:w-4/12">Verified</div>
<div class="col-auto">
<UIBadge :color="user?.emailVerified ? 'success' : 'warning'">
{{ user?.emailVerified ? 'Verified' : 'Not yet verified' }}
</UIBadge>
<UIButton
size="sm"
class="ml-2"
@click="sendVerificationMail"
v-if="!user?.emailVerified"
:disabled="verificationEmailSent"
>
{{
verificationEmailSent
? 'Verification email sent'
: 'Request new verification email'
}}
</UIButton>
</div>
</div>
<div class="w-full flex-row sm:flex">
<div class="font-bold sm:w-4/12">Account creation date</div>
<div>
{{ format(Date.parse(user?.metadata?.creationTime || ''), 'dd/MM/yyyy') }}
</div>
</div>
</template>
</UICard>
<UICard>
<template #title>Notes</template>
<template #default>
<div class="w-full flex-row sm:flex items-center">
<div class="font-bold sm:w-4/12">Export notes</div>
<UIButton size="sm" color="secondary"><i class="fa-fw fa-solid fa-file-export mr-2"></i>Export notes</UIButton>
</div>
<div class="w-full flex-row sm:flex items-center">
<div class="font-bold sm:w-4/12">Delete account</div>
<UIButton size="sm" color="error"><i class="fa-fw fa-solid fa-trash mr-2"></i>Delete account</UIButton>
</div>
<div class="w-full flex-row sm:flex items-center">
<div class="font-bold sm:w-4/12">End-to-end encryption</div>
<UIButton size="sm" color="secondary"><i class="fa-fw fa-solid fa-key mr-2"></i>Enable end-to-end encryption</UIButton>
</div>
</template>
</UICard>
</div>
</template>
</UIModal>
</template>

View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
import { activeNotesSource, availableNotesSources } from '@/composables/useNotes'
import { preferredNotesSource } from '@/composables/useSettings'
const sourceLabels: { [source: string]: string } = {
local: 'Switch to local notes',
firebase: 'Switch to cloud notes'
}
const blur = () => (document.activeElement as HTMLElement)?.blur()
const handleClick = (fn: (...args: any[]) => any) => {
blur()
fn()
}
</script>
<template>
<UIDropdownItem
v-for="source in availableNotesSources.filter((source) => source !== activeNotesSource)"
:key="source"
@click="handleClick(() => (preferredNotesSource = source))"
>
<i class="fa-fw fa-solid fa-database" />
{{ sourceLabels[source] }}
</UIDropdownItem>
</template>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { preferredNotesSource } from '@/composables/useSettings'
import { signOut as firebaseSignOut } from '@/composables/useFirebase'
const signOut = async (close: () => Promise<boolean>) => {
await firebaseSignOut()
preferredNotesSource.value = null
close()
}
</script>
<template>
<UIModal>
<template #activator="{ open }">
<UIDropdownItem @click="open">
<i class="fa-fw fa-solid fa-right-from-bracket" />
Sign out
</UIDropdownItem>
</template>
<template #title>
<i class="fa-fw fa-solid fa-right-from-bracket mr-2" />
Sign out
</template>
<template #default>
<p>Are you sure want to signout?</p>
<p>Your synchronized notes can't be accessed until you sign-in again.</p>
</template>
<template #actions="{ close }">
<UIButton size="sm" @click="close">Cancel</UIButton>
<UIButton size="sm" color="primary" @click="signOut(close)">Sign out</UIButton>
</template>
</UIModal>
</template>

View File

@@ -2,11 +2,13 @@
interface Props {
size?: 'xs' | 'sm' | 'md' | 'lg'
variant?: 'regular' | 'outline' | 'ghost'
color?: 'regular' | 'info' | 'success' | 'warning' | 'error'
}
const props = withDefaults(defineProps<Props>(), {
size: 'md',
variant: 'regular'
variant: 'regular',
color: 'regular'
})
const styleClass = computed(() => {
@@ -21,11 +23,19 @@ const styleClass = computed(() => {
outline: 'dui-badge-outline',
ghost: 'dui-badge-ghost'
}
const colorVariants = {
regular: '',
info: 'dui-badge-info',
success: 'dui-badge-success',
warning: 'dui-badge-warning',
error: 'dui-badge-error'
}
const sizeClass = sizeVariants[props.size]
const variantClass = variantVariants[props.variant]
return [sizeClass, variantClass]
const colorClass = colorVariants[props.color]
return [sizeClass, variantClass, colorClass]
})
</script>
<template>
<div class="dui-badge" :class="styleClass"><slot></slot></div>
<span class="dui-badge" :class="styleClass"><slot></slot></span>
</template>

View File

@@ -2,7 +2,7 @@
interface Props {
size?: 'xs' | 'sm' | 'md' | 'lg'
variant?: 'regular' | 'outline'
color?: 'regular' | 'primary'
color?: 'regular' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'
dropdown?: boolean
}
@@ -22,7 +22,12 @@ const styleClass = computed(() => {
}
const colorVariants = {
regular: '',
primary: 'dui-btn-primary'
primary: 'dui-btn-primary',
secondary: 'dui-btn-secondary',
info: 'dui-btn-info',
success: 'dui-btn-success',
warning: 'dui-btn-warning',
error: 'dui-btn-error'
}
const variantVariants = {
regular: '',

View File

@@ -0,0 +1,9 @@
<template>
<div class="dui-card bg-base-100 shadow-sm">
<div class="dui-card-body">
<h3 class="dui-card-title" v-if="$slots.title"><slot name="title" /></h3>
<slot></slot>
<div class="dui-card-actions justify-end" v-if="$slots.actions"></div>
</div>
</div>
</template>

View File

@@ -5,10 +5,12 @@ const props = withDefaults(
defineProps<{
open?: boolean
persistent?: boolean
size?: 'sm' | 'md' | 'lg'
}>(),
{
open: false,
persistent: false
persistent: false,
size: 'md'
}
)
@@ -46,6 +48,16 @@ const onLeave = (el: Element, done: () => void): void => {
el.addEventListener('transitionend', () => done())
}
const styleClass = computed(() => {
const sizeVariants = {
'sm': 'max-w-xs',
'md': 'max-w-md',
'lg': 'max-w-2xl'
}
const sizeClass = sizeVariants[props.size]
return [sizeClass]
})
defineExpose({ open, close })
</script>
<template>
@@ -53,8 +65,10 @@ defineExpose({ open, close })
<Teleport to="body">
<Transition @enter="onEnter" @leave="onLeave" appear>
<div class="dui-modal bg-neutral-800 bg-opacity-60" v-if="show" ref="modal">
<div class="dui-modal-box" ref="modalBox">
<h3 class="text-lg font-bold" v-if="$slots.title"><slot name="title" /></h3>
<div class="dui-modal-box" :class="styleClass" ref="modalBox">
<h3 class="flex items-center text-xl font-bold" v-if="$slots.title">
<slot name="title" />
</h3>
<p class="py-4">
<slot v-bind="slotProps" />
</p>

View File

@@ -1,7 +1,7 @@
const colors = require('tailwindcss/colors')
const appWidth = '1280px'
const sideBarWidth = '220px'
const sideBarWidthMobile ='260px'
const sideBarWidthMobile = '260px'
const primary = '#1E4BC4'
const secondary = colors.gray[500]
@@ -29,7 +29,7 @@ export default {
}
}
},
plugins: [require('daisyui')],
plugins: [require('@tailwindcss/typography'), require('daisyui')],
daisyui: {
prefix: 'dui-',
themes: [